Coding style
Coding rules might sound overdone, but they are essential to keep a code clean and tidy and even more important consistent. At the end this will help everybody to read code of somebody else, because it is structured at least very similar everywhere and the optical layout reflects somewhat its structure. These rules include a lot of experience and are adapted to modern screen (which have more than 25 lines). The order is random and has not yet any meaning. They are the basis for the development of FACT++ and MARS. Future projects might use different rules, but the more consistent the existing code basis is, the easier it is for everybody to understand all existing code.
- In general lines should not be longer than 80 characters.
- Use const qualifiers where ever possible. This includes functions, function arguments and every variable declaration. This has several advantages: Improved checking by the compiler, better optimization by the compiler, and faster understanding (You are only interested in a loop at the end of a function and an important variable is declared at the beginning, you would have to check all code in between carefully to know its value, and even worse, ever function call which might take the non-const variable as a reference and alter it).
- Declare variables as late as possible. To understand code, it makes sense to have all you need as close as possible which avoids scrolling forth and back.
- The indentation is 4. No tabs allowed.
- Curly braces are *always* on a single empty line (this gives a clear structure to the code), although in *switch* statements. No comments on the line with the opening brace. Put them above (e.g. the if-clause) or below in the next line.
- *goto* is a no-go, it breaks the structure of the code (check google for details)
- Global variables are a no-go as well. Look at root, then you know what I am talking about.
- Try to avoid filling the global namespace with enums. Make them class members or create a namespace for them.
- In switch statements the *case* label is on the same level as the *switch*. The corresponding code or brackets are indented.
- There is only one command per line. In if conditions the command to be executed is always on a new line, the *else* is always on it's own empty line.
- Nested if's like "if () else if () else if () else ..." are written like a staircase following the previous rules. Every other formatting hides the structure!
- Variables definitions in class headers are at the beginning of the class definition. Usually (not always reasonable) the order of modifiers is: private, protected, public. After the variables, put the functions, in the same order.
- Data members always start with an f (for field), static constant members with a g (for global) and non-const static members with fg (for global field). We use capitalization for variable names, e.g. fThisIsMyDataMember. class names and function names start with a capital. Strongly Mars related classes start with a capital M. Classes which can compile without Mars, might start with a different capital. Some exceptions are very very low level classes (e.g. zfits) which are written closer to the system coding style.
- File names should generally have the same name as the class name.
- Each header has to be encapsulated in an #ifndef MARS_ClassName, #define MARS_ClassName, [...code...], #endif block to avoid double inclusion.
- *using namespace* is never be used globally in a header.
- Try to use empty lines to separate blocks (e.g. everything which belongs to a loop plus the loop from the rest)
- Use '\n' to finish a line not *endl* except you want to flush a line to the console (e.g. to make sure it is available if after that a crash happens). For example: You output many lines successively, use '\n'. For the last one before your code returns use *endl*
- Do not use curly braces in if-statements to bracket a single line.
- Use a white space after the semicolon in a for-statement, but not before. Keep the three blocks without white-spaces. This simplifies to see what belongs to which part.
- In argument lists, use a white space after the comma, not before.
- If you use the ?:-operator, put white spaces around the ? and the colon. Try to keep the rest without white spaces.
- Avoid to assign variables just to give something a different name, use comments. Generally, do not invent variables for something which is only used once, except it helps for example to keep a function call much shorter.
- Use white spaces around logical operators (&&, ..) in conditionals (==, <= are conditional operators, not logical operators). This mimics the priority of the operators and makes the condition easier to understand. Try to keep the calculations compact. Avoid unnecessary parenthesis. If you are not sure, check operator priorities. This is a language issue nothing random from the compiler!
- Avoid using line long variable names. They should not be too short either. *timv* doesn't tel us much, but *myVar* tells us as much as *thisIsMyVariable* but makes code more compact thus better readble. Don't use variable names to explain their contents, use comments!
- Whenever possible propagate const-references instead of copying the argument. This is an optimization issue.
- Use gRandom and do not define your own TRandom object. The global scope will take care of that. A major advantage of simulations is to be able to set a global(!) seed.
- For proper documentation, please have a look at other classes. Note that FACT++ and Mars style is slightly different. Mars is using THtml with some Mars specific setup, FACT++ is using doxygen.
- Never define constants as preprocessor directives. Generally avoid preprocessor directives. If you really need them use upper case only.
- omit else in constructions like *if (...) return x; [else] ....*
- Source files have the extension cc, header files the extension h, C source files just c.
- Operators +=, -=, etc. are preferable over constructions like x=x+, x=x-, ...
- If you omit intentionally a break in a switch statement (*fallthru*) add a comment (only necessary *after* code between two case-statements, not necessary if there is nothing at all between two case statements)
- Sometimes including system headers is not identical for all operating systems, comments might help
- Try to avoid using dynamic allocation whenever possible: "int i=5; /* do something with i*/; /* i runs out of scope */" is preferred over "int *i = new int; *i=5; /* do something with i */; delete i;" Although this example sounds ridiculous, the same is true for every class instance you allocate.
- Initialization of all data members using their constructor (see example) is preferred over using assignments (optimization reasons)
- Keep your code linear! Avoid using a lot of if-else and blocks in loops. Use *continue* and *break* instead (easier to read, human brains have difficulties to handle to many levels, better fits on the screen, easier to optimize)
- avoid using c-style comments (/* ... */) if you comment a whole line
- Use forward declaration whenever possible instead of includes (compile time)
- Do never use global variables, except you have discussed that with everybody else before.
- Try to avoid static variables (only use them if it is really unavoidable -- think twice)
- directory names should be lower case only (to easy distinguishable from source code files)
- typedefs should end with a _t like UInt_t
- Try to avoid code repetitions. Invent a new function instead.
- Move code to their own function, If otherwise the structure gets too complicated. A typical example is
"MyClass *ptr = new MyClass; CallFunction(ptr); delete ptr;"
is much easier than"MyClass *ptr = new MyClass; if (...) { delete ptr; return; } if (...) { delete ptr; return; } delete ptr;"
In a similar case"int i = Find(a);"
with something like"int Func(int a) { for (int i=0; i<n; i++) { if (x[i]==a) return i; if (x[i]==a+1) return a+1; ... } return -1; }"
then if you try to construct the same with breaks and variables declared outside of the for-scope. - If you have many almost identical lines one after each other, try to align them to make the common structure immediately visible. Don't overdo that! A lot of unexpected spaces if they don't make things immediately apparent confuse and do not help at all.
- Code is not a piece of art. Try to avoid to format things because they seem to look nice or fancy. Keep your formatting functional and make sure it represents the structure of your code.
- Although pointers are a concept you need to learn and understand, loops over iterators (or pointers) are usually easier(!) to read and the possibility to misunderstand their meaning is much less, because the access of the data is not indirect (and every indirection can be doing something different than what you expect) and limits are better defined.
begin()
andend()
are very clear, while0
and5
can be everything, even wrong. In many cases also optimization and hence performance will profit. - Do not complain about strange construction, sometimes they are a matter of necessity to get performance!
- The is a lot of fancy coding stuff which looks complicated at first, but there are very good reasons that they were invented (like iterators). These are mostly performance issues and readability. Note: Readability does not mean that it is easy to read, it means that when you read it once, after it there is no doubt left what it is doing!
- Do not re-invent the wheel. Check the STL, it is worth. There are a lot of helpful function (find minima, find maxima, sort, ...) They are often more efficient that what you could program yourself, and again: Somebody reading it does not need to understand your code (which might even be wrong) *and* has the advantage of an existing documentation.
- When you use a container, carefully think what makes its use efficient. Check the documentation (Just search e.g. for std::list, I am sure the first answer will contain what you are looking for). A good C++ documentation contains all info you need (does searching in the container scale with N or Nlog(N)? Is access constant or does it scale with the number
of elements? Do you need random access or is linear access enough?)
- Note that when Mars was coded, STL weren't yet available in all Compilers, especially templates weren't. So try to be conservative using that to keep consistency (most STL stuff can be replaced by root stuff, see TMath or TCollection)
Exception from these rules are of course acceptable but should stay an exception. Exceptions are only useful if using a different style, reveals the structure of the code (what the code is doing) much better.
Example
#ifndef MARS_Complex #define MARS_Complex #include <math.h> // needed for sqrt on Ubuntu 10.14 class Complex { private: double fRe; double fIm; public: Complex(double re, double im) : fRe(re), fIm(im) { } double Modulus() const { return sqrt(fRE * fRe + fIm * fIm); } bool Foo(int a, int b) { for (int i=0; i<b-a; i++) { if (i<b) bar(i); else { bar(i); bar(b); } } const int c = a + b; switch (c) { case 0: // Code goes here return false; case 1: case 2: // Code goes here return false; default: // Code goes here return true; } return true; } }; #endif
ROOT
Root is sometimes a bit tricky... here are a few coding rules or hints.
- Try to avoid using global pointers (gRandom is an exception). Properly store the pointers to the objects you created instead (e.g.
file=new TFile(...); file->...
instead ofnew TFile; gFile->...
- *same* is TGraphs is done by omitting the A (A means *with axis*)
- Try to avoid pointers, use objects, except if you add them to a root list.
- Histograms are usually automatically written to open files (it is switched of in the MARS code, but be carefull). Avoid that by removing the histogram from everything after creation:
hist.SetDirectory(0)
- Make sure that all objects are properly deleted. E.g. when drawing something with DrawClone like
g=graph.DrawClone("AP")
, make sure you set the kCanDelete bit withg->SetBit(kCanDelete)
. When you draw something with DrawCopy this is done automatically. - Sometimes graphs are not where they ought to be (usually when you use DrawClone). In this case, add a
gROOT->SetSelectedPad(0)
before you cd() into the pad you want the graph to appear. Alternatively you can create the Clone() first and add the clone to the pad withgPad->GetListOfPrimitives(clone, draw_options)
FACT camera display
// Create a FACT camera with MGeomCamFACT geom; // Create a display with MHCamera h(geom); // Set value of pixel 0 with h.SetBinContent(1); // (note the +1!) // Enable the display of the pixel with h.SetUsed(0) // Or enable the the display of all pixels with h.SetAllUsed() // Draw display h.DrawCopy();
Reading a display
TFile file("filename.root"); if (file.IsZombie()) return; MStatusArray arr; if (arr.Read()<=0) return; TWhatever *ptr = arr.FindObjectInCanvas("CanvasName", "TWhatever", "ObjectName"); if (!ptr) return;