| Data Acquisition and Online Analysis at the NSCL | ||
|---|---|---|
| Prev | Chapter 5. Best Practices for Building Software | Next |
Comment your code fiercly and profusely. Keep comments up to date. While computers will see your code more often than people, when people see your code, they (including you need to understand it). Any explanatory help you can give them improves the likely hood of correct understanding and decreaases the time to reach it. At a minimum you should:
Put a header comment in the front of each file that describes the purpose of the code in that file.
Put a comment in front of the definition of each non-trivial data structure you use that describes what it is for and what the key members are.
Put a header comment in front of each non trivial function/procedure that descibes the procedure, its intended use, inputs and outputs.
If a function/procedure can be broken down into several blocks of code, consider either breaking it into several functions/procedures or at least using comments to document what each block is doing.
Give variables descriptive english names. Try to also pick a naming convention that allows you to deduce something about the scope (member, global, local) of a variable from its name (SpecTcl, for example prefixes member variables with m_ and global variables with g).
Give procedures and functions descriptive verb based english names (e.g. computePosition).
Use consistent indenting to show how your flow control stuctures nest.
Use blank lines to indicate breaks in logical thought withinthe program. Use whitespace within statements to make them more readable. A completely blank line or a blank space costs you exactly one keystroke and one byte of storage, not much to pay for improved readability.
So that functions and procedures are easy to understand, try to minimize the use of global data. Rely instead on class member data or perform computations based on your parameters. Even more strongly, try to avoid modifying global data as much as possible.
Minimize the #include directives in your headers by using forward class definitions. Minimizing the dependencies of your headers on other headers helps prevent circular header dependencies.
Make your headers 'complete'. This means that a file consisting of:
#include "myheader.h"
Should compile without error. This makes it easier for your
users, and you to keep write software by placing the
dependencies for myheader.h where
they belong. In myheader.h itself.
There can be a design tension between this and the previous
item.
The compiler attempts to provide default versions of copy constructors, assignment and == comparison operators. These are based on bitwise copies, and comparison and are often not correct. They are never correct for classes that use dynamic storage, as to be safe you must copy the storage as well (deep copy) or manage reference counts to ensure that the destruction of one of the objects holding a pointer to storage does not destroy the storage out from underneath other objects that are retaining this storage. You have a few choices:
Implement your own versions of these functions in terms of deep copies of dynamic storage so that memory leaks and 'pointer accidents' are prevented.
Implement your own versions of these functions in terms of reference counted items. See Refptr.h in both SpecTcl and the nscldaq include areas for an implementation of reference counted dynamic storage.
Forbid the usage of copy construction, assignment, and comparison. This can be done by declaring these private: in your class header and never implementing them. Making these members private ensures that use outside of your class is flagged at compile time, while use inside your class gets flagged at link time. This is useful because it is possible to 'accidently' call copy construction or assignment operators in the flow of innocent looking code.
When constant numbers have some greater meaning, avoid using them literally, but define either a preprocessor macro or a static const variable to represent them in C++ or allocate a global variable to hold them in Tcl. This makes it much easier to read the code as well as to change the value of the variable when you need to as its value is only expressed in one place, rather than scatttered throughout the code. This should be especially adhered to for constants that represent sizes or operating limits.
All SpecTcl event processors that operate on the raw data should set the event size. This ensures that regardless of the mix of event processors used in the program at least one of them will do this necessary chore.