This page contains guidelines on best coding practices that allow developers to write clean unambiguous code that is maintainable and easily accessible for debugging and future development. Four aspects are discussed here:
- Naming Conventions
- Coding Practices
- Regression Testing
Naming conventions and programming styles vary from developer to developer, and from team to team. Regardless of what conventions you adopt, the key to a successful and enjoyable programming experience is not so much coming up with a great naming convention, rather, to have the discipline and patience to adhere to the adopted convention and programming style.
This section will be based on the naming conventions adopted in Wasatch.
- All functions are lower case, with words separated by an underscore:
- Types (e.g., classes, structs, typedefs, etc.) use CamelCase notation:
// methods named with underscores separating them
// member variables named using camel-case with trailing underscore:
const double myConstDouble_;
- Private members of class use lowerCamelCase_ with a trailing underscore:
- All variables use lowerCamelCase – no exception – even if the variable starts with the name of a person
- Enumerations use all caps, with words separated by underscores:
- When using typedef for a type, always append the typedef with a T to emphasize that this is a Type. For example:
typedef CCVariable CCVarT;
typedef XVolField XVolT;
- Use const whenever possible.
- Member variables in classes should be made const when possible and set during constructor initialization
- Class methods should be made const when possible. If a method does not modify the state of the object, it should be made const:
// when a value does not change through application
// of any member functions, it should be const.
const double someValue_;
const HeavyObject& objRef_; // hold const references to objects when possible
double get_some_value() const; // access methods should always be const.
const HeavyObject& get_heavy_obj() const; // return const references from const access methods.
- Use references instead of pointers where possible. References MUST reference something while pointers can be NULL, hence the preference to use references. You will less likely make a mistake.
- Never open namespaces in header files unless it is within scope (e.g., within the body of a function, etc.).
- If necessary, you can open a namespace in a local scope only (between braces in an implementation for example)
- You can open namespaces in implementation files (.cpp, .cc files) as you see fit. If you do this, it is frequently better to open it selectively: [C] using std::vector[/C] rather than globally: [C]using namespace std[/C]
- Always use #include guards in header files. For example
#endif /* MyClass_H */
- Use Doxygen for documenting your code.
- No commit is acceptable without proper API documentation! That means that all public methods should be documented.
- Bad (wrong) documentation is worse than no documentation. If you make changes to the API, ensure that the documentation is updated to reflect these changes.
- Seasoned developers are responsible for enforcing API documentation.
- Whenever a new capability is added to the code, one or more regression tests should be added to exercise that capability.
- Test hierarchically! Unit tests, excercising individual pieces of code, should be employed where possible. Then tests covering combined portions of code, and finally full-scale tests exersizing the application-level code.
- Full-scale regression tests should be comprehensive and cover as many aspects of the code as possible. For example, if testing flux limiters, tests should exercise the limiters in 1D, 2D, and 3D with all direction permutations (X, Y, Z, XY, XZ, etc…)
- Design tests to help you catch bugs. For example, having tests that cover different aspects of the code can help you determine where a bug is by looking at which tests are passing/failing.
- Trust tests! Don’t be anxious to re-bless gold standards – even if errors are very small.
Please read this blog post.