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:

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

Formatting and naming rules

  1. 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.
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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.

  8. Static member functions names should be named in the same manner as public member functions.
  9. Namespaces names should be all lowercase, with word boundaries delimited by underscore.
  10. 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.
  11. 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
  12. 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.
  13. Class documentation should appear in the header. This must include the following:
  14. 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.
  15. [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
  16. [NEW] Do not use any C-style casts. Use dynamic_cast, reinterpret_cast or const_cast, whichever is required for the situation.

Rules for handling event data

  1. Follow the standard interface to the EDM.
  2. 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( ).
  3. 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.
  4. The database API must be used to obtain non-event data.
  5. 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:

Rules on the use of ROOT

CDF Offline software dependent on ROOT libraries or headers in any way, directly or indirectly, should either:

  1. 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.
  2. 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).
  3. 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.
  4. Classes that interface with ROOT will be expected to follow the ROOT coding guidelines in respect to member function and data names.

Miscellaneous rules

  1. Each package must have an appropriate validation or test target.
  2. Use of global variables is forbidden. Use of Singletons to provided global access is allowed only when approved by the Infrastructure and Integration group.
  3. 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.
  4. Geometry information must be obtained through the interface of the geometry classes.
  5. Magnetic field information must be obtained through the interface of the magnetic field classes.
  6. 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

  1. 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
  2. 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
  3. 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
  4. 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.
  5. 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.
  6. [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

  1. Always consider portability when writing code. This will become even more important when compilers other than KAI C++ are given approval.
  2. 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