These are some rules and guidelines that we decided to adopt in order to write more readable and safe code.
Following these rules is not absolutely necessary, and much of the existing code was written before we even decided them, but their use is definitely suggested.
Coding Rules
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
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
Avoid conflicts when having files with the same name in multiple directories.
Example: Filesrc/shared/sensors/ExampleSensor.h
#ifndef SRC_SHARED_SENSORS_EXAMPLESENSOR_H
#define SRC_SHARED_SENSORS_EXAMPLESENSOR_H
//Code goes here
#endif /* SRC_SHARED_SENSORS_EXAMPLESENSOR_H */
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
Preferstatic 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.
Commenting rules
- Every function shall be preceded by a doxygen-style 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
- Write a comment describing the purpose of each class, struct and enum
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()
-
Classes, structs and enums will be named using CamelCaseNotation
Example:class TemperatureSensor{};
-
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
}
What's Next
You will find more specific best practices in Writing a Driver and Writing a Sensor, but before that maybe you should take a look at Boardcore's Codestyle.