Software Design - Coding Standards
Coding standards are a bit like beer: 1) everybody has their favorite brand, 2) everybody believes that their favorite brand is the best brand, and 3) too much will make you sick. So use these standards as general guidelines, which can be violated in some situations.
General Structured Programming Standards
These standards apply to all procedural langauges. If your language is unstructured (like assembly), you can still follow the spirit of these standards by coding convention.
- One function = one purpose. For example, a function that reads a file then prints the file does two functions. It should be split into a function to read and a function to write.
- One entry node per function. Each function should have only one way to enter the function. In structured languages it is generally not possible to violate this guideline; however, in assembly or batch langauges, the use of GOTO can create multiple entry nodes to a function.
- One exit node per function. Each function must have only one exit path. The use of return or exit function may not always violate this because the compiler will generate a jump to the function's end (though multiple returns do create multiple exit nodes from the programmer's perspective, so violate this only when it helps readability). However, a GOTO or END statement violates this guideline. C++ exception handling also violates this, which is why it has to unwind the stack. The try...catch exception handling should be used only when it is appropriate.
- Limit Fan-In and Fan-Out, the number of variables affected. This means that a function only uses variables that it either created itself, or were passed in as parameters. Do not use global variables.
- Limit the number of basis paths in a function to less than 15. This is the number of distinct pathways through a function (also called Cyclomatic Complexity or McCabe's Complexity). See White Box Methods, Branch Testing, for a method of counting the number of basis paths. See Basis Paths for a description of a basis path. If the number of basis paths becomes large, the function should be broken into smaller functions. (Note: different sources recommend no more than 10, some 11. Some sources also allow cases in a switch statement to be ignored as long as each case is short and simple.)
Object-Oriented Programming Standards
- Limit coupling between classes. This means only using classes that were either created by this class, or were passed as parameters.
- Fish may be friends (not food), but classes should not. Friend classes create configuration management problems during maintenance. Classes should remain friendless - they really don't mind.
- Leave Multiple Inheritance to your relatives. Multiple Inheritance is another configuration management headache during maintenance. Avoid it whenever possible; the maintenance issues can become real headaches. Yes - Java and C# make use of it, and COM programming requires it. Java interfaces at least make sense, but that is an example of a well thought out use of multiple inheritance.
- Use Design Patterns. The book Design Patterns describes numerous standard patterns for common tasks.
- Keep data members private. Use access methods for accessing the private data.
- Minimize header file dependancies. For example, if 10 modules use ClassA, then even though they cannot access ClassA's private and protected members, they still have to be rebuilt anytime ClassA's header file changes - even if the change to ClassA had no effect on them (such as adding a private data member)! This dependency can be decoupled by creating a wrapper class for ClassA that only exposes the public members, with the 10 modules only using the wrapper's header file. That way, if anything private or protected changes in ClassA, the other 10 modules do not require rebuilding.
Returning arbitrary datatypes can also minimize coupling. For example, Windows returns many arbitrary data types, such as HWND, HINSTANCE, and HBITMAP. Often these are pointers or indexes to classes inside Microsoft Windows™, which are simply typecast to (void*) so that our programs do not become dependant on the header files that were used to build Windows itself. This allows Windows to be changed without breaking prior programs (in theory!).