Adopt Tips For Healthy Software, Adopt Tools To Support Tips

Here

tip
means an ambiguous process for helping you develop or maintain software, and
tool
means an automated process for helping you develop or maintain software.

In spite of their ambiguity, tips (such as this one) can help you improve your software quality and lessen your production time.

Because they often promise to improve software quality and production time without much mental effort, tools can be lots more attractive than tips. In spite of their more mechanical nature, tools usually require much more time and money to implement than tips.

Both tips and tools sometimes don't live up to our expectations. Because they require more resources, we may notice the problem more with tools.

Here is a simple guideline for avoiding disappointment with your tool choices: ask not "What will this tool do for you?" Ask, instead, "What you are doing (or what do you want to do) that this tool will make easier?"

Your answer shouldn't be "build better software faster". It should involve specific aspects of the process by which you build better software faster. You should be able to summarize your answer this way, "this tool helps me use programming tips that I already know are valuable and it does so in a cost-effective manner."

Thinking this way will allow you to see some situations in which the tip doesn't really need the tool. This will stretch your tool resources further.

Thinking this way will allow you to keep in mind the difference between what tips are good for and what tools are good for. It is true that both are valuable, but you can't be a smart tool buyer if you lose track of which is which.

To help you think this way, I'll confess two bugs that I have introduced into beta software and I'll describe tips and/or tools would have helped prevent those bugs.

By the way, I am not claiming that software engineering considerations are the only aspects of choosing a tool. The way you chose your compiler probably would prove that claim wrong. I'm just not going to help with those inevitable non-software-engineering considerations.

The first bug needn't be described in detail -- in fact, I've forgotten what it was. What is important is that it exhibited a tell-tale property: it's behavior altered when I inserted print statements to trace the values of some variables.

When the addition of a print statement changes other program behavior, the reason, almost certainly, involves one of:

In my case, the first two possibilities were out -- I needed to find a part of my program that was fooling with memory allocated to another part of my program. (Which other part was affected by the size of the code in the various compilation units, i.e. by the introduction of print statements.) This kind of error is pernicious because its symptoms are completely unrelated to its causes.

An easy way to commit this kind of error is to mess up one's usage of pointers. In my case, this was a possibility. However, since I was working in pre-ANSI C and assembler, there were plenty of other possibilites.

My program was large enough that the source listing was an inch thick. My method of finding my bug involved writing my own tool for identifying which part of the code was using unallocated memory. To use the tool, I had to clean up my assembler code in a way that I should have done from the beginning. After the tool was written and the assembler code was cleaned up -- this was a few days work -- the bug was quickly found.

The bug was of a well known variety -- one that would have been caught by a program called lint had I chosen to acquire it. It involved a missing ampersand in a function invocation.

As many readers know, a function invocation in C that looks like f(A) passes the value of A into the function and a function invocation that looks like f(&A) passes the addess A into the function. If you pass a value where an address is expected, it is used as an address and the function fools with memory it has no business fooling with. One use of lint is to prevent that kind of error.

Even though a lint program would have solved my particular problem, I wasn't real sorry to have a more general tool that would work with pointer errors and I knew in my heart that my assembler code should have been cleaned up. (The tool, by the way, was entwined with the characteristics of the old C compiler I was using. You'd have to start over if you wanted one now.)

This example is interesting because it shows three stages under which a tool seems to evolve:

  1. In the first stage, there is a tip that can be applied in a more mechanical way than most. In the current example, the tip was to check all your function parameters to see that you are using pass-by-value and pass-by-address where required.

  2. Next, tools become available which implement the mechanical tip with more accuracy than humans can. In this case, the tool was called lint. Stage-two tools tend to be a little clumsy to use.

  3. Finally, general purpose tools evolve to the point where the error cannot be made.

    For example, newer versions of C and C++ prevent the error I made. They accomplish this through strong typing which ensures that addresses don't get mixed up with values.

    In another well-known example, today's programming languages often lack the go-to statement that caused problems for an earlier generation of programmers.

    Often, stage-three tools are so integrated with the development process that we don't really notice them.

It happens that C++ is better at preventing the kind of error I made than is C because its standard library extends strong type checking to functions for I/O and memory management. Even so, ugly-but-preventable errors are possible in C++. A good source of tips for avoiding such errors is Effective C++ That some of these tips will find their way into future language design, I have no doubt.

The second bug occurred in my PPP Control Management System which consists of two very small C programs and one not-so-large Expect script. These programs and script share a state file that became corrupted as the system was used. Although the programs run as concurrent processes, I soon discovered concurrency didn't have anything to do with my problem. I proved this in the simplest way possible: I recreated the problem in a single simple C program.

Bugs often arise because of a confusion between contexts. The first bug, for example, arose because a parameter had one form in the context of the function that used it and another in the context of the client that invoked it.

This bug arose because I confused file access in a random access context with file access in a sequential context. My needs were sequential, but I used random access methods. That isn't necessarily bad but t I was thinking sequential and so missed noticing one of the effects of random access. Put another way, I was thinking reset in Pascal and using seek in C.

I know now what my mistake was even though, at that time, I didn't apply the mental effort needed to find it. Since my thoughts and needs were sequential, I solved my problem by using sequential access in C. This caused files to be opened and closed more often than is necessary or elegant, but the PCMS programs work and don't seem to be an untoward drain on resources.

And how did I learn my mistake? I attached a note to the program explaining why I was opening and closing files so much. Within days of release at the Software Build and Fix web site, I had polite letters from readers telling me essentially this: when one rewrites a "r+ mode" file from the beginning and the new file is shorter than the old file, one will see some of the old file at the end. My "corruption" was simply the tail end of the previous incarnation of the file.

This example describes a use of program documentation that I haven't seen in discussed in the softare engineering literature: I used documentation to admit something most programmers sweep under the rug namely that I had made a mistake and was too intent on getting finished to bother finding my error.

I think this situation is common but, until now, you probably haven't seen a public admission of it. What was the advantage of my confession to me?

I was able to take the short cut and learn the thing which I should have known. I recommend that more of you follow my example and put the real reasons why you do things into your documentations. You'll become stronger programmers and those who read your program documentation will not be confused by misleading explanations of why you did what you did.

Of course, if you are working with sharks who will cut you up at every opportunity, you probably shouldn't follow this advice. In that case, I recommend you look for another job.

A tip to prevent an error such as mine is easily crafted. It would say something like "use fixed length records in random access files." (This isn't quite as restrictive as it might seem since unions in C or variant records in Pascal count as fixed length.)

It may seem that no tool would help with a bug like this second one. That's wrong. Once again it is possible for strong typing to come to the rescue. Suppose you were forced to declare your files as either SEQUENTIAL or INDEXED X where X is the type of record you allow in the file. With this regime, the first type of file is for sequential access and the second type is for random access in which each record has type X.

The kind of file corruption which I created would be impossible with this kind of random access file. (Yes, there would still be a problem when the number of records must be reduced but bugs involving this problem are easier to recognize.)

In summary, buy tools to support tips rather than use tips to make tools work. Always identify the tips that underlie your tools. Then, take the following into consideration.

Copyright 1996, J A Zimmer

These programming tips are distributed to individuals by the copyright holder, J Adrian Zimmer. All other distribution (including but not limited to internal distribution within an organization and mirroring of any kind) is forbidden without written consent of the copyright holder. The meaning of the word "distribution" here includes any kind of copying for use by a different person or organization than the one who obtained the programming tip from copyright holder.

Context  Some Tips for Programmers    Author J Adrian Zimmer  
Dated: May 23, 1996 ; Revised: Oct 07 1998