These are some rules and guidelines that we decided to adopt in order to write more readable and safe code: they were chosen after some trials and errors, so you will see that not all of the code in this repository follows them, yet it is very important that you keep them in mind when producing the software here.
You can find some good rules for writing safety-critical code here: http://pixelscommander.com/wp-content/uploads/2014/12/P10.pdf.
The following rules take inspiration from the JSF - C++ Rules, which you can read here: http://www.stroustrup.com/JSF-AV-rules.pdf
Style Rules
Use clang-format. Full Stop. There is an extension for it for nearly every IDE/Text Editor in the world: make it run automatically when saving a file and get the rules from the .clang-format
file in the top directory.
Critical Rules
These rules shall be followed every single time. An exception to these rules can be made only after extensive consultation with the majority of the software team. Every deviation from this rules shall be documented in place with the reasoning behind the decision.
-
Do not use dynamic memory allocation (
malloc
ornew
) after initialization The use of dynamic allocation can lead to heap fragmentation and out-of-memory errors after the startup of the rocket (eg. In flight) - Do not use direct or indirect recursion Recursive code is hard to read and debug, and can easy lead to stack overflow errors.
- Every class with a virtual function shall define a virtual destructor Avoid undefined behavior where a derived class is destroyed through a reference to the base class, possibly leaking resources.
-
Every header file shall be guarded using defines containing the full project path of the file, or by using #pragma once
Avoid conflicts when having files with the same name in multiple directories.
Example: File
src/shared/sensors/ExampleSensor.h
#pragma once
//code goes here
General rules
These are not critical rules, but you should follow them in most cases.
- Check the validity of the input arguments for every function Avoid errors caused by out-of-domain or otherwise wrong input arguments.
- Avoid throwing exceptions Exceptions can cause the program to crash if not handled correctly.
- Every nonlocal name, except main(), should be placed in some namespace
- Data objects must be declared at the smallest possible level of scope
- Prefer inline function over macros. Macros should be avoided or kept as simple as possible While sometimes useful, macros kill the readability of the code. Avoid them when you can, and if you use them, keep them simple and comment them appropriately.
-
Constant values should be not be defined using the #define directive
Prefer
static const
instead, to improve readability and avoid type ambiguities. - Variables should not be introduced until they can be initialized with meaningful values Avoid errors where someone tries to use an uninitialized variable.
- Avoid pointers and prefer references, when possible
- Prefer enum class over plain, c-style enum Enum classes restrict the scope of their members and are strongly-typed.
- The return value of non-void functions must be checked by each calling This is done to check if the function had an error.
- Use C++ style cast (static_cast, dynamic_cast…) instead of their C counterparts
- Initialize objects in their costructors: avoid init() functions when possible It's very easy to forget to call an object's init() method, leaving objects in a undefined state.
-
Use TRACE() instead of printf()
printf()
is a very heavy function (surprised?) and it's very unlikely you need it when the rocket is flying. TRACEs are like printfs but they are stripped out when compiling the flight binary.
Commenting rules
- Every class shall be documented using a doxygen comment describing the purpose of the class and for what it is useful.
- Every function shall be documented using a doxygen comment describing the purpose of the function, its parameters and the return value.
- Write a comment defining pre and post-conditions for most methods. This rule is aimed at improving the readability of the code.
- Write a comment describing every variable when its meaning is not trivial
For a guide on how to use doxygen to document the code, take a look here: https://www.stack.nl/~dimitri/doxygen/manual/docblocks.html
Best practices
These rules are mostly aimed at keeping the code readable and consistent.
- Functions generally should return an integer value which tells the caller if there were problems
- No function should be longer than about 80 lines Split functions in multiple ones to improve readability of the code
Naming conventions
-
Function will be named using camelCaseNotation, starting with a lower case letter
Example:
float readTemperatureSample()
Exception: Acronyms in function names will be UPPERCASE. Example:bool selfTestIMU()
-
Classes, structs and enums will be named using CamelCaseNotation
Example:
class TemperatureSensor {...}
Exception: As for functions, acronyms in class names will be UPPERCASE. Example:class FSM {...}
-
Constants and enum members will be named using ALL_CAPS_WITH_UNDERSCORES
Example 1:
float TEMPERATURE_SAMPLE;
Example 2:
enum class ExampleEnum {
EXAMPLE_MEMBER_1,
EXAMPLE_MEMBER_2
}
-
Local and member variables will be named all_lowercase_with_underscores
Example 1:
float pressure_sample;
- Names of members of c-style enums shall begin with a identifier of the enum, to avoid ambiguities, since they are not scoped Example 2:
enum EventIDs {
EV_ID_1,
EV_ID_2
}