This tip builds upon a another tip, "Restrict Access to Variables Using Four Categories and the Magical Number". If you haven't already looked at that tip, you may wish to do so now. That tip suggested four ways to categorize your variables so you can keep intellectual control over them. This tip explains a fifth category.
What makes variables tricky is the same thing that gives them their power, they change values. After all, when a symbol X represents a constant, X is easier to keep track of than when X represents a variable.
Of course, we cannot make everything a constant. On the other hand, we can make variables less variable.
In fact, in a purely functional programming language, there is no assignment statement. Once a parameter X is bound to a value when a function F starts executing, X keeps that value. This is a lot like being a constant, but X can have different values each time F is executed.
The original functional programming language was LISP. However, LISP didn't stay purely functional. Today, most LISP programmers seem to rely heavily on SETQ which is LISP's form of the assignment statement. LISP's mutation is not surprising. Functional programming is anything but popular. I won't advocate it here.
Another approach is to permit assignments to X but only under controlled circumstances. Here is a classification scheme for variable-like symbols X :
The subprograms with statements that can alter X's value are called its controllers. X is not passed as a parameter to its controllers -- they access X directly. The constraint on X is called X's invariant,. The controllers must be written so that the invariant is satisfied whenever they are not active.
When you are dealing with a controlled variable, you know exactly which subprograms may alter it and you know what to expect from it when none of those subprograms is executing.
Here's a quick example. Suppose you are writing a program which
This program will require some respresentation of an output line. I'll place it in a variable Line and control Line with three procedures: clear_line, add_word, and shift_space.
My idea is to invoke clear_line whenever I'm ready to start a new output line. Then I'll invoke add_word each time I have another word to add to the line -- add_work returns a boolean, true iff it has been able to add the word. When the line is full, I'll adjust the spacing with shift_space. My algorithm for doing so will right-justify the line and spread out the extra spacing between words.
This idea is not fully developed. I've yet to decide exactly how shift_space will work. Will it automagically fix up Line? Or, will it simply shift blank space from the right to some place in the middle determined by an argument? If the latter, the subprogram which runs shift_space will need to be able to obtain information about the spacing in Line . I don't feel a burning need to solve this problem yet. What I'm doing now is nailing down the places where Line changes and the properties it has.
The property I'm counting on for Line is that it is always the same size: so many pixels high and so many pixels wide. This property is my invariant: the height and width of Line measured in pixels does not change.
The invariant doesn't tell you what the individual controllers do. It sets ground rules that have a strong influence on the way the controllers must operate.
The controllers aren't necessarily the only subprograms that deal with Line -- just the only subprograms that can change Line's value.
When you make a variable controlled you create two comments of this form:
X,Y and Z are controlled by .... Invariants affecting X, Y and Z are ...
These particular comments would document three variables that are controlled by the same subprograms. Any number of controlled variables can be documented at once if they involve the same subprograms and invariants. The invariant (or invariants) for the controlled variables (or variable) can involve other parts of the system as well. It simply describes properties you can count on when none of the controlling subprograms is executing.
When I read object-oriented documentation, I am often frustrated by the lack of detail. Objects will be named and methods listed. There may be a vague sentence telling what each method is for and, then again, there may not. Without any detail, the documentation seems to tell me nothing.
On the other hand, we all know that complete documentation is a lot of work and as I mention in Don't Write Comments that Lie ... complete documentation that isn't kept up-to-date can be worse than useless.
Controlled variable documentation is a way around this dilemma. With object-oriented design, it is your classes (or objects if you haven't any classes) that need documenting. Objects are the "variables" being controlled. Your documentation identifies which methods can change an object and some properties which the object can be trusted to have when none of those methods is executing. This documentation is only difficult to write if you haven't thought much of anything out. If you are the kind of programmer who programs first and thinks second, then start the documentation when the class or object is being used for the first time.
As my digression on object orientation shows, controlled variables need not be global. They can be regional as well. (Variables which make up an object's attributes are an example of regional variables.)
Here's a list of advantages of controlled variables.
To show how flexible the concept of controlled variable is: suppose that the line formatting example does not have fixed size lines. Let line size be altered with font size and margin settings. To handle these complications, Line will be controlled by five procedures: set_line_font, set_line_margins, clear_line, add_word, and shift_space. All of these work with parameters Line_Height and Line_Width. The invariant is that Line_Height has the value set by the most recent set_line_font and Line_Width has the value set by the most recent set_line_font or set_line_margins.
Here's a thought experiment to show you the value of formally controlling your variables. Look back through your work of the past couple months for a bug that involved a variable X having an unexpected value. This might have happened because of pointer or parameter errors. If so, find a different example.
In fact, you are looking for an example of error that occurred because you had one idea about X when you wrote one part of the program but a quite different idea when you wrote another part of the program. Such errors are not uncommon and debugging them can waste a lot of time.
Now, suppose X had been controlled. That means: as you created it, you wrote a comment listing exactly those subprograms that could change X and also a comment explaining the ground rules under which those subprograms could change X . As you wrote the code you made sure these comments were obeyed. If they could not be obeyed, you changed the comments and, then, rechecked all relevant code to see if the new comments were obeyed.
Would you have made your mistake?
I cannot answer that question. You will have to. If the answer is no, then you are starting to see the value of controlled variables. If the answer is yes, your programming error probably had to do with one subprogram not doing what was expected and not with a confusion about the general purpose of X. This is because the invariants you would have written about X describe its general properties, not the behavior of specific controllers.
My experience has been that I can understand and debug an individual subprogram more easily than I can understand and debug a set of subprograms. Moreover, when I have to deal with a set of subprograms, it is easier if I have written down some common characteristics they are supposed to have. In other words, documenting invariants early makes things easier for me without forcing me to write a lot of comments.
This tip is distributed to individuals free of charge from the Software Build and Fix web site. 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.
Return to the top of this document.