CDF Coding Guidelines
This page last updated: 22 May 1999 11:22 AM
Items added since the previous version are indicated by the marking [NEW], and are listed below:
Items modified since the previous version are indicated by the marking [MODIFIED], and are listed below.
This document describes the current CDF C++ coding guidelines. The guidelines are
presented with three different levels of formality:
- Rules: these are guidelines that will be enforced, for example
during code reviews.
- Recommendations: these are guidelines intended to make
code easier to maintain and understand, or to make it more efficient, but which may be
violated when there is good reason.
- Suggestions: these guidelines will not be enforced, but you
should consider following them for your own benefits.
A separate document detailing CDF Coding Policy will be forthcoming. We will provide a
link to that policy statement as soon as it is available.
Rules
- Classes may not have public data members. Public data violates the encapsulation of a
class, and makes clients of the class directly dependent on the implementation details of
the class. The result is a dramatic increase in the cost of maintenance.
- Class members (instance variables) must have names beginning with an underscore,
followed by a lower-case letter, and which use upper-case letters to show the breaks
between words. Uniform application of this rule will make it easier to read each other's
code.
|
examples |
|
| int |
_someMember; |
// ok |
| int |
another_member; |
//not ok |
- Variables names need not be "decorated" to show their type. Such decoration
makes it hard for the non-expert to read the code.
|
examples |
|
| char** |
thing; |
// ok |
| char** |
lpszThing; |
// obscure |
- Class names should be mixed upper- and lower-case, with an initial upper-case letter and
with word boundaries indicated by upper-case letters. Uniform application of this rule
will make it easier to determine what is a class, and what is something else.
|
examples |
|
| class ExampleClass: |
public BaseClass {}; |
// ok |
| class otherclass: |
public SHOUTING_CLASS {}; |
// not ok |
- Names of static member data should be all upper-case, with word boundaries indicated by
underscores. This makes them easily distinguishable from (instance) member data, and from
local variables.
|
examples |
|
| double |
Geometry::PI; |
// ok |
| double |
Geometry::pi; |
// not ok |
- Variables local to a function should be given names that do not follow the pattern of
class names, (instance) member data, or static member data.
|
examples |
|
| float |
localVariable; |
// ok |
| float |
SomeOtherVariable; |
// not ok: looks like a class name |
| float |
_badlyChosen; |
// not ok: looks like a member variable |
- Public member functions should be given names that start with a lower-case letter, and
are mixed upper- and lower-case, with upper-case letters at word boundaries. Uniform
application of this rule will help make it easier for non-experts to remember function
names. Private member functions names should begin with an underscore, so that they are
easily distinguishable from public member functions; otherwise, they are to be named like
public member functions.
|
examples |
|
| void |
SomeClass::doSomethingInteresting() const; |
// ok |
| void |
SomeClass::unlovely_function_name() const; |
// not ok |
[MODIFIED] Please note that there is an exception to this
rule, in the case of classes which are similar to STL
containers. STL-like functions should follow the STL naming conventions, rather than
the format in this item.
- Static member functions names should be named in the same manner as public member
functions.
- Namespaces names should be all lowercase, with word boundaries delimited by underscore.
- No class should be defined in the global namespace. Closely associated classes should be
grouped into one namespace. This will help prevent the extraneous decoration of class
names with prefixes which show these associations. An exception is provided for the case
of classes which provide access to Fortran common blocks.
- No header may contain using directives or declarations.
This is because such a directives and declarations would affect all files which include
such a header, either directly or indirectly, effectively doing away with the benefits of
namespaces. Such directives and declarations are allowed in source code files, since the
effect in a source file is local.
|
examples |
|
| using namespace std; |
// not allowed in headers; |
allowed in source files |
| using std::vector; |
// not allowed in headers; |
allowed in source files |
| std::vector<float> x; |
// allowed in headers; |
allowed in source files |
- Each header and source file pair (component) should define one
class, or a few closely related classes. The filenames should be the same as the class
name, or the name of the most important of the classes.
- Class documentation should appear in the header. This must include the following:
- A short description of the purpose of the class.
- The name of the person currently responsible for supporting the code.
- Be aware of physical dependencies between components and
between packages. Unnecessary dependencies increase compilation time, increase link time
and executable image size, and make testing more difficult. To limit dependencies, use
forward declarations rather than including headers whenever possible.
- [NEW] Do not put any conditional compilation clause that defines a
new member datum, or otherwise changes the size of an object based upon the value of a locally
defined variable, in any header.
example |
class BadNews
{
...
private:
#ifdef BAD_LOCAL_PREPROCESSER_VARIABLE // Not set by SRT, not OK
int _nowSizeOfBadNewsIsDifferent;
#endif |
- [NEW] Do not use any
C-style casts. Use dynamic_cast, reinterpret_cast or const_cast,
whichever is required for the situation.
- Follow the standard interface to the EDM.
- Do not pass data in global objects. This makes it difficult to achieve reproducibility
and reliable comparisons.
- Beware of stale handles, which are possible in the old EDM.
- Prepare to move to the new EDM.
- Global data is not allowed. The use of the Singleton pattern as a
replacement for global data is allowed only after prior approval by the Infrastructure and
Integration group. Singletons classes should provide a member function instance( ).
- Modules must use AbsParms to get values for those data
which may be set by the user. These parameters must have the description()
method implemented.
- The database API must be used to obtain non-event data.
- Use of any 3rd party package in CDF offline software must be approved by at least the
Infrastructure and Integration group. Issues to be considered before accepting a 3rd party
package include:
- availability on all our Run 2 platforms
- long term support (who will do it and how much manpower will it take)
- what are the cost/benefits of using it
CDF Offline software dependent on ROOT libraries or headers in any way, directly
or indirectly, should either:
- Live in one of the ROOT-dependent packages: RootMods,
RootUtils, and RootObjs, or other packages with
names beginning with Root. These packages are built only if and only if
the ROOT product is setup. This is enforced by these packages' makefiles. No macro
definition tests are required in the source code. USE_ROOT
does not affect these packages.
- Be wrapped by #ifdef USE_ROOT preprocessor directives
(in C/C++ source code) or USE_ROOT environment variable
tests (in makefiles) if the software is not reposited into one of the ROOT-dependent
packages listed in [1]. This software
(lib, bin, and tbin targets) must compile and link successfully whether ROOT is setup or
not, though its run-time functionality may vary within reasonable limits defined by the
I&I leaders. The run-time functionality that currently exists in validation programs,
for instance, must continue to exist without USE_ROOT
being defined or the ROOT product being setup. The ROOT-dependent pieces are only built
and linked if the user sets an environment variable USE_ROOT
(to any value) and sets up the ROOT product. SRT or CDF makefiles test on this and set the
macro definition USE_ROOT if the environment variable is
set (not done generally yet).
- Trybos and HEPTUPLE are permitted exceptions to these two rules since it uses ROOT to
implement core functionality, event I/O. This use of ROOT has been reviewed and accepted.
Trybos uses the ROOTSYS environment variable and macro to
build ROOT-related pieces. Trybos must build and link whether ROOT is setup or not. The
ROOT-dependent pieces of Trybos, however, are built if and only if the ROOT product is
setup. USE_ROOT does not affect this package.
- Classes that interface with ROOT will be expected to follow the ROOT coding guidelines
in respect to member function and data names.
Miscellaneous rules
- Each package must have an appropriate validation or test target.
- Use of global variables is forbidden. Use of Singletons to provided global access is
allowed only when approved by the Infrastructure and Integration group.
- Each major class there should have the stream insertion method (for class X, this is std::ostream& operator<<(std::ostream& os, const X&
x) that dumps it's state, for debugging purposes.
- Geometry information must be obtained through the interface of the geometry classes.
- Magnetic field information must be obtained through the interface of the magnetic field
classes.
- If it is necessary to allow C++ access to Fortran common blocks, the memory for the
common blocks should be allocated in Fortran and C++ should only find the address of this
memory and map the structure onto it.
Recommendations
- Pass objects by reference, not by value. This is done for efficiency; passing an object
by value requires making a copy, while passing by reference gives access to the same
object. This is not needed for basic types like int, float, or double. It is also not
needed for the standard library string, which is usually implemented to make copying
inexpensive.
|
examples |
|
| void |
func(std::vector<float> x); |
// bad; pass by value |
| void |
func(std::vector<float>& x); |
// good; pass by reference |
- Pass objects that should not be modified by const
reference. This is for both efficiency (as above) and for safety. When using the const keyword, you gain the help of the compiler in making sure
things that should not be changed are not changed.
|
example |
|
| void |
func(std::vector<float>& x); |
// x may be changed |
| void |
func(const std::vector<float>& x); |
// x may not be changed |
- Declare non-mutating member functions const. This is for safety and for interoperability
with other objects. The compiler will allow only const
member functions to be called on const objects or const references. Thus, if you do not declare a member function const, the compiler may prevent someone else from using it, when
you did not intend this to happen.
|
examples |
|
| void |
SomeClass::func1() const; |
// may be called on const objects |
| void |
SomeClass::func2(); |
// may not be called on const objects |
- Container-like classes should provide access to the objects which they contain, but they
should not provide all the functions for those objects. Violation of this guideline leads
to "fat" interfaces, which are difficult to maintain and to understand. They are
difficult to understand because they are large. They are difficult to maintain because
whenever the contained object is changed, each container-like class which holds such
objects must change at the same time. The container-like class should provide the methods
necessary for efficient iteration over the contents of the container, so that each user
does not need to discern the correct rules for iteration.
- Classes generally do not need a "set" method for each member datum.
Initialization should be done in the constructor methods, or in an
"initialization" method which is called by constructors. "Set" methods
should be provided only for those cases in which the user of a class really needs to
manipulate the member data directly. One good example of such a need is when several data
members must be modified simultaneously, to assure that an object remains in a consistent
state. "Get" methods should be provided to the extent is necessary to perform
the functions of the class.
- [NEW] Developers should commit code to the repository as often as
reasonably possible. Adherence to this guideline will minimize the disruption that occurs
when interfaces change, or when package dependencies change, because librarians can only
fix code that is in the repository. It is not possible for them to fix code that is in
private directories.
Suggestions
- Always consider portability when writing code. This will become even more important when
compilers other than KAI C++ are given approval.
- In the absence of C++ exceptions, functions which can fail should return a status code,
and clients of such functions should test these codes. A nested series of calls should
propagate a "failure" code up to the level that can deal intelligently with the
failure.
This page kept by: Marc Paterno