Coding Conventions
The rules and suggestions in this document are to be applied in spirit of the basic truth of software development: "It’s harder to read the code, than to write it."
Naming convention is significant part of the software language's culture. Naming convention is a set of rules for choosing the character sequence to be used for identifiers, which denote variables, types, functions, and other entities in source code and documentation. A coding convention is a personal choice and many of us develop our very own style as we go along. You must adhere to general rules mentioned below, apart from that we do not strictly enforce any particular style. Whether you are comfortable using elements of Taligent rules, Hungarian notation (system or apps) or anything else you prefer, the main focus is to make our code utmost readable and of best design.
The general convention rules below are partially based on different sources like "Google C++ Style Guide", MSDN Documentation, "Collaborative Collection of C++ Best Practices" and many others.
Most of the following rules can be either enforced (using clang-format), or validated during static analysis.
Basic Rulеs for Identifier Name
- Length of the identifier name has no restrictions but you should avoid extremes. In general it should be more than 3 characters.
- Optimize the identifier names for the reader understanding, and not for the writer convenience.
- Do not use abbreviations in identifier names.
- In most cases, declarations that are not used outside a single source file should be in that source file.
- The variables, constants, and class members should be following the camelBack case (CamelCase with first letter being lowercase).
- Functions, classes, structures, and their methods should be following the CamelCase rule. Only exception are function names of classed derived from external packages (e.g. FairRoot) where one has to preserve the case of functions from parent class.
- Do not use Hungarian notation prefixes or underscores in functions, classes, structures, and their methods.
- Avoid using prefixes in names of variables, constants, and class members. Make variable names descriptive. Exception are prefixes connected to ROOT classes. One may use following prefixes:
| prefix | meaning |
|---|---|
| f | class data member |
| fg | static data member |
| k | constant |
File/Class Naming Rules & Suggestions
- C++ source files must have a .cxx extension and headers .h
- Files may not contain word “Mpd”.
- Files may not contain name of library which they implement (it is obvious from the folder name).
Only exception is the main file of the given library. For example
MpdTofPoint.cxx- always wrong (Mpd in file name),TofPoint.cxxinside Tof library - wrong (library name Tof is obvious from directory structure),Tof.cxxinside Tof library - OK (implementation of the library main class),TofMatching.cxxinside MpdActs library - OK. - File names follow the CamelCase.
- Abstract base classes should begin with word “Abstract” (AbstractTpcClusterHitFinder)
- Implementations should end with implementation identifier separated by an underscore, (TpcClusterHitFinder_Mlem: public AbstractTpcClusterHitFinder)
- If module, instead of having Abstract Base Class, has one default implementation, from which all other implementations inherit, then this class should begin with word Base (BaseTpcSectorGeo)
- We are gradually moving towards each module located in its own subdirectory (interface file + its various implementations)
External libraries
- When using external libraries focus on code readability. If possible, stick to the MpdRoot rules.
- Don't use the keyword “using” to avoid library conflicts. You can use the keyword reasonably if you are absolutely sure it won’t generate any library conflicts in the future. For example, instead of "using namespace std", use "using std::vector". As a rule of thumb don't use "using std::XXXXX" if XXXXX repeats in your source file less than 10 times. No "using std[::XXXXX]" is allowed inside header files.
Design
- Do not use Singleton design, avoid using static variables. They both represent very toxic global state anti-pattern.
- Each library must be placed inside own namespace. This does not concern (yet) libraries in the directories
mpdroot/Physicsandmpdroot/Tools/Database. Common namespace for all libraries isMpd. Second-level namespace is the library name. If possible, try to avoid the 3rd (and more) level namespaces. Each namespace name starts with capital letter and if necessary, follows CamelCase. - Do not use magic numbers. Each constant should be declared as a constant either in the namespace of given class,
if its scope is of that class or in
Mpd::Coreif it is expected to be used by whole MpdRoot. Also, please note, that branch names of ROOT files are constants and they will be placed in the respective header in theMpd::Corenamespace. - Don't use enums, use enum class instead.
Logging
While it may be convenient during the development process, it is strictly forbidden to use std::cout and friends
in the final releases of MpdRoot. In the future, we may automatically remove (disable) all calls to std::cout during
the release build of MpdRoot. In order to produce text output to the screen, one has to use FairLogger and its
options. This is done in order to have the possibility to control the amount of produced output without having to
rebuild the MpdRoot itself or regulary comment out parts of the code. To use
FairLogger add to your source file
#include <fairlogger/Logger.h> and replace the calls to std::cout (std::cerr) by calls to LOG(severity).
For example, instead of
std::cout << "This are debug numbers 1 2 3.\n";
std::cout << "This is some important message.\n";
std::cout << "Here I just warn user of unexpected.\n";
std::cerr << "Something bad just happened.\n";
std::cerr << "Catastrophic failure.\n";
you would write
LOG(info) << "This is some important message.";
LOG(debug) << "This are just debug numbers 1 2 3.";
LOG(warn) << "Here I just warn user of unexpected.";
LOG(error) << "Something bad just happened.";
LOG(fatal) << "Catastrophic failure.";
- Don’t add to the message parts like function name, [WARNING], [ERROR], [INFO], -I-, -D-. These can be added automatically depending on the verbosity level.
- Note that macro
LOGadds newline automatically. If one need to write multiple values to single line, the text of the message must be built (e.g. usingstd::string) prior to passing it to the logger. - Besides macro
LOG, several macros are available for formatted or conditional output. SeeLOGF, LOGP, LOGN, LOG_IFin the FairLogger manual.
Includes
- Include only files necessary for the code.
- Use includes as close to the source file as possible to avoid long compilation time. If possible, use forward declarations in headers.
- Each include file starts with include railguars (the first line) and terminates with the closingrailguars.
- Includes comming from the MpdRoot are closed in the double quotes ("").
- All other includes, e.g. from ROOT, JSON, system includes are closed in single angle quotes (<>), even if examples of using given headers on internet use double quotes.
- All MpdRoot's includes must contain "full path" to the header (starting with Mpd). No local includes are allowed, even if two header files are in the same directory.
- Order of includes -- headers of the same level are sorted alphabetically:
- Header of the class that given source file implements.
- All other headers from MpdRoot.
- All other headers.
See MpdRoot's doxygen documentation for detailed example of how to include headers and how to make sure, their order is kept even after source core reformatting. Static analysis tool Include-What-You-Use from the static analysis part can help you to determine which headers you need to include and which forward declarations are possible.
Documentation
For now. we don't have proper way how to handle User's documentation (how to use given library). Therefore it is placed inside the same directory as library, usually in the form of Readme.md file. In the future, it is expected to have this information as a part of User information in nica pages.
For automatically generated documentation we use Doxygen. See our page for details how to document source code and use the doxygen creator.