"Software is written for people, not for machines." — anon.
The key to successful (large) programming projects, like any other project, involves planning, management, and testing. The amount of time, effort and emphasis placed on each of these three components depends on the size of the project, the expected/desired outcomes, and the experience of the people involved. Consistency in design and implementation is also key to success, especially for ongoing software projects, and is what we will address in this article by outlining some (fairly standard) coding conventions that are used in the Darwin project. These conventions will make the project more manageable over time especially when many different people are involved.
It is absolutely guaranteed that some users will not like the style guidelines, and that others will even hate them. Everyone has their own style which they prefer to use on their own projects. As Grace Murray Hooper once said: "The great thing about
standards is that there are so many of them to choose from." However, as part of a collective effort, your team-mates, as well as yourself, will benefit greatly from the uniformity that a fixed set of style guidelines offer.
Finally, since the Darwin framework is designed to be platform-independent, it is essential that you make don't implement any platform specific functionality (or atleast provide generic implementations for other platforms). Following these guidelines will help reduce the amount of incompatibility introduced by developing in multiple environments.
Finally, before writing a new class or function, check to see whether one already exists that does what you want, or nearly what you want. Can that function be generalized to meet your needs? However, before making changes to library functions, think about how these changes will affect code alreay using these functions.
Source Control
- Always use a source/revision control system. Darwin uses Git. Other good systems include Subversion (svn) and Mecurial (hg).
- Check-in all code, configuration files and scripts necessary for rebuilding a project from a clean environment. Do not check-in files that can be regenerated or easily downloaded from somewhere else (e.g., third-party libraries).
- In direct contradiction to the above, it is sometimes useful to check-in (non-standard) external packages that the project requires.
- Don't forget to add new source and header files before doing a commit.
- The latest code checked into the repository is assumed to be correct. Always make sure you merge your code with the latest revision before checking in (i.e.,
svn update
before svn commit
). If you're working on something experimental then create a separate branch which you can merge later.
- Always make sure your code compiles and passes any regression tests before checking-in. Remember if you break something it's likely to affect a lot of other people.
- Check-in code regularly. Don't be afraid to check-in small changes. As mentioned above, if you're working on something big then create a separate branch and merge later.
- Add comments when you check-in code. This will help people (including yourself) understand what you were trying to do when they check-out your code and it doesn't work.
Structure
- Always include a header comment at the top of each file. The header should include the name of the project, name of the file, sometimes a copyright notice, and most importantly your name and email address.
- Group the declaration of public and private members. Separate the declaration of methods from variables.
- Likewise, group common header files together, starting from standard headers (e.g.,
stl
) through to project specific headers.
- Declare constructors and destructors before other methods.
- Declare like-functions together.
- Always implement functions in the same order in which they are declared in header files or in the prototype section at the top of the file.
- Code should be implemented in
.cpp
files not .h
files. Exceptions are templated and short inline functions.
- Name a file the same as the class that it implements. It is okay to implement multiple classes in the same file if they logically belong together. In this case find a filename the is appropriate to the group of classes.
Variable and Object Naming
- Use all-caps for constants and macros. Separate words with an underscore.
- All variables should be named starting with a lowercase letter.
- Prepend an underscore to private data members. Do not use two consecutive underscores. Do not follow an underscore with an uppercase letter (these are reserved e.g.,
_T
).
- Prepend 'b' to boolean types, 'g' to global types.
- Descriptive variable names are strongly preferred to compactified names. Exceptions are allowed for standard technical equations. For example, to evaluate a quadratic equation it is acceptable to write
y = a * x * x + b * x + c;
.
- Single letter variables should be restricted to either loop iterators (preferably i, j, or k), or terms in very local computations (i.e., it is okay to use 'x' in a computation only if the scope of 'x' is less than a few dozen lines of code at the most).
- All Darwin classes and namespaces begin with
drwn
.
Comments
- It is a waste of time to write software without comments! Your comments don't need to be lengthy, but they should be informative.
- Make sure you update your comments whenever you change your code.
Portability and Maintainability
- Never use variable or object names that could be keywords under different systems (e.g.,
min
, max
, win
, file
, interface
).
- Use standard libraries (available on all platforms)—in particular, the
stl
. Don't reinvent the wheel.
- On a similar note, use standard file formats (e.g., XML). Put version numbers in parameter/model files so that you can read them back even if you change the format later.
- Keep object interfaces short and simple, it will make it much easier for other people to learn and use.
- When composing
stl
datatypes make sure you put a space between the >
characters, for example vector<vector<double> >
. Some compilers will (correctly) interpret >>
as an operator and will generate an error.
- Some compilers like to have a blank line at the end of all source files. If you're working in multiple environments then it is good practice to do this.
- Set your editor to replace tabs with spaces. The default indentation in Darwin is four spaces.
- Do not have two (or more) file names that differ only in their character case—for one thing this will confuse SVN under Windows.
- Do not have code with side-effects inside
assert
statements. Often assert
s get commented out for release builds and you do not want the behaviour of your code to change.
Performance
- Don't copy large data structures around. Rather pass by reference (
&
) or by pointer (*
).
- Use
const
whenever you can.
- Avoid allocating and deallocating memory in tight loops—rather allocate all the memory you need outside of the loop, but don't forget to deallocate the memory eventually.
- Don't use
printf
's (or output stream operators) in tight loops.
- Use
reserve
to allocate memory to vectors and other stl
datatypes before populating.
- Use the appropriate data structure (container) for objects and know their running times. For example, calling
size
on an stl::list
can take O(n).
Miscellaneous
- Read the first chapter of ``The Mythical Man-Month'' by Frederick P. Brooks and remember that, although it was written in 1975, it still applied today.
- Avoid using
#define
when you can use a const variable or enum instead.
- Use structures, unions and classes to keep related variables together.
- Limit the use of global and static variables.
- Enclose conditionally executed code in braces (e.g., after an if or for statement) even if the code is only a simple statement. This will prevent bugs later on when you modify the code.
Testing and Debugging
- Write and use regression tests.
- Make sure your code compiles without any compiler warnings.
- If you discover a (non-trivial) bug, first write a simple test that exposes the bug, before debugging. Then add the test to your regression test suite.
- Use lots of
assert()
's. You can always compile them out for speed later. However, beware not to have side-effects inside the assert
.
- Run you code while watching system memory (Task Manager under Windows or
top
under Linux) to identify memory leaks. You can also use valgrind
.
- If you have a bug, first think about where in the design the bug could be, before jumping into the code.