hivemind 1.0.0
Loading...
Searching...
No Matches
Coding Standards

Introduction

This document serves as both a guide to the developers and maintaners of Hivemind, and an explanation of code design and architecture choices for other actors who needs to or wants to look at the source code of Hivemind.

Semantics and coding style issues

Treat compiler warnings as errors

Compiler warnings are useful hints to improve code. Generally, if the compiler issues a warning, adjust the code to suppress this.

Object oriented programming

Generally, developers of Hivemind should adhere to an object oriented programming (OOP) style in Hivemind's codebase, especially when implementing top-level interfaces of major components. It is important to note, however, that it is encouraged avoid OOP when moving into deeper implementation details. Being too strict on an object oriented approach often leads to unnecessary complications.

Assert extensively

The use of assertions in code is encouraged. Assertions are not only very useful for verifying states and data, they also document expected behaviour for other developers looking at the codebase.

Source Code Formatting

Clang Format

The root of the project contains a .clang-format configuration file. This should be used to format all source files to maintain consistent formatting throughout the codebase, and to prevent unnecessary changes to untouched code cluttering the version control history.

The control comments clang-format off and clang-format on can be used for specific code blocks where retaining a specific format is preferable, but this should be used sparingly.

Commenting

Comments are useful for documenting source code and provides improved readability and maintainability. Comments should provide an explanation of the code's purpose rather than an explanation of how it is done; the code documents the process itself.

Class definitions

Proper documentation of class definitions are expected. The purpose of a class and how it works should be explained with Doxygen comments to keep the docs as up to date as possible. These comments should be located in the header file of the class.

Comment formatting

Generally, prefer C++-style comments rather than C-style. For normal comments, this means using //, and using /// for doxygen comments.

Doxygen comments

Prefer using triple-slash (///) comments for doxygen documentation. Prefer using backslash (\) over at (@) for doxygen tags such as param, file and returns.

Prefer:

///
/// \brief Function used to create Bar
///
/// \param id Unique ID that represents Bar
/// \returns Bar object
///
Bar Foo(int id);

Avoid:

/**
* @brief Function used to create Bar
*
* @param id Unique ID that represents Bar
* @returns Bar object
*/
Bar Foo(int id);

White space

Prefer spaces over tabs. There are valid arguments for both the use of spaces and tabs, but a mixture of both of them should not be used. Therefore, a standard of using spaces is preferred in this project. Clang Format should ensure that tabs are converted to spaces.

Column width

If a maximum column width is going to be defined, using a standard width makes sense. Therefore, a maximum column width of 80 has been set. This should be enforced by Clang Format. There are exceptions to this rule, and these are generally related to comments or strings that have a specific format that makes more sense than the one enforced by Clang Format. In these cases, the use of clang-format off and clang-format on are allowed.

Language specifics

For now, all the source code of Hivemind is written in C++. When we start to use ROS as part of the system, there are plans to experiment with the feasibility of implementing modules in Python.

C++

Standard version

Hivemind uses C++17.

Although C++20 is both feature-complete and mostly supported by the major compilers, our preferred build tool-chain, CMake, only supports some features through the use of experimental flags. As such, we currently view C++20 as not fully supported and not a viable option. This may change in the future.

Standard library

Generally prefer to use the data structures, algorithms and functions available in the C++ standard library rather than implementing custom solutions. The standard library is mature, robust, extensively tested and highly optimized.

Naming convention

Maintaining a uniform naming convention throughout the codebase helps increase readability. As such, we use a well-defined naming convention that must be adhered to when writing C++ code.

  • Namespaces, classes, structs and enums should all be named using PascalCase.
  • Macros and enum values should be named using SCREAMING_SNAKE_CASE.
  • Local variables and functions outside classes/structs should be named using camelCase.
  • Members and attributes of classes/structs should be named using PascalCase, but private attributes should be pre-fixed with m_.

Classes and structs

In C++, classes and structs are essentially the same thing and they can generally be used interchangeably, given that you take access specifiers into account.

We define a semantic difference in our codebase: Classes are to be used for more complex data objects with attributes and members of both private and public access. Structs are to be used for more simple data objects where all attributes are public.

When defining classes, the attributes and members of different access specifiers should be defined in the following order:

  1. public
  2. protected
  3. private

The rationale for this is that if someone looks at the header file of a class to see what attributes and members they can access, they will not care about private members and implementation details. They want to know which members and attributes they can actually use.

Include style

At the top of the file, below the header guard in the case of header files, should the includes required by the file be listed. They should be ordered as follows:

  1. Main module header
  2. Project headers
  3. Library headers
  4. System headers

The main module header only applies to .cpp files with a header files whose classes and functions it implements. The project headers refer to other header files part of the Hivemind project that the file depends on. The library headers refer to dependant header files from external libraries such as QT headers. Finally, system headers generally refer to headers that are part of the C standard library and C++ standard library.

The main module header and project files should be include with the double-quote style, and library files and system files should be included with the angled brackets style.

Header files should only include other header files that it strictly needs. If the include can be moved to the corresponding .cpp file instead, it should.

Example:

// foo.cpp
#include "foo.h"
#include "hivemind_core.h"
#include "hivemind_gui.h"
#include <QWidget>
#include <QMath>
#include <vector>
#include <map>
...

Header Guards

Header files should be protected using #pragma once rather than traditional header guards. #pragma once is technically not standard but it is widely supported and provides several advantages including less code, less risk of name clashing and potentially improved compilation speed.

Prefer:

// foo.h
#pragma once
class Foo
{};

Avoid:

// foo.h
#ifndef FOO_H
#define FOO_H
class Foo
{};
#endif // FOO_H

Use of the auto keyword

The use of the auto keyword should be reserved for cases where the type can be deduced from the context. An example of this is when casting a variable to another type. The cast operation will specify the type, so it is easily deduces.

Example:

// It is obvious the resulting type will be Foo
auto foo = static_cast<Foo>(bar);

The auto keyword can sometimes also be used to increase readability of the codebase. Examples of this is when using the chrono library in the std namespace. It is extremely verbose, and using auto can help with readability.

Example:

// The following assignments are equivelant, but one is arguibly more readable.
std::chrono::time_point<std::chrono::steady_clock> start = std::chrono::steady_clock::now();
auto start = std::chrono::steady_clock::now();

RAII

RAII, or Resource Acquisition Is Initialization, is a C++ programming technique which ensures that the life-cycle of a limited resource, such as heap memory or a locked mutex, is bound to the life-cycle of an object, meaning that the resource is accessible and usable as long as the object lives, and that it is automatically freed when the object is destroyed.

RAII is generally implemented by acquiring the needed resource in the constructor of a class, and freed in the destructor.

Prefer to use RAII where applicable.