Hades Coding Rules and Guidelines
based on
"HADES coding rules"
by R. Holzmann (June 1999) and
"HADES Computing Guide" by H. Schön and
M. Münch (Dec. 1996).
References
The following links are worth reading before you start programming:
Preface
The large number of developers and the complexity of the HADES
analysis (HYDRA) and simulation (HGEANT) packages make it necessary to introduce
- or rather revive - mandatory coding rules and to formulate experience based guidelines.
These coding rules are issued by the members of the Hades Software Maintenance Board (SMB).
The SMB controls the central code repository at GSI and enforces compliance of all new code
with these mandatory coding rules upon submission for check-in to the code management system.
Note that non-compliant code can and will be rejected by the SMB.
All HYDRA analysis software is developed and supported for LINUX as operating system.
The code-management system is currently "CVS" and will be "Subversion" in the near future.
C++ has been chosen as object oriented programming language and all C++ code is compiled with g++.
Contents
Programming advice
Keep the following 4 (5) points in mind while writing code
and also while interpreting the rules of this document.
- Keep it simple (code for clarity rather than efficiency). Break complexity into simpler chunks. Clearly comment necessary complexity.
- Be explicit. Avoid implicit or obscure features of the language. Say what you mean. Avoid tricks!
- Be consistent. Use the same rules as broadly as possible.
- Minimize scope. This includes logical and visual scope. Be more explicit about items with wider scope.
(M. Münch, 1996)
- Every time a rule is broken, this must be clearly documented.
Mandatory Conventions
- File names and directory names are written in lower case, using only a-z and 0-9.
- Files should be less than 1000 lines or about 20 pages.
- Modules shall be small enough so that they can be rewritten completely in a reasonable amount of time. Ideally, documentation in the header file should be sufficient to allow a rewrite.
- Each file starts with a line providing information in a well defined form. The information should contain at least: the code management system version, the original author and how to reach him and the time and the author of the last modification. We recommend to use
// $Id: HadesCodingRulesThomasEberl.txt,v 1.3 2006/12/18 12:55:31 ThomasEberl Exp www-data $
// Author: your name, your email address, last modifed on: date, time
as the first line of each new code file. "Id" is interpreted by CVS upon check-out.
- Suffixes are .h for header files, .cc for code files and .C for ROOT macros. Furthermore, plain Fortran files have .f, Fortran files with preprocessor statements have .F and Fortran include files have .inc.
- For class definition files, the file name should equal the class name.
- Comments are written in English.
- Names of data types, classes, typedefs, structures/unions start with a Capital, e.g. MyClass.
- All instances (variables, constants, classes, functions) start lower case, e.g. setValue(), nextChoice().
- Use capitals to separate words in a name, e.g. myFloatVariable, HerSmartClass; use underscores only to separate numerals, e.g. iteration_3.
- Globals must start with a g, e.g. gHades, and we recommend to start constants with a k, e.g. kUnit.
- Preprocessor constants and macros are all upper case with no leading or trailing underscores, e.g. TWO_PI, SUM(A,B).
- The following convention should be used for identifiers of larger scope:
- Hades base classes start with an H
- RICH-relevant classes start with HRich
- MDC-relevant classes start with HMdc
- SHOWER-relevant classes start with HShow
- TOFINO-relevant classes start with HTofino
- TOF-relevant classes start with HTof
- START-detector classes begin with HStart
- ORACLE-relevant classes start with HOra
- TRIGGER-relevant classes start with HTrigger
- RPC-relevant classes start with HRpc
- FORWARD WALL-relevant classes start with HWall
- ...
Rules
- Do not re#define the language.
- Avoid the preprocessor, use const when possible and static functions instead of macros. Most compilers can inline simple functions automatically.
- Code defensively. Always check the return values of functions and don't assume that the call succeeded. If your function only operates on a limited range of input values, check them at the beginning of the function and don't assume that the caller has made the check.
- Always check a pointer variable for a value different from 0 before using it.
- Do not use implicit evaluation order in equality expressions, use separate statements instead.
- Never compare floating points variables for equality. Instead write:
#include
if (fabs(value1 - value2)
In order to replace XX you find following definitions in /usr/include/float.h:
#define FLT_EPSILON 1.19209290e-07f
#define DBL_EPSILON 2.2204460492503131e-16
#define LDBL_EPSILON DBL_EPSILON
- To avoid multiple inclusions .h files should contain a precompiler definition, e.g. for myfile.h:
#ifndef MYFILE_H
#define MYFILE_H
[...]
#endif /*!MYFILE_H*/
- Decrease the number of #includes in .h files, put them into the .cc files and use class forward declarations in the .h (if only pointers to classes are declared), e.g.:
class MyClass; rather than #include "myclass.h"
class YourClass class YourClass
{ {
MyClass* f(); MyClass* f();
}; };
Advice on:
Types
- Implicit typing (of ints mostly) shall not be used.
- Do not assume any word length for the build in types, use the typedefed instead, i.e. use ROOT types whenever possible, e.g. Int_t instead of int. This rule is mandatory for streamed data members!
- Default to Int_t as the integral type. Use "unsigned" and "long" only when the large range is needed. Use Char_t and Short_t only when space is a problem.
Control structures
- Avoid deep nesting (of statements, parentheses and structures). Aim for a normal maximum of three levels.
- Make "case"s independent of each other, use a break at the end of each. Always use a default.
- Avoid complicated "if" constructs. It is better to use several simpler nested "if" constructs rather than a complicated compound one.
- Make implicit precedence rules explicit, if unclear: Use parentheses to identify the components of such constructs. (e.g. a && b || c)
- Use goto only for error handling, jumping outwards and downwards.
- Use break and continue in loops and return in functions only under abnormal circumstances (errors etc.), they are in fact disguised gotos.
- Never use "exit()" from within a class implementation! Use function return values and proper error handling instead.
Functions, Variables, Structs, ...
- Avoid functions with a large number of parameters. Use structs or classes instead or define default behavior via another public function of the module.
- Always use function prototypes.
- Minimize the use of globals.
- Never implicitly compare pointers to nonzero (i.e. do not treat them as having a boolean value). Use if ( 0 = ptr ) ... instead of if ( ptr ) ... If you are doing an assignment in a comparison expression, make the comparison explicit: while ( 0 = (ptr=iterator() ) )... is much better than while ( ptr=iterator() ) ...
- Avoid overloading functions and operators unless there is a clear improvement in the clarity of the resulting code. For read and write access to data members, use:
int myData( ) const;
void setMyData( const int value );
rather than
int myData( ) const;
void myData( const int value );
The former makes the intention clear
whereas the latter is an example of using a facility because it exists,
not because it has any advantages.
Naming instances
A uniform and consistent naming convention should prevent name conflicts when large
and complex programs are created that use code from many different libraries.
It also eases differentiation between local and external variables etc.
- Use descriptive English names of reasonable length (somewhere between 2 and 20 characters). The wider the scope, the longer the name. Variables with small, temporary scope can be given small names, but avoid single character names except for local loop and array indices.
- Avoid abbreviations (e.g. use nameLength instead of nLn) and non-standard acronyms.
- Multiple word names let the reader know the intended use of an item, avoiding ambiguities.
- Example
- Does "empty" mean a test for emptiness or setting the object to empty? In the former case, "isEmpty" would be better; in the latter case, use "setEmpty" instead.
- Try to name functions with a verb phrase (setValue, fitTrack, calculateResponse) and variables with a noun phrase (loopIndex, radiatorLength).
- You may use the following prefixes:
the - global variables
our - static variables in functions.
my - Data members of a class. This is strongly recommended in order to avoid name conflicts with any access or functions.
Constructors and operators
Every class should have a destructor and at least one constructor even if they do nothing
or are defined by nothing. You must define the copy constructor of your class
so that your objects are copied properly.
- Clearly comment any violation of the mandatory coding rules.
- Clearly comment necessary complexity in your code.
- Do not comment out code, use conditional compilation instead.
- To be compatible with the automatic ROOT HTML documentation scheme, comments should be placed in 3 places: 1) a class description on top of the .cc and the .h files 2) data member comments in the .h file 3) function member comments following the function names in the .cc file. c.f. to the ROOT source code for examples.
- Make every comment count (but do not use this as an excuse for under-commenting).
- Keep code and comments visually separate.
- Describe the purpose of a module in the header file.
- Provide a comment for each public function or function member - unless trivial - in the header file that describes the purpose of the function, the input and output arguments.
- Provide comments for each private function in the code file.
- Add comments to both the interface (.h) file as well as the implementation (.cc) file for each class. Interface comments should stress what the class does whereas implementation comments should stress how.
- The end of a long block (i.e. more than 24 lines) may be marked by a comment immediately after the closing brace:. Example:
while (i < 1)
...
] /* while (i... */
Format of Comments
When using C++, the preferred form for comments is:
// This is a one-line comment.
i.e. use the "//" comment format. If the comment extends over multiple lines, each line must have // at the beginning:
// This is a boring comment.
// Put // at the start of each line.
// Since the comment starts at the // and extends to the
// end of line, it can appear on the same line as code.
Unfortunately this construct is not part of ANSI C, where the form of a comment is
/* This is a C comment */
A uniform appearance can be maintained using the following form for C comments:
/*
// This boring C comment looks like a C++ comment
// by adding // at the start of each line
*/
Code layout
- Attempt to write linear code - i.e. code that starts at the first executable statement and flows down to a final "return" or end of block statement.
- Indent each nested block in order to highlight the start and end of each of them. Use tabs for indentation if possible, if not, use 2 spaces. An indent of 2 spaces is compatible with the emacs default and the indent code beautifier.
- There should be one action per line.
- Code chunks should be separated by blank lines and/or comments.
- Do not use spaces within object references. Do not space between unary operator and operand. Balance spacing around binary operators.
- Wrap lines according to parentheses/precedence rules. Indent wrapped lines by one level, start with an operator when possible.
- Stick to a line length of 80 characters throughout the whole file.
Pitfalls
- Watch out for the following: if ( value = 0 ) ... Where what you really meant was: if ( value
= 0 ) ... One method of avoiding this pitfall is to specify the const item first: if ( 0 =
value ) ... since this will give rise to a compile-time error if an assignment is specified by mistake.
- Use while (...) {} instead of while (...) ; for null loops. They happen remarkably often in C++, and the semantics of the semi-colon is a trap for beginners.
-- ThomasEberl - 18 Dec 2006