- optimize for the reader of the code: the code will be read many more times than it is written
- don't be clever: any code that contains clever or peculiar tricks will be surprising to the reader which, in all likelihood, will be you two months from now
- what is not covered by the style guide is generally fine to use/do
- if in doubt: ask
- lowercase
- use only lower case alphanumeric characters (
a-z0-9
) - no spaces
- name must not start with a number
- use only lower case alphanumeric characters (
- consider starting the project name with a letter
a
as prefix (that stands foradev
,Agnesoft
or an indefinite article) to distinguish the name from similar/common words
- snake_case
- use only lower case alphanumeric characters (
a-z0-9
) _
instead of space- name must not start with a number
- use only lower case alphanumeric characters (
- file names that should follow project name (i.e. module/project interface) follows project name rules
- always use
[project]
prefix to identify the work (use multiple if the work affects multiple, e.g.[project1][project2]
) - always include issue number in branch name, PR title and commit message, e.g.
#111
,(#111)
- always start with a noun such as
Add
,Remove
,Update
,Refactor
,Fix
(capitalization is useful to distinguish it from the project affiliation) - always match all four: issue title, branch name, pull request title & merge commit message
- use
Fix
verb for bug issues/branches/PRs/merge commit messages
Examples
issue: [devops] Add style guide
branch: devops-Add-style-guide-#111
PR: [devops] Add style guide #111
merge commit: [devops] Add style guide #111 (#112)
NOTE 1
Git does not allow branches starting with [
so remove the square brackets from the branch name.
NOTE 2
GitHub reformats issue titles, PR titles and commit messages (especially when there is only one commit in a branch). Double check these before saving or committing.
- all projects are located under
projects/
directory - sources should be placed directly in the project directory
- public headers (if headers are used) should be placed in
include/<project name>
directory within the project directory so that they can be included without ambiguity as<project name>/<header>.hpp
- private headers should be placed with the source files
- every project should have a
test/
directory with a test application - projects can have sub-projects (they should be rare) placed directly in the parent project's directory (e.g.
projects/abuild/preprocessor
) - every project must have a readme.md
- use markdown
- always provide table of contents
- the purpose of the readme is to provide quick overview of the project
- what the project does
- how is the project used
- every readme must have at lest 3 sections:
- overview: what the project does/provides and what are its features
- prerequisites: what are the prerequisites & dependencies of using the project
- usage: steps to start using the project with examples, e.g.
- add build dependency to
myproject
- add
import myproject;
in your code - call
myproject::foo()
to do X - call
myproject::bar()
to do Y
- add build dependency to
- add additional sections and information as you deem fit such as
Known Issues
- Array
- Block Statements
- Columns
- Comments
- Conditions
- Command Substitution
- eval
- Functions
- Files
- Indentation
- Listing Files
- Quoting
- Variables
- use
(...)
to declare an array
then
anddo
on the same line asif
andwhile
- unlimited but keep it reasonable / logical
- avoid commenting code, use well named functions
- use
[[...]]
- use
((...))
and$((...))
for mathematical constructs - use
==
instead of=
for equality - prefer using
if [[ command ]]; then ... fi
instead of checking value of$?
- do not use
[...]
- do not use
test ...
- do not use
[ $a -gt $b ]
for mathematical constructs
- use
$(...)
- do not use
`...`
- prefer
"${var}"
over"$var"
- do not use
- names according to generic rules
- use
*.sh
- use shebang
- make shell scripts executable unless they are libraries/imports only
- use
snake_case
for function names - use
function
keyword - all variables declared
local
- start functions with saving arguments to named variables
- only spaces
- 4 spaces
- use builtin
glob
in loops - do not parse
ls
orfind
- use
"..."
for all strings with variable substitution - use
'...'
only to prevent variable substitution - always quote variables if they could fail the syntax when empty
- use
camelCase
for variable names - use
SCREAMING_SNAKE_CASE
for environment variables and constants - do not use
let
ordeclare
- variables inside functions should be declared
local
- variables that are not to be modified should be declared
readonly
- Aliases
- auto
- Casts
- Classes
- Comments
- const
- consteval
- constexpr
- Documentation
- Enumerations
- Exceptions
- Formatting
- friend
- Functions
- Global Variables
- Headers
- Imports
- Includes
- Initialization
- inline
- Integer Types
- Lambdas
- Language Extensions
- Literals
- Local Variables
- Member Variables
- Memory Management
- Modules
- Multithreading
- Namespaces
- Naming
- noexcept
- nullptr
- Preincrement vs Postincrement
- Preprocessor
- References & Pointers
- Run Time Type Information (RTTI)
- Scoping
- sizeof
- Sources
- Static Variables
- Structs
- Templates & Concepts
- Ternary
- this
- Version
- virtual
- never use
typedef
, useusing
instead - avoid type aliasing: do not use type aliases for regular types (e.g.
using MyList = std::vector<int>
), always spell out the type for clarity - use type aliasing for complicated template types such as custom iterators (e.g.
using iterator = MyTemplatedIterator<ValueType, ConstValueType, SomeFlag>
) - use type aliasing to satisfy type traits, e.g.
using value_type = ...
- almost never auto
- use auto when necessary such as with lambdas (e.g.
auto l = [] {};
) - use auto when the assignment expression denotes explicitly the type already (e.g.
auto i = static_cast<int>(var);
) - use auto when the explicit type would obfuscate the code such as when using iterators in loops (e.g.
for (auto it = v.begin(); it != v.end(); ++it) {}
)
- never use C-cast, i.e.
(int)var
- never use
const_cast
- use
reinterpret_cast
only if you know your use case is not undefined behavior
- do the initialization in constructor (no two phase initialization) and throw an exception in case of an error
- single parameter constructors must be declared
explicit
- no implicit conversions, mark conversion operators
explicit
- always declare member variables private and use accessor methods
- be mindful of padding when ordering member variables
- follow the rule of 0 or rule of 5
- avoid multiple inheritance
- prefer composition to inheritance
- never inherit virtually
- avoid commenting code unless you are documenting it
- refactor and create a named functions instead of commenting
- if you must use a comment prefer
//
over/* */
- always be const correct
- use west const
- use as much as possible
- always use for purely compile time calculations to avoid leaking them to runtime via
constexpr
- always document externally visible public and protected symbols (i.e. exported or header declared classes, methods, functions, structs, member variables, enums etc.)
- use Doxygen style
//!
comments for in-code documentation in front of the symbol (i.e. function, class, member variable) - never document private code
- add brief summary when documenting classes (e.g.
\brief The X provides something
) - focus on
what
a documented entity does rather than onhow
orwhy
it does it - always mention assumptions (e.g. about arguments), side effects and results (e.g. return value)
- include examples of usage
- use
enum class
- do not use old
enum
- use exceptions for error handling
- use or inherit from
std::exception
or its descendants (e.g.std::runtime_error
,std::logic_error
etc.)
- use
clang-format
- avoid using friend classes
- use friends only wheen necessary (e.g. standalone comparison operators)
- ~ 4 lines of code per function (split your code into more well named functions)
- prefer 1 to 2 arguments, max 4 individual arguments
- use custom
struct
to pass in more than 4 arguments - do not use default arguments, use overloads instead
- prefer return values over output arguments
- prefer references to pointers
- always fully qualify free function calls, e.g.
::ns::foo();
- always qualify static function calls, e.g.
MyClass::bar();
- always use
this->
when calling non-static member function, e.g.this->foo();
- avoid using global variables
- never use mutable global variables
- only use const global variables that cannot throw during initialization (e.g. a type is trivial)
- always place them in a namespace
- always fully qualify them when using them, e.g.
::ns::my_var;
If you must create a header file:
- use
*.hpp
extension to distinguish the header from C header (C++ headers are rarely compilable in C) - use preprocessor include guard
#ifndef/#define/#endif
rather than non-standardpragma once
- the include guard define should have format
PROJECT_HEADER_HPP
(e.g.atest/expect.hpp
->ATEST_EXPECT_HPP
) - never forward declare symbols used in public API (e.g.
#include <string> ... void foo(const std::string &str);
) so that the user of the header does not need to guess what else is needed to use the header - forward declare symbols used internally and move the includes into the source file
- import with
""
only header units that can be located by the relative path from the current file, i.e. files in the same directory - import with
<>
header units from all other sources (other projects, system headers etc.) - disambiguate the imports of header units if possible with a path prefix (e.g.
projectX/header.h
instead of justheader.h
)
- avoid using
#include
, use import instead
If you must use an include:
- include with
""
only headers that can be located by the relative path from the current file, i.e. files in the same directory - include with
<>
headers from all other sources (other projects, system headers etc.) - disambiguate the include if possible with a path prefix (e.g.
projectX/header.h
instead of justheader.h
) - use
clang-format
for ordering
- use
=
for scalar types (e.g.int i = 10;
) - use aggregate initialization
{}
for all other types (e.g.MyClass my_class{"hello"};
) - use designated initializers for structs (e.g.
S s{.member1 = 1, .member2 = "value"};
) - never use
()
for initialization unless necessary (e.g. certainstd::string
constructors)
- do not use unless it is required (e.g. standalone function definitions in a header)
- use fixed length integer types in public APIs
- use
int
only in internal code - prefer signed integer types
- avoid unsigned integer types in loops
- avoid comparing signed and unsigned integers and avoid casting in such cases, use
std::cmp_*
family of STL functions instead
- avoid lambdas that escape current scope
- avoid captures if possible
- prefer
&
capture for local lambdas - prefer
=
capture for lambdas that escape current scope - do not use capture list solely to rename variables
- do not use language extensions, no exception
- do not use user defined literals
- use digit separators for long values (e.g.
1'000'000
) - prefer specifying the type via a literal in case of possible ambiguity (e.g.
auto v = 5.0f
)
- declare variables close to their first use
- declare variables with narrowest scope possible
- declare variables const or constexpr correct
- consider using conditional and loop initializers for variables bound only to that scope (e.g.
if (int i = foo(); i == 1) {}
,while (int i = 0; i < 10) { ++i; }
- always declare member variables
private
in classes - always declare member variables
public
in structs - always use
this->
when accessing member variables
- prefer stack over heap
- use smart pointers and prefer
std::unique_ptr
- avoid use of
std::shared_ptr
unless it is necessary - never manage memory manually, i.e. use naked
new
anddelete
- never manage raw arrays on the heap (
new[]
,delete[]
), usestd::array
instead - never use
malloc/free
unless a third party API requires it
- the module name should be the project name (e.g.
atest
->export module atest
) - only one module interface per project
- avoid the use of global module fragment and includes in general, import them as header units instead
- split modules into interface and module partitions
- define each module partition in its own file
- keep module partitions small, i.e. one class per partition
- export only what is part of the public API, you do not have to export transitively unless the transitive symbol is part of the API as well (as opposed to just be used by the API), e.g.
class C; export C foo();
is fine because C cannot be instantiated outside of the module and can be used only via the exported API, i.e. functionfoo
- avoid exporting entire namespaces
- avoid multithreading if possible
- avoid using locks if possible, consider using coroutines
- prefer highest possible level of abstraction (e.g. atomics, coroutines) to low level multithreading primitives (e.g. mutex)
- place all code in a namespace
- use only single level of namespaces
- the namespace name should be the project name (e.g. project
atest
->namespace atest
) - never use
using namespace
directive (especiallyusing namespace std;
)
- modules: lowercase (e.g.
mymodule
,acommandline
) - namespaces: lowercase (e.g.
namespace ns
,namespace myns
) - types: CamelCase (e.g.
class MyClass
,struct Iterator
,typename T
) - enums: CamelCase (e.g.
enum class MyEnum { EnumValue };
) - macros: SCREAMING_SNAKE_CASE (e.g.
#define MY_MACRO 3
) - functions: snake_case (e.g.
foo()
,my_bar()
) - variables: mixedCase (e.g.
int var
,std::string someDoc
) - constants: SCREAMING_SNAKE_CASE (e.g.
const int MY_CONSTANT = 3
)
NOTE
Use mixedCase
for member variables as well and use this->
to disambiguate them from local variables.
- use when the entire call chain of the function is noexcept
- avoid use of
noexcept
constraints
- always use
nullptr
- never use
NULL
- never use
0
as a pointer value
- prefer preincrement
- never create macros
- never create preprocessor defines
- never use macros unless they are unavoidable part of third party API
- avoid the use of preprocessor defines unless it is needed for platform specific code (e.g.
ifdef _WIN32
)
- prefer references in all contexts
- never transfer ownership via a raw pointer, use smart pointers instead
- always enabled
- always use
{}
even for statements with single statement (e.g.if (true) { foo(); }
) - avoid use of arbitrary scoping (
e.g. void foo() { { fizz(); } buzz(); }
), make a function or lambda instead - use loop/conditional bound scopes where applicable, e.g.
if (int i = 0; x < z) {}
- prefer the variable as argument (not the type)
- use
*.cpp
extension for source files - name the files according to their content, ideally after the class/module/partition they define (e.g.
class MyClass
->myclass.cpp
,export module atest
->atest.cpp
)
- avoid using global static variables including as class members
- never use mutable global static variables
- only use global static variables that are const and that cannot throw during initialization (e.g. a type is trivial) and that do not use other static variables in their initialization
- prefer static variables in functions that ensure the initialization on first use
- always qualify them when using them, e.g.
MyClass:my_var;
- no member functions
- be mindful of padding when ordering member variables
- prefer custom structs over
std::pair
andstd::tuple
- use
concepts
(requires
) instead - never use SFINAE, use
concepts
instead
- avoid ternary operator, prefer full
if/else
instead - use ternary for simplest of cases such as one line functions with very clear logic
- always use
this->
when calling member functions (methods) - always use
this->
when accessing member variables
- avoid runtime polymorphism
- use compile time alternatives such as CRTP
- always use
override
- never put
virtual
andoverride
on the same function - never change accessor of the function you are overriding (e.g.
private
->public
)