Coding Style
Since the seminal work by Kernighan et al. in 19741, there is a clear concern on the style in writing software and its impact on the final quality of the product. Following programming guidelines and code conventions not only helps to avoid introducing errors but cuts maintenance costs and favors effective code reuse.
Definition:
The following rules capture the most important aspects of coding style:
- All should be as understandable as possible.
- All should be as readable as possible, except when it would conflict with the previous rule.
- All should be as simple as possible, except when it would conflict with the previous rules.
The best way to look at these rules is to make everything as simple as possible unless understandability or readability suffers. As a programmer, you must always try to respect the above rules, even if you do not follow our suggested style of coding.
Any violation of the guide is allowed if it enhances readability. The main goal of the recommendation is to improve readability and thereby the understanding and the maintainability and general quality of the code. It is impossible to cover all the specific cases in a general guide and the programmer should be flexible.
The rules can be violated if there are strong personal objections against them. The attempt is to make a guideline, not to force a particular coding style onto individuals. Experienced programmers normally want to adopt a style like this anyway, but having one, and at least requiring everyone to get familiar with it, usually makes people start thinking about programming styling and evaluate their own habits in this area. On the other hand, new and inexperienced programmers normally use a style guide as a convenience of getting into the programming jargon more easily.
Not invented here! This coding style guide was written based on this Coding Style Generator. Some ideas were borrowed from the Google C++ Style Guide and the High Integrity C++ Coding Standard Version 4.0 Guidelines for the use of the C++ language in critical systems.
Interested in doing a pull-request? Go straight to the point! The application of some of the rules described in this page can be automated with tools such as clang-format and clang-tidy, which can help you to meet the project conventions (automatically changing the code for you!) and integrate well with many other tools such as code editors and building systems. If you want to do a pull-request, please apply these tools to your code before doing it. Scroll down to read about how to install and use them. Do not forget to sign your commits as an indication of Developer’s Certificate of Origin fulfillment.
Naming conventions
Naming rules for variables
Variables are named using lower-case letters and words are separated using under-score. Abbreviations, when used in variable names, are also written in lower-case letters. Examples:
fft_size
my_variable_name
Naming rules for files
Files are named using lower-case letters and words are separated using
under-score. Abbreviations, when used in file names, are also written in
lower-case letters. Source files are named using .cc
suffix, whereas header
files end with .h
extension. Examples:
my_file.h
my_file.cc
Naming rules for functions
Function names are named using lower-case letters and words are separated using under-score. Abbreviations, when used in function names, are also written in lower-case letters. This rule applies both to stand-alone functions as well as to member functions of classes. Example:
do_something( with, these, parameters );
When function calls get too long, you will have to split them up into several lines. Align the following lines with the previous ones so the structure becomes obvious, and go to the next line after the comma.
Channel(ConfigurationInterface *configuration,
unsigned int channel,
std::shared_ptr<GNSSBlockInterface> pass_through,
std::shared_ptr<AcquisitionInterface> acq,
std::shared_ptr<TrackingInterface> trk,
std::shared_ptr<TelemetryDecoderInterface> nav,
std::string role,
std::string implementation,
boost::shared_ptr<gr::msg_queue> queue);
Naming rules for classes and structures
Each new word in a class or structure name should always start with a capital letter and the words should be separated with an under-score. Abbreviations are written in capital letters. Examples:
My_Class_Name
My_Struct_Name
BPSK
Use sensible, descriptive names
Do not use short cryptic names or names based on internal jokes. It should be easy to type a name without looking up how it is spelled. Exception: Loop variables and variables with a small scope (less than 20 lines) may have short names to save space if the purpose of that variable is obvious.
Only use English names
It is confusing when mixing languages for names. English is the preferred language because of its spread in research and software development and because most libraries already used are in English.
Variables with a large scope should have long names, variables with a small scope can have short names
Scratch variables used for temporary storage or indices are best kept short. A
programmer reading such variables should be able to assume that its value is not
used outside a few lines of code. Common scratch variables for integers are i
,
j
, k
, m
, n
, and for characters c
and d
.
Use namespaces for identifiers declared in different modules
This avoids name clashes.
Use name prefixes for identifiers declared in different modules
This avoids name clashes.
Do not use magic numbers
Unnamed or ill–documented numerical constant values make the code difficult to follow.
Indentation and Spacing
Braces should follow the GNU style
The GNU Bracing Style means that the curly brace pairs are indented from the surrounding statement. Statements and declarations between the braces are indented relative to the braces. Braces should be indented 4 columns to the right of the starting position of the enclosing statement or declaration. Example:
void f(int a)
{
int i;
if (a > 0)
{
i = a;
}
else
{
i = a;
}
}
class A
{
};
Function parameters should be lined up with one parameter per line
This allows enough space for Short comments after each parameter. Loop and conditional statements should always have brace enclosed sub-statements. The code looks more consistent if all conditional and loop statements have braces.
Even if there is only a single statement after the condition or loop statement today, there might be a need for more code in the future.
Braces without any contents may be placed on the same line
The only time when two braces can appear on the same line is when they do not contain any code. Example:
while (...) {}
Each statement should be placed on a line on its own
There is no need to make code compact. Putting several statements on the same line only makes the code cryptic to read.
Declare each variable in a separate declaration
This makes it easier to see all variables. It also avoids the problem of knowing which variables are pointers. (Bad) example:
int* p, i;
It is easy to forget that the star belongs to the declared name, not the type, and look at it and assume that the type is “pointer to int” and both p and i are declared to this type.
For declaring pointers and reference the “*
” and “&
” should be surrounded by spaces on both sides
All binary arithmetic, bitwise and assignment operators and the ternary conditional operator (?:
) should be surrounded by spaces
The comma operator should be followed by a space but not preceded by one; all other operators should not be used with spaces.
Lines should not exceed 78 characters
Even if your editor handles long lines, other people may have set up their editors differently. Long lines in the code may also cause problems for other programs and printers.
Do not use tabs
Tabs make the source code difficult to read because different programs treat the tabs differently. The same code can look very different in different viewers. Avoid using tabs in your source code to avoid this problem. Use spaces instead.
Comments
Comments should be written in English
Comments should use the C++-style
Be consistent and use the // ...
style comments.
Use JavaDoc style comments
The comment styles ///
and /** ... */
are used by JavaDoc, Doxygen and some
other code documenting tools.
For a complete description of how to document the code, see the Doxygen Manual
All classes in GNSS-SDR should be properly documented with Doxygen comments in
include (.h
) files. Source (.cc
) files should be documented according to a
normal standard for well documented C++ code.
An example of how the interface of a class should be documented in GNSS-SDR is shown here:
/*!
* \brief Brief description of My_Class here
*
* Detailed description of My_Class here. With example code, if needed.
*/
class My_Class
{
public:
//! Default constructor
My_Class(void)
{
setup_done = false;
}
/*!
* \brief Constructor that initializes the class with parameters
*
* Detailed description of the constructor here if needed
*
* \param[in] param1 Description of \a param1 here
* \param[in] param2 Description of \a param2 here
*/
My_Class(TYPE1 param1, TYPE2 param2)
{
setup(param1, param2);
}
/*!
* \brief Setup function for My_Class
*
* Detailed description of the setup function here if needed
*
* \param[in] param1 Description of \a param1 here
* \param[in] param2 Description of \a param2 here
*/
void setup(TYPE1 param1, TYPE2 param2);
/*!
* \brief Brief description of member_function1
*
* Detailed description of member_function1 here if needed
*
* \param[in] param1 Description of \a param1 here
* \param[in] param2 Description of \a param2 here
* \param[in,out] param3 Description of \a param3 here
* \return Description of the return value here
*/
TYPE4 member_function1(TYPE1 param1, TYPE2 param2, TYPE3 ¶m3);
private:
bool setup_done; // Checks if the class is properly initialized
TYPE1 private_variable1; // Short description of private_variable1 here
TYPE2 private_variable2; // Short description of private_variable2 here
};
Include formulae
Follow this link to see how can include formulae.
Multiple line comments should be split in one comment per line, each having the /*
and */
markers on the same line
Long comments which span several lines are difficult to follow. Having each line
in the comment begin with /*
makes the comment much clearer and easier to
identify. This also avoids problems where comments contain code and possibly
nested comments.
All comments should be placed above the line the comment describes, indented identically
Being consistent on the placement of comments removes any question on what the comment refers to.
Use #ifdef
instead of /* ... */
to comment out blocks of code. The code that
is commented out may already contain comments which then terminate the comment
block and causes lots of compile errors or other harder to find errors.
Files
There should only be one externally visible class defined in each header file
Having as few declarations as possible in a header file reduces header dependencies.
The header file should have the same name as the class plus extension .h
.
External non-member functions that belong to the class interface may also be declared in the same header file.
There should only be one externally visible function defined in each header file
Having as few declarations as possible in a header file reduces header dependencies.
The header file should have the same name as the function plus extension .h
.
Overloaded functions count as a single function here.
File name should be treated as case sensitive
C++ source files should have extension “.cc
”
C++ header files should have extension “.h
”
Inline functions should be declared in header files and defined in inline definition files
The keyword inline
should be used in both places.
Using a separate inline file is useful to keep the header files clean and small. The separation is also useful where the inlining is disabled in debug builds. The inline file is then included from the source file instead of the header file to reduce compile time.
Header files must have include guards
The include guard protects against the header file being included multiple
times. The format of the symbol name should be <PROJECT>_<FILE>_H
. For
example, the file gnss-sdr/src/bar_baz.h
should have the following guard:
#ifndef GNSS_SDR_BAR_BAZ_H
#define GNSS_SDR_BAR_BAZ_H
...
#endif // GNSS_SDR_BAR_BAZ_H
The name of the macro used in the include guard should have the same name as the file (excluding the extension) followed by the suffix “_H
”
This avoids clashing with other names.
Header files should be self-contained
No missing #include
s.
When a header is included, there should not be a need to include any other headers first
A simple way to make sure that a header file does not have any dependencies is to include it first in the corresponding source file. Example:
// foobar.cc
#include "foobar.h"
#include <cmath>
...
System header files should be included with <>
and project headers with ""
Put #include directives at the top of files
Having all #include
directives in one place makes it easy to find them.
Do not use absolute directory names in #include
directives
The directory structure may be different on other systems.
Do not use relative directory names in #include
directives
The directory structure of the project may change in the future. It is then difficult to correct all the directory names.
Preferred order for #include
directives
Immediately after the header file comment (and include guards if working on a
header file), the minimal list of #includes
required by the file should be
listed. We suggest this order:
- Main Module Header.
- Local headers.
- Third-party library headers.
- System headers.
and each category should be sorted lexicographically by the full path.
The Main Module Header file applies to .cc
files which implement an interface
defined by a .h
file. This #include
should always be included first
regardless of where it lives on the file system. By including a header file
first in the .cc
files that implement the interfaces, we ensure that the
header does not have any hidden dependencies which are not explicitly
#include
d in the header, but should be. It is also a form of documentation in
the .cc
file to indicate where the interfaces it implements are defined. Other
headers should be grouped from most specific to least specific, for the same
reasons described above.
Example:
// foobar.cc
#include "foobar.h"
#include "GPS_L1_CA.h"
#include <gnuradio/io_signature.h>
#include <cmath>
...
Use const
instead of #define in header files
#define
is a preprocessor directive. Before compiling, the middle symbol is
replaced by the right-hand symbol(s). The preprocessor does nothing but text
replacement, so #define
s have no respect for the usual C++ scoping rules. In
other words, #define
is not type-safe.
When replacing #defines
with constants, two special cases are worth
mentioning. The first is defining constant pointers. Because constant
definitions are typically put in header files (where many different source files
will include them), it is important that the pointer be declared const
,
usually in addition to what the pointer points to. To define a constant
char*-based string in a header file, for example, you have to write const
twice:
const char * const authorName = "Carlos Aviles";
However, it is worth reminding you here that string objects are generally
preferable to their char*
-based progenitors, so authorName
is often better
defined this way:
const std::string authorName("Carlos Aviles");
The second special case concerns class-specific constants. To limit the scope of a constant to a class, you must make it a member, and to ensure there is at most one copy of the constant, you must make it a static member:
class My_Acquisition_Algorithm
{
private:
static const int num_dwells = 5; // constant declaration
int scores[num_dwells]; // use of constant
...
};
In general, use const
whenever possible. The wonderful thing about const
is
that it allows you to specify a semantic constraint — a particular object should
not be modified — and compilers will enforce that constraint. It allows you to
communicate to both compilers and other programmers that a value should remain
invariant. Whenever that is true, you should be sure to say so, because that way
you enlist your compilers’ aid in making sure the constraint is not violated.
Each file must start with a copyright notice
Please use the following template at the header of all files:
/*!
* \file filename
* \brief Brief description of the file here
* \author Names of the authors who contributed to this code
*
* Detailed description of the file here if needed.
*
*
* -----------------------------------------------------------------------------
*
* GNSS-SDR is a Global Navigation Satellite System software-defined receiver.
* This file is part of GNSS-SDR.
*
* Copyright (C) 2022 (see AUTHORS file for a list of contributors)
* SPDX-License-Identifier: GPL-3.0-or-later
*
* -----------------------------------------------------------------------------
*/
This Project follows the REUSE recommendations and it is compliant with the REUSE Specification. Please check the details in this tutorial.
Declarations
Provide names of parameters in function declarations
Parameter names are useful to document what the parameter is used for.
The parameter names should be the same in all declarations and definitions of the function
Use a typedef
to define a pointer to a function. Pointers to functions have a
strange syntax. The code becomes much clearer if you use a typedef
for the
pointer to function type. This typedef
name can then be used to declare
variables etc.
double sin(double arg);
typedef double (*Trigfunc)(double arg);
// Usage examples
Trigfunc myfunc = sin;
void callfunc(Trigfunc callback);
Trigfunc functable[10];
Do not use exception specifications
Exception specifications in C++ are not as useful as they look. The compiler does not make the code more efficient. On the contrary, the compiler has to insert code to check that called functions do not violate the specified exception specification at runtime.
Declare inherited functions virtual
An inherited function is implicitly virtual if it is declared virtual in the base class. Repeat the virtual keyword when declaring an inherited function in a derived class to make it clear that this function is virtual.
Do not use global variables
Use singleton objects instead.
Global variables are initialized when the program starts whether it will be used or not. A singleton object is only initialized when the object is used the first time.
If global variables are using other global variables for their initialization there may be a problem if the dependent variables are not initialized yet. The initialization order of global variables in different object files is not defined. Singleton objects do not have this problem as the dependent object will be initialized when it is used. However, watch out for cyclic dependencies in singleton object initializations.
Do not use global variables or singleton objects
Global variables and singleton objects break visibility of what functions do as these can only be used as invisible side effects of functions. To make it clear what inputs and outputs a function has, pass these objects as parameters to the functions.
Do not use global using declarations and using directives in headers
Bringing in names from a namespace
to the global namespace
may cause
conflicts with other headers. The author of a header does not know in which
context the header is used and should avoid polluting the global namespace.
Instead, only use using declarations in the source files.
Do not use using
directives. Bringing in names from a namespace
can cause
all sorts of problems as the namespace
might contain more names than you would
expect. Use them carefully.
#include <iostream>
// Bring in names from the std namespace.
using namespace std;
// Declaring an object with the same name as
// a function in the std namespace.
int dec(int);
void f()
{
// Now we can use iostream names lazily.
cout << "Hello world.\n";
// Error: Ambiguous reference to dec.
cout << "Decimal base is " << dec << '\n';
}
The parts of a class definition must be public
, protected
and private
This makes it easy to read the class definition as the public interface is of interest to most readers.
Declare class data private
Classes should encapsulate their data and only provide access to this data by member functions to ensure that data in class objects are consistent.
The exception to the rule is C type struct
that only contains data members.
Functions that can be implemented using the public interface of a class should not be members
A class definition can be kept small and less prone to change if it only defines the core functionality. Any other functions that can be implemented with this minimal class definition should be implemented as non-member functions. They are still seen as part of the interface of the class.
Example:
class T
{
T operator+=(const T & right);
};
T operator+(const T & left, const T & right)
{
T temp(left);
temp += right;
return temp;
}
Statements
Never use gotos
goto
s break structured coding.
Do not use break
in loops
A break
statement is a goto
statement in disguise and makes code less
readable. A break
statement is still acceptable in switch statements.
Do not use continue
in loops
A continue
statement is a goto statement in disguise and makes code less
readable.
Only have one return
in a function
It is confusing when there is more than one return
statement in a function.
Having only one exit point of a function makes it easy to have a single place
for post-conditions and invariant check. When debugging it is useful to have a
single exit point of a function where you can put a single breakpoint or trace
output. It is sometimes necessary to introduce a result variable to carry the
function return value to the end of the function. This is an acceptable
compromise for structured code.
All switch
statements should have a default
label
Even if there is no action for the default
label, it should be included to
show that the programmer has considered values not covered by case
labels. If
the case labels cover all possibilities, it is useful to put an assertion there
to document the fact that it is impossible to get here. An assertion also
protects from a future situation where a new possibility is introduced by
mistake.
Do not use do-while
loops
do-while
loops are less readable than ordinary while loops and for loops since
the conditional is at the bottom of the loop. The reader must scan the entire
loop in order to understand the scope of the loop. In addition, do-while
loops
are not needed. Any do-while
loop can easily be rewritten into a while
loop
or a for
loop. Reducing the number of constructs used enhances readability.
Other typographical issues
Avoid macros
Most macros can be replaced by constants, enumerations, or inline functions. As macros are not part of the C++ language, they do not provide type safety and debugger support.
Do not use literal numbers other than 0 and 1
Use constants instead of literal numbers to make the code consistent and easy to maintain. The name of the constant is also used to document the purpose of the number.
Use plenty of assertions
Assertions are useful to verify pre-conditions, post-conditions, and any other conditions that should never happen. Pre-conditions are useful to verify that functions are called with valid arguments. They are also useful as documentation of what argument value ranges a function is designed to work with.
Assertions are macros that print error messages when the condition is not met. The macros are disabled in release mode and do not cost anything in performance or used memory in the end product.
Example: This square root function is only designed to work with positive numbers.
#include <assert.h>
double sqrt(double x)
{
// precondition: x is positive
assert(x > 0);
double result;
...
// postcondition: result^2 ~= x
assert(abs(result*result-x)/x < 1E-8) ;
}
Use prefix increment/decrement instead of postfix increment/decrement when the value of the variable is not used
For class objects, there may be two different member functions for the postfix and prefix operations. The postfix operation has to keep a temporary return value of the object before changing the object. For built-in objects, this does not matter as the compiler will be able to optimize away the temporary value when it is not used.
Even if this only matters for class objects, it is a good habit to use prefix increment/decrement at all times.
Write conditional expressions like: if ( 6 == errorNum ) …
This style avoids accidental assignments of the variable when the comparison operator is written with only one equal sign (=). Do not rely on implicit conversion to bool in conditions.
if (ptr) // wrong
if (ptr != NULL) // wrong
if (ptr != nullptr) // ok (C++11)
Use the new cast operators
Use dynamic_cast
, const_cast
, reinterpret_cast
and static_cast
instead
of the traditional C cast notation. These document better what is being
performed.
-
Use
static_cast
as the equivalent of a C-style cast that does value conversion, or when you need to explicitly up-cast a pointer from a class to its superclass. -
Use
const_cast
to remove theconst
qualifier. -
Use
reinterpret_cast
to do unsafe conversions of pointer types to and from integer and other pointer types. Use this only if you know what you are doing and you understand the aliasing issues. -
Do not use
dynamic_cast
except in test code. If you need to know type information at runtime in this way outside of a unittest, you probably have a design flaw.
Language support library
The C library should not be used.
Some C++ libraries (e.g., <cstdio>
) also have corresponding C versions
(e.g., <stdio.h>
). This rule requires that the C++ version is used.
The library functions atof
, atoi
and atol
from library <cstdlib>
should not be used.
These functions have undefined behavior associated with them when the string cannot be converted.
Example:
#include <cstdlib>
int32_t f ( const char_t * numstr )
{
return atoi ( numstr ); // Non-compliant
}
The library functions abort
, exit
, getenv
and system
from library <cstdlib>
should not be used.
The use of these functions leads to implementation-defined behavior.
Example:
#include <cstdlib>
void f ( )
{
exit(0); // Non-compliant
}
The time handling functions of library <ctime>
should not be used.
Various aspects are implementation-defined or unspecified, such as the formats of times.
Example:
#include <ctime>
void f ( )
{
clock(); // Non-compliant
}
The unbounded functions of library <cstring>
should not be used.
The strcpy
, strcmp
, strcat
, strchr
, strspn
, strcspn
, strpbrk
,
strrchr
, strstr
, strtok
and strlen
functions within the <cstring>
library can read or write beyond the end of a buffer, resulting in undefined
behavior.
Ideally, a safe string handling library should be used.
Example:
#include <cstring>
void fn ( const char_t * pChar )
{
char_t array [ 10 ];
strcpy ( array, pChar ); // Non-compliant
}
The macro offsetof
should not be used.
Use of this macro can lead to undefined behavior when the types of the operands are incompatible, or when bit fields are used.
Example:
#include <cstddef>
struct A
{
int32_t i;
};
void f1 ( )
{
offsetof ( A, i ); // Non-compliant
}
Dynamic heap memory allocation should not be used.
The use of dynamic memory can lead to out-of-storage run-time failures, which
are undesirable. The built-in new
and delete
operators, other than the
placement versions, use dynamic heap memory. The functions calloc
, malloc
,
realloc
and free
also use dynamic heap memory.
There is a range of unspecified, undefined, and implementation-defined behavior associated with dynamic memory allocation, as well as a number of other potential pitfalls. Dynamic heap memory allocation may lead to memory leaks, data inconsistency, memory exhaustion, non-deterministic behavior, etc.
Note that some implementations may use dynamic heap memory allocation to
implement other functions (for example, functions in the library cstring
). If
this is the case, then these functions should also be avoided.
Example:
void f1 ( )
{
int32_t * i = new int32_t; // Non-compliant
delete i;
}
The signal handling facilities of <csignal>
should not be used.
Signal handling contains implementation-defined and undefined behavior.
Example:
#include <csignal>
void my_handler ( int32_t );
void f1 ( )
{
signal ( 1, my_handler ); // Non-compliant
}
Do not use std::vector<bool>
The std::vector<bool>
specialization does not conform to the requirements of a
container and does not work as expected in all STL algorithms.
In particular &v[0]
does not return a contiguous array of elements as it does
for other vector types. Additionally, the C++ Language Standard guarantees that
different elements of an STL container can safely be modified concurrently,
except for a container of std::vector<bool>
type.
Example:
#include <cstdint>
#include <vector>
void foo ()
{
std::vector <int32_t> vi; // Compliant
std::vector <bool> vb; // Non-Compliant
}
The error indicator errno should not be used.
errno
is a facility of C++ which should in theory be useful, but which in
practice is poorly defined by ISO/IEC 14882:2014. A non-zero value may or may
not indicate that a problem has occurred; therefore errno
should not be used.
Even for those functions for which the behavior of errno
is well defined, it
is preferable to check the values of inputs before calling the function rather
than relying on using errno
to trap errors.
Example:
#include <cstdlib>
#include <cerrno>
void f1 ( const char_t * str )
{
errno = 0; // Non-compliant
int32_t i = atoi ( str );
if ( 0 != errno ) // Non-compliant
{
// handle error case???
}
}
The stream input/output library <cstdio>
should not be used.
This includes file and I/O functions fgetpos
, fopen
, ftell
, gets
,
perror
, remove
, rename
, etc. Streams and file I/O have a large number of
unspecified, undefined, and implementation-defined behaviors associated
with them.
Example:
#include <cstdio> // Non-compliant
void fn ( )
{
char_t array [ 10 ];
gets ( array ); // Can lead to buffer over-run
}
Final recommendations
Use tools for automated code formatting
As the code base and the number of contributors grows, maintaining a consistent code formatting is difficult and creates a lot of noise in commits. In order to handle this issue, GNSS-SDR uses clang-format, a set of tools for automated code formatting. It can be used as a standalone tool and supports a number of editor integrations.
The rules for code formatting are configured in the file .clang-format at the root of the source tree. The style options are described in Clang-Format Style Options.
You can use clang-format in two simple steps:
Step 1.- Install clang-format
- In GNU/Linux using Debian / Ubuntu distributions:
$ sudo apt-get install clang-format
- In GNU/Linux using Fedora / CentOS distributions:
$ sudo yum install clang-tools-extra
- In GNU/Linux using ArchLinux:
$ sudo pacman -S clang
- In GNU/Linux using openSUSE:
$ sudo zypper -n install llvm-clang
- In macOS using Homebrew:
$ brew install clang-format
- In macOS using Macports:
$ sudo port install clang-15
NOTE: You can see all available choices with
port select --list
for clang:$ port select --list clang Available versions for clang: mp-clang-15 none (active)
With
sudo port select --set clang <version>
you choose one of them as the new default, which will create symlinks in/opt/local/bin
without the version suffix.$ sudo port select --set clang mp-clang-15 selecting 'mp-clang-15' for 'clang' succeeded. 'mp-clang-15' is now active.
You can confirm this change by looking at the version of the tool:
$ clang-format --version clang-format version 15.0.0
If you later wish to remove these symlinks in order to avoid hiding tools installed by Xcode, just select the
none
version.
Step 2.- Apply clang-format
-
Tell your favorite editor to use clang-format. You can use it in Eclipse via CppStyle, in Visual Studio Code via the Clang-Format Extension, and in many other editors. Once the corresponding plugin or module is installed, configure your editor to run clang-format on every file save.
-
For applying code formatting from the command line:
$ clang-format -i <file>
or for a folder and its and subfolders:
$ find src/algorithms/conditioner/ -iname *.h -o -iname *.cc | xargs clang-format -i
For each input file, clang-format will try to find the
.clang-format
file located in the closest parent directory of the input file, so the one in the root folder will apply. Please do not modify that file, but feel free to propose changes (that would be applied to the whole source tree) by filling an issue at GitHub in order to let other developers to discuss them.
You can disable the automatic formatting of a piece of code by using comments:
// clang-format off
... code here will not be formatted.
// clang-format on
Note the space in between the comment start (//
) and clang-format
. This
space is required for the comment to be successfully detected.
Please apply clang-format to your changes before any pull request.
Step 3.- Check Markdown formatting
If you have modified markdown files (ended in .md
), please apply
prettier.
- Install prettier:
$ sudo npm install --global prettier
- Run it from the root of the source code tree:
$ find . -iname "*.md" | xargs prettier --parser markdown --print-width 80 --prose-wrap always --write
Please apply prettier to your changes before any pull request if you changed and/or created markdown files.
Automated code formatting tools help to improve Maintainability.
Use code linters
In this context, linting refers to the process of running a program that analyzes code for potential errors.
clang-tidy is a clang-based C++ “linter” tool. Its purpose is to provide an extensible framework for diagnosing and fixing typical programming errors, like style violations, interface misuse, or bugs that can be deduced via static analysis.
The checks performed by clang-tidy are configured in the .clang-tidy file at the root of the source tree. Definitions and rationale are available at the list of clang-tidy checks.
You can use clang-tidy in two simple steps:
Step 1.- Install clang-tidy
- In GNU/Linux using Debian / Ubuntu distributions:
$ sudo apt-get install clang clang-tidy
- In GNU/Linux using Fedora / CentOS distributions:
$ sudo yum install clang clang-tools-extra
- In GNU/Linux using ArchLinux:
$ sudo pacman -S clang
- In GNU/Linux using openSUSE:
$ sudo zypper -n install llvm-clang
- In macOS using Homebrew:
$ brew install llvm $ ln -s $(brew --prefix llvm)/bin/clang-tidy /usr/local/bin /usr/local/bin $ ln -s $(brew --prefix llvm)/bin/clang-apply-replacements /usr/local/bin $ ln -s $(brew --prefix llvm)/bin/run-clang-tidy /usr/local/bin
- In macOS using Macports:
$ sudo port install clang-15
Step 2.- Apply clang-tidy
This tool integrates nicely with CMake >= 3.6. In GNSS-SDR, all you need to do is to tell CMake to use clang. From an empty build folder (so if you have leftovers from previous buildings with GCC, please delete them out before switching to clang), write:
$ cmake -DCMAKE_CXX_COMPILER=/usr/bin/clang++ \
-DCMAKE_C_COMPILER=/usr/bin/clang ..
$ make volk_gnsssdr_module core_monitor core_libs pvt_libs
(pointing CMAKE_CXX_COMPILER
and CMAKE_C_COMPILER
to the actual location of
the clang binaries in your machine). The first command line will create a file
named compile_commands.json
in your build folder containing the exact compiler
calls for all translation units of the project in machine-readable form. Then,
you need to build some libraries (i.e., volk_gnsssdr_module
, core_monitor
,
core_libs
, and pvt_libs
) that require some building steps not supported by
clang-tidy. After that, you can use the run-clang-tidy
script (called
run-clang-tidy.py
in some platforms) to perform the project default checks
over all files in the compilation database:
$ run-clang-tidy -fix
or you can apply specific checks by doing:
$ run-clang-tidy -header-filter='.*' -checks='-*,modernize-use-nullptr' -fix
An alternative choice is to run clang-tidy along with the building process, by
activating the building option ENABLE_CLANG_TIDY
:
$ cmake -DCMAKE_CXX_COMPILER=/usr/bin/clang++ \
-DCMAKE_C_COMPILER=/usr/bin/clang \
$ make volk_gnsssdr_module core_monitor core_libs pvt_libs
$ cmake -DENABLE_CLANG_TIDY=ON ..
$ make
In this latter case, please do not use parallel building (e.g., make -j4
).
Be aware that this process can take a long time.
You can read more about the usage of this tool in the clang-tidy documentation.
Please apply clang-tidy to your changes before any pull request
With clang and clang-tidy already installed, please do:
$ cmake -DCMAKE_CXX_COMPILER=/usr/bin/clang++ \
-DCMAKE_C_COMPILER=/usr/bin/clang ..
$ make volk_gnsssdr_module core_monitor core_libs pvt_libs
$ run-clang-tidy -fix
or
$ cmake -DCMAKE_CXX_COMPILER=/usr/bin/clang++ \
-DCMAKE_C_COMPILER=/usr/bin/clang \
$ make volk_gnsssdr_module core_monitor core_libs pvt_libs
$ cmake -DENABLE_CLANG_TIDY=ON ..
$ make
and check if there are warnings and/or fixes related to your code (use git
status
and git diff
). If you agree with the changes, add and commit them in
order to be included in your pull request. Otherwise, discard the changes with
git ckeckout -- /path/to/changed_file
.
An automated code linter helps to improve Efficiency, Reliability, and Maintainability.
Learn from the best
Take a look at the C++ Core Guidelines edited by Bjarne Stroustrup and Herb Sutter.
Use of Boost libraries
Boost is a set of free, expertly designed, peer-reviewed portable C++ source libraries. Boost libraries are intended to be widely useful, and usable across a broad spectrum of applications. However, Boost regularly makes backward-incompatible changes, making supporting a wide range of Boost versions hard. All things equal, prefer standard C++ constructs over Boost constructs.
Use common sense and BE CONSISTENT
The point of having style guidelines is to have a common vocabulary of coding so people can concentrate on what you are saying, rather than on how you are saying it. We present global style rules here so people know the vocabulary. But local style is also important. If the code you add to a file looks drastically different from the existing code around it, the discontinuity throws readers out of their rhythm when they go to read it. Try to avoid this.
References
-
B. W. Kernighan and P. J. Plauger, The Elements of Programming Style, McGraw-Hill, New York, 1974. ↩
Leave a comment