Programming Style
The purpose of this document is to collect in one place
suggestions about programming style to be used in the Ptolemy project.
This document is available for use at the University of Texas at Austin
with permission of the
Ptolemy project
at the
University of California at Berkeley.
SUMMARY OF SUGGESTIONS BELOW
- Think of your software as a publication.
- If you have to use global variables, use long descriptive names.
- Never guess about data structure sizes.
- Use Purify to plug memory leaks and detect memory access violations.
- If what you're doing seems clever, think of another way to do it.
- Make core dumps impossible.
PROOFREAD
Software has become a publication medium. People will read your code.
Your name is on it, so you want to be proud of it. It seems that most
people stop working on the code when it "works." This is like stopping
work on a paper when what it says is correct, and not worrying at all
about how it is said. PROOFREAD YOUR CODE AFTER YOU'VE GOTTEN IT WORKING.
ERROR MESSAGES AND COMMENTS
Use grammatically correct structure. Why say "X not foo" when you could
say "X is not foo"? It's true that the former makes you sound like a
computer, but wouldn't it be better to sound like a literate human being?
NAME SPACE ISSUES
All programming languages have a global namespace. In C++, it's class
names, mainly. In Tcl, it's procedure names and global variables.
Choose your names carefully. Never name a class "Window" or a procedure
in Tcl "makeWindow" unless this class or procedure is designed to support
any window at all. Use longer names that more specific, and more
descriptive, like tkLogicAnalyzerMakeWindow. If you don't like long
names, FINAL NOTEtake a typing class, or learn to use registers in emacs.
CHOOSING NAMES
Never name a variable x
or i
unless its scope
is one or two lines. You cannot usefully search for these names in a
text editor. Use longer more descriptive names.
Never name a variable after an arbitrary symbol. For instance, if some
book you read used the Greek letter alpha for a step size, you might call
your variable "alpha". Alternatively, you could call it "stepSize".
Which do you think is better?
In Tcl, there is no limit to name sizes (as it should be). In some
primitive operating systems, names in a link file must be unique
in their first 13 characters. In a 13-character file system,
the stars MDSDFFFTDecInTime.pl and MDSDFFFTDecInFreq.pl will be
the same. This is too bad. Use all 13 characters wisely, and
use additional characters for readability. In the previous example,
MDSDFDecInTimeFFT.pl and MDSDFDecInFreqFFT.pl would be unique.
GLOBAL VARIABLES
When possible, avoid using global variables.
If you have to use global variables, then make them static to the module.
It is more robust and more easy to test functions if you pass all of the
information as arguments to the function.
Similarly, in C++ and itcl classes, do not create a new data member just
to store side information that a single method will use.
Instead, just pass the information as an argument to the method.
This is a common violation in the code generation stars when enumerating
over the ports in a multi-port hole.
For example, compare the go methods in the CGCAdd
and CGCAddCx stars.
The CGCAdd star relies on side effects, whereas the CGCAddCx star does not.
Which is easier to maintain?
Perhaps the worst violations of good programming style fall in this category
of global variables.
I've seen, for example, a global variable called y
set in Tcl
during the setup method of a star, and used during the run method. First,
it's a lousy name (see above).
Second, it is unlikely to have the same value by the time the run method
is invoked.
Third, if there is more than one instance of the star, THEN THIS VARIABLE
IS GUARANTEED TO HAVE THE WRONG VALUE when the go method is invoked.
The same global variable is being used to store state for two different stars!
Tcl isn't object oriented, but you can still write object-oriented code.
For Tcl stars, the recommended method is to store all state in the array
$starID
.
This array has a name set by the underlying base classes.
It will not clash with any name used in any other star.
To store a value in this array, just do:
set $starID(foo) value
To retrieve the value, do:
[set $starID(foo)]
We have been rewriting our tcl code in the object-oriented language
incr tcl to hide shared variables.
ARRAY, STRING, AND DATA STRUCTURE SIZES
The old fashioned way to handle strings is to guess about their size. E.g.:
#define STRING_SIZE 1024
char mystring[STRING_SIZE];
sprintf(mystring, ... );
Almost all programs that do this crash eventually. It's an axiom.
The same applies to arrays. Unless you know ahead of time the size of
the data structure you need, use a dynamic data structure. Tcl has a
very nice infinite string structure you can use from C. In Ptolemy, use
the InfString class. For arrays, once you can determine the required
array size, dynamically allocate the memory (be sure to free it later):
double* newArray = new double[arrayLen];
If the array size changes dynamically, use an unbounded list structure.
You can use Purify to verify that you have deallocated any memory
you allocated (see below).
DEALLOCATING MEMORY AND FINDING MEMORY LEAKS
A class should manage its own data members that are dynamically allocated.
Any data members that are pointers should be initialized to zero in the
constructor.
The destructor should always deallocate them.
This follows the rule that the class that allocates memory should be
responsible for deallocating it.
In some cases, that is not possible.
For example, many of the classes in Ptolemy have a method to return a
copy of themselves.
In that cases, it is up to the caller to deallocate the memory.
Deallocating dynamic memory is a hassle, but you should always free
allocated memory.
Run Purify on your code to find memory leaks and memory violations.
It will not be put into Ptolemy if Purify gives warnings.
For more information about how to use Purify and Ptolemy together,
see the Purify Section of
the Developer's Guide.
HACKS
I just rewrote some code that was full of uncommented Tcl lines like this:
set Sd [string trim $win .w]
After some puzzling over this, I figured out that the starID variable
(renamed to Sd
to save typing, presumably) was
being parsed from the window name, which had the form .w$starID.
Is this clever or dumb?
In this case, probably both.
The starID
should have been passed as a parameter into
the procedure.
The above code would break if a simply and innocent change was made
in the window name, such as changing it to .win$starID
.
DEFENSIVE PROGRAMMING
If someone else is going to use your code, assume they will use it wrong.
Thus, despite the fact that your documentation clearly states, for
example, that the
number of labels should equal the number of inputs, CHECK IT. Dumping
core is simply not an acceptable error message.
CORE DUMPS
Any code that dumps core under any circumstances is not ready to be sent
out to the world. It is possible to write crash-proof code. It is also
possible to test your code. If during testing it crashes, find the reason.
Crashes are not caused by gremlins. They are caused by faulty code.
Use the debugger. It's easy to use and has on-line help.
FINAL NOTE
If you write bad software, and are very lucky, someone will rewrite it
for you and you will still get credit for it. Only the person who rewrote
the code for you will know just how badly you did. More likely,
however, nobody will find the time to rewrite it, and your software will
either be thrown out (if someone reads it) or published (if nobody reads it).
The latter is the worst possible scenario for you. We have had to discard
entire domains because they were so badly written.