Programming Style Guide#
Overview#
This document contains rules that are to be followed when creating or updating the primary robot code repositories (e.g., ESP32PRCodebase).
There are three main areas: programming principles, naming conventions, and formatting. Most formatting can be done automatically using the astyle formatter.
All code that is in the
production,dev/main, ordev/experimentalbranches must follow this guide unless explicit approval is granted by the programming team lead.You should strive to follow this guide as you are writing code. However, this is not always feasible, so you should make sure to clean up your code before submitting a pull request to one of the three aforementioned branches.
This guide may seem strict, and it is, but for good reason:
It is much easier to understand the purpose of code that you did not write.
It helps reduce maintenance load (and other problems commonly referred to as “technical debt”) – that is, the amount of effort required to keep the code working.
Certain parts of this guide are designed to entirely avoid difficult and obscure problems we have encountered and solved already, which would waste development time.
Many companies institute similar policies.
It will help you develop good coding style.
The biggest takeaway
If you don’t remember anything else from this document, at least remember this: our code must be readable and maintainable. Someone that isn’t you should be able to pick up where you left off.
Programming Principles#
High-Level#
Document, document, document.
This is documentation. There must also be documentation in the code (i.e., comments).
Prioritize readability and maintainability over clever tricks.
If you do something that isn’t easily understandable, you must document it well.
Make it work, make it right, make it fast.
Always test your code on live hardware (i.e., on a robot).
This is a required part of the process to merge code into
production.
Always start by testing the simplest case. Walk before you run.
Follow principles of polymorphism and encapsulation where possible.
If you haven’t taken ECCS 1621 Programming 2 yet, don’t worry too much about these concepts. You’ll learn about them eventually.
Code-Level#
No
using namespace std.Sometimes this is taught as a way to ease learning C++. Here, we don’t use it in order to enhance readability.
See also: https://stackoverflow.com/questions/1452721/whats-the-problem-with-using-namespace-std
Use
#includeguards, not#pragma onceInclude guards should be named in the following format:
FILENAME_Hi.e.,
MACRO_CASEwith the.replaced with_.
See also: https://en.wikipedia.org/wiki/Include_guard
A file with include guards looks like this:
// foo.h
#ifndef FOO_H
#define FOO_H
// your code here
#endif /* FOO_H */
Do not define functions in header files unless they are incredibly simple.
If you do this, it can cause problems because functions defined inside classes are automatically
inlined.
// foo.h
class Foo {
// acceptable
Foo() {}
// acceptable
Foo(int bar) {
this->bar = bar;
}
Bar getBar() {
return this->bar;
}
// not acceptable
Foo(int bar, int car, Bar other, ...) {
...
...
...
}
}
Naming Conventions#
Do not obfuscate variable names.
Make your names clear and logical.
Avoid abbreviating except where the meaning is blatantly obvious (ex. Quarterback → QB in
Quarterback.cpp).When in doubt, error on the side of a more descriptive name.
It is also strongly recommended to clarify what the variable does using a comment at its declaration
Do not prefix variables with letters indicating type (ex.
bool bFoo).In some cases it may be acceptable to do this for pointers (ex.
int* pBar), but usually if you feel like you are forced to do this, you should rethink your naming conventions on a larger level.
Do not prefix private members with
m_(ex.m_foo).Avoid prefixing private members with
_(ex._bar).This may sometimes be acceptable (with permission) if the variable would logically start with a number or other invalid starting character.
Variables and functions should use
camelCase(or rarely,snake_case)If the name includes an abbreviation, acronym, etc. of some kind (ex. ESP) then you should use capitals appropriately (ex.
ESPinstead ofEsp).In such cases, it may be more readable to separate consecutive acronyms with
_.
Classes and Enums should use
UpperCamelCase.Enum members should use
MACRO_CASEor (rarelysnake_casewith approval from a senior dev)Note: right now we only use lowercase enum with robot config related stuff. And debouncer.
Constants should use
MACRO_CASEInclude guards must use
MACRO_CASEwith the.(separating the filename from the extension) replaced with_.For example, a file named
foo.hwould useFOO_Has the include guard#define.
Filenames should generally be
UpperCamelCaseif they primarily house a class, or normalcamelCaseif they do not.
Formatting#
Whatever you do, be consistent.
Don’t violate one of these rules unless you have a good reason.
You should also ask the programming team lead first.
Most formatting can be done automatically by astyle (see the section below).
Things astyle cannot do automatically#
Functions with a long list of parameters shall be formatted with the parameters on line(s) after the opening parenthesis
(with a single tab indent, with the closing parenthesis)and opening brace{on the next line. For example:
void foo(
int bar,
int car,
int dar
) {
...
}
note: astyle will not do this automatically, but it will not mess with it given my config
Classes and constructors with a long list of base classes or member initializers shall be formatted the same way as functions with many parameters. For example:
class Foo : Bar, Car {
Foo(
int a,
int b,
int c,
int d
) : Bar(a, b, c, d),
Car(a, b, c, d) {
this->a = a;
}
}
Constructor parameters that directly initialize a class member shall be named the same as the class member, and the
thiskeyword shall be used to reference the class member. See the codeblock above for an example.As of 2024-08-25,
astylemay move the*or&of pointer dereferences or address references. This is a known issue and is being worked on. Fix appropriately or surround withastyleguards (see here).
Things astyle can do automatically#
publicandprivatekeywords insideclassheaders shall be indented one tab. Class members under them shall be indented two tabs.Spaces shall be placed between:
any non-function keyword and open parenthesis
ex.
if (instead ofif(
comparison or assignment operators and anything else (except increment operators, i.e.,
++/--)ex.
x == 0instead ofx==0,y = 0instead ofy=0
closing parentheses and open braces
ex.
) {instead of){
a comma and the next token
ex.
(foo, bar)instead of(foo,bar)
Pointers and references shall be formatted with the
*or&at the end of the type with no space, and one space between the*or&and the variable name. For example:
// correct
char* str = "Hello World!";
// incorrect
char *str2 = "Foo Bar";
Pointer dereferences and address references shall be formatted with the
*or&directly attached to the identifier name.Be careful to distinguish between these and pointer/reference declarations.
As of 2024-08-25,
astylemay move the*or&of pointer dereferences or address references. This is a known issue and is being worked on. Fix appropriately or surround withastyleguards (see here).
// correct
void foo(int bar, int* ptr);
foo(*bar, &var);
// incorrect
foo(* bar, & var);
Open braces (
{) should be placed on the same line as the end parenthesis of a function or conditional. For example:
void foo() {
...
}
// instead of
void foo()
{
...
}
elseorelse ifshould be placed on the line after the closing brace of the precedingifblock.
if (foo == bar) {
...
}
else if (foo == car) {
...
}
else {
...
}
Leave at least one blank line between function definitions or conditionals. For example:
void foo() {
...
}
// comment
void bar() {
...
}
Avoid
iforelsestatements without braces unless they are separated from other code by at least one line above and below.Do not place the action of an
ifstatement on the same line as the conditional. Instead, use a ternary conditional operator. For example:
// acceptable
if (foo)
bar();
else
car();
// not acceptable
if (foo) bar();
else car();
// acceptable
foo ? bar() : car();
astyle#
astyle, or Artistic Style, is an automatic C++ formatter
Installation#
Windows#
https://astyle.sourceforge.net/install.html#_Windows_Version
Distributes
astyle.exewith download (ex.astyle-3.5.2-x64.zip)Can be ran as-is, but would be best to add to
PATH
Linux#
https://astyle.sourceforge.net/install.html#_Linux_Version
Requires
make,cmake, andgcc.
cd ~/src
cp /mnt/hgfs/share/astyle-3.5.2.tar.bz2 .
tar -xjvf astyle-3.5.2.tar.bz2
cd astyle-3.5.2
Assumed to be run from root of
astylerepo:
mkdir as-gcc-exe
cd as-gcc-exe
cmake ../
make
sudo make install
Usage#
https://astyle.sourceforge.net/astyle.html#_Usage
Generally the easiest way to use a customized version is to add
.astylercto the project root directory (it must be specifically at the project root).Then, running
astyle --projectwill consider the.astylercoption file.
Disabling formatting sectionally#
You can disable parsing of sections of code with a syntax similar to
#defineshttps://astyle.sourceforge.net/astyle.html#_Disable_Formatting
Basically, put line comments with
*INDENT-OFF*then*INDENT-ON*Also: “A line-end comment tag
*NOPAD*will disable thepad-oper,align-pointer, andalign-referenceoptions.”
Running astyle#
Run from project root, for use on files inside
./srcMake sure to escape wildcards – they must not be processed before astyle receives them
If
--recursiveis enabled inside the project file, it does not need to be included in the command call.
Do not run on all files in the repository otherwise it can cause issues.
Linux (or Windows when astyle is in your PATH)#
astyle --project src/\*
Windows (embedded in repository)#
./astyle/astyle.exe --project src/\*
Current .astylerc#
## General
# !!! If enabled, astyle will NOT retain a backup of the original, unformatted file.
# "Do not retain a backup of the original file. The original file is purged after it is formatted."
# ONLY enable this option when you are CERTAIN that the formatting is working properly and to your liking (or you are using version control CAREFULLY).
--suffix=none
# Recursively process a folder.
--recursive
# ! Will remove excess whitespace. Probably don't want to use this often if at all.
#--squeeze-ws
# Maximum length of a single line of code (in characters)
# Lines longer than this will be broken after a logical operator
--max-code-length=125
--break-after-logical
## Braces
# Open braces are placed on the same line as the closing paren of the header/function/conditional.
--style=attach
# Breaks closing headers (e.g. 'else', 'catch', ...) from their immediately preceding closing braces.
--break-closing-braces
# Attach open braces to namespaces, classes, etc.
--attach-namespaces
--attach-classes
--attach-extern-c
# Attach the closing while to the same line of the closing brace in a do-while statement.
--attach-closing-while
# Attach the return type to the function name.
--attach-return-type
--attach-return-type-decl
## Indents
# Use tab indents with 2 spaces
--indent=tab=2
# Converts tabs into spaces in the *non-indentation* part of the line.
--convert-tabs
# Indent the contents of classes and structs
--indent-classes
--indent-namespaces
# Indent, instead of align, continuation lines following lines that contain an opening paren `(` or an assignment `=`. This includes function definitions and declarations and return statements.
--indent-after-parens
# Indent 'case' one indent from 'switch'
--indent-switches
# Indent the contents of 'case' blocks
--indent-cases
# Indent preprocessor blocks
--indent-preproc-block
#--indent-preproc-cond
## Other Whitespace
# Pad empty lines around header blocks (e.g. 'if', 'for', 'while'...).
--break-blocks
# Format pointers and references with the `*` or `&` appended to the type with no space,
# and one space between the `*` or `&` and the variable name.
--align-pointer=type
# not needed if --align-pointer-type is used
#--align-reference=type
# Insert space padding around operators. This will also pad commas.
# Any end of line comments will remain in the original column, if possible.
# Note that there is no option to unpad. Once padded, they stay padded.
--pad-oper
# Insert space padding between a header (e.g. 'if', 'for', 'while'...) and the following paren.
# This can be used with unpad-paren to remove unwanted spaces.
--pad-header
# Remove extra space padding around parens on the inside and outside.
# Only padding that has not been requested by other options will be removed.
--unpad-paren
#--unpad-brackets
# Break one line headers (e.g. 'if', 'while', 'else', ...) from a statement residing on the same line.
--break-one-line-headers