Code Style Guide

From Arx Libertatis Wiki
Jump to navigation Jump to search

This page describes the style used for Arx Libertatis source code.

The goal of this guide is to make the code and commits as readable as possible.
If you find missing rules or examples where this guide reduces readability, we are happy to discuss them.

New contributions should follow this guide.

Examples

Example Files

src/somedir/SomeClass.h:

/*
 * Copyright notice(s) ...
 */

#ifndef ARX_SOMEDIR_SOMECLASS_H
#define ARX_SOMEDIR_SOMECLASS_H

namespace somens {

//! Short documentation comment
void someFunction();

class SomeClass {
	
	int m_memberVariable;
	long m_secondMemberVariable;
	
	static int s_staticMemberVariable;
	
public:
	
	/*!
	 * Longer documentation comment
	 *
	 * \param functionParameter Some parameter...
	 *
	 * \return some value.
	 */
	int memberFunction(int functionParameter);
	
};

extern int g_globalVariable;

} // namespace somens

#endif // ARX_SOMEDIR_SOMECLASS_H

src/somedir/SomeClass.cpp:

#include "somedir/SomeClass.h"

namespace somens {

int g_globalVariable = 0;

int SomeClass::s_staticMemberVariable = 0;

int SomeClass::memberFunction(int functionParameter) {
	
	int localVariable = functionParameter;
	
	someFunction();
	
	return localVariable + m_memberVariable;
}

} // namespace somens

Example fragments

if(foo) {
	bar = 1;
}
if(foo) {
	a = "foo";
	i++;
} else if(bar) {
	a = "bar";
	j++;
} else {
	a = "else";
	k++;
}
switch(bar) {
	case 0: {
		++bar;
		break;
	}
	case 1: {
		--bar;
		break;
	}
	default: {
		bar += bar;
		break;
	}
}
switch(bar) {
	case 0:  ++bar;      break;
	case 1:  --bar;      break;
	default: bar += bar; break;
}
for(int i = 0; i < 10; i++) {
	// do stuff
}
while(condition) {
	// ...
}
do {
	// ...
} while(condition);
try {
	number = boost::lexical_cast<long>(string);
} catch(...) {
	number = -1;
}
class Point {
 	
	double x;
	double y;
 	
public:
 	
	Point(double x, double y)
		: x(x)
		, y(y)
	{
		// ...
	}
};

Symbol Names

Symbol names use variants of CamelCase.

Classes

Classes and structs use camel case names and start with an upper case letter:

struct SomeType;

TODO: what about typedefs?

Functions

Function names are also camel case, but start with a lowercase letter:

void someFunction();

Member functions

Same as Functions

Rationale: Member functions are also just functions - there is no need for a different visual style.

Variables

Variable names should describe what the variable is used for, not it's type. Particularly, hungarian notation ("pVariable", "iVar") and variations "varPtr" should not be used.

Local variables

Local (function-scope) variables use camel case names with a lower-case start:

int localVariable = 0;

Function parameters

Same as Local variables

Rationale: Function parameters are local variables, so they should follow the same naming scheme.

Member variables

Member variables should be named just like local variables, but with an added m_ prefix:

class SomeClass {
	int m_someVariable;
};

The prefix makes it easy to distinguish local variables from those with a longer lifetime.

Static member variables

Static member variables should be named just like local variables, but with an added s_ prefix:

class SomeClass {
	static int s_someVariable;
};

The prefix makes it easy to distinguish local variables from those with a longer lifetime.

Static function-scope variables

Should be avoided in hot code paths.

Rationale: Due to Thread Safety they can be surprisingly expensive.

No special formatting rules are necessary.

Global variables

... should be avoided if possible! But for the cases where they are needed, they should be named just like local variables, but with an added g_ prefix:

int g_someVariable = 0;

Rationale: The prefix makes it easy to distinguish local variables from those with a longer lifetime.

Namespaces

Namespace names should be lowercase and as short as possible.

If the namespace block contains more than a few lines, consider repeating the namespace keyword and name at the end:

namespace ns {

// ...

} // namespace ns

Rationale: Namespaces are often closed without the start being visible and are not indented, the closing comment can help to associate a "stray" closing bracket.

Low-level symbols

Low-level classes and their member functions and variables (container, path, ...) use C++-stdlib-style lowercase names with underscores.

Rationale: This is so that they visually fit in with other lower-level symbols from the C++ stdlib and Boost.

Formatting

Line length

Try to keep the line length below 120 characters, indentation with tabs counts as 4 characters.
This limit is not mandatory if a line break reduces readability.
Do not compromise the quality of symbol names just to stay below this limit.

Rationale: Shorter line lengths improve reading speed, this soft limit is a compromise.

Indentation

Use a single tab character for each indentation level.

Rationale: Users can set the indentation width according to their needs or preferences.

Indent "empty" lines according to the indentation of adjacent lines.

Rationale: Users with enabled display of white space characters can easily see the start or end of blocks.

if(...) {
	
	// ...
	
}

Alignment

Always uses spaces for alignment, never tabs:

if(...) {
	
	function(arg, arg, arg,
	         arg, arg);
	
}

Line breaks

Other whitespace

#includes

Syntax

For system and library includes, use angle brackets:

#include <header>

For our own includes use quotes and include the full path below the src directory:

#include "subsystem/File.h"

Order

  1. First include should be the header corresponding to the current source file
  2. Then come standard C/C++ includes
  3. After that, other system includes (<unistd>, etc.)
  4. Next, library includes
  5. Finally, includes for our own header files

There should be an empty line between each of these groups of includes. More empty lines may be added to aid readability (but if that is needed, consider splitting the source file to reduce the number of includes).

Within each group, #includes should be sorted semi-lexicographically: Within each path hierarchy, entries are sorted lexicographically, except that all files always come before subdirectories.

Example (subsystem/File.cpp):

#include "subsystem/File.h"

#include <string>
#include <vector>

#include <boost/foreach.hpp>

#include "a/Header.h"
#include "a/another/Header.h"
#include "b/Header.h"

Documentation

For doxygen comments use either

//! …

or

/*! … */

variants.

Comments at the end of a line should use

//!< …

to signify what the comment belongs to.

All other doxygen comment variants should be avoided.

Use '\' for commands, not '@':

/*!
 * \brief Something that does something
 *
 * \param name the one and only parameter.
 *
 * \return a value.
 */

Other notes

Literals

Floating point literals use lower case suffixes without 0 in the fractional part

float foo = 1.f

variable initialization

When initializing fundamental types (e.g. int, float, size_t) use copy initialization.

static const int foo = 5;

casts

For fundamental types prefer functional style casts

double foo = 10.3;
int bar = int(foo);

Otherwise use c++ style casts

char * bar = static_cast<char *>(foo);

Avoid using c-style casts!

char * bar = (char *)foo;

iterators

Always prefer pre-increment (++i) over post-increment (i++) for non-builtin types.

using namespace statements

... should be avoided. Use namespace aliases instead:

namespace a = b::c::d;

However, try to use a meaningful alias. If possible, consistent aliases should be used in different files for the same source namespace.

other using statements

... are also usually best avoided. There are of course exceptions (for example to invoke ADR with std::swap), but leaving the namespace prefix where the symbol is used makes it easier to tell where the symbol comes from.

#include guards

The preprocessor macros used for include guards should follow the directory structure below the src directory prefixed with ARX_ and suffixed with _H

... copyright header here

#ifndef ARX_SUBSYSTEM_FILENAMEINUPPERCASE_H
#define ARX_SUBSYSTEM_FILENAMEINUPPERCASE_H

... header code here

#endif // ARX_SUBSYSTEM_FILENAMEINUPPERCASE_H

Commit messages

Example

Prevent bad things from happening

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla lorem
lacus, malesuada sed nulla ut, mollis dignissim mauris. Donec in leo
dictum, sollicitudin elit vitae, dignissim nulla. Morbi consectetur
magna turpis, eu sodales tortor blandit vitae.

Fixes: bug #42

Title

The first line of the commit message should be a short description of what the commit does. It should use the imperative tense (it's generally shorter), should not end in any punctuation, and the first word should be capitalized (unless it's a code reference).

It can optionally be preceded by the component is modifies, followed by a colon and space ": " and/or by the tool used to find the bug (enclosed in array brackets: "[tool] …).

If the change in the commit cannot be summarized in a short pseudo-sentence it is a good indicator that the commit should be split up.

Good commit titles:

Frobnicate the knobs
FooBar: Frobnicate the knobs
[foo] Frobnicate the knobs

Non-comformant commit titles:

frobnicate the knobs
Frobnicate the knobs.
Frobnicates the knobs
Frobnicated the knobs
This commit does this and and it also does this

Body

The commit message body can contain any free-form text but should try to keep lines shorter than 80 characters.

Tags

Optionally, commit messages can be annotated with one more tag. Tags should come after the message body (separated by an empty line). Each tag should begin at the start of its own line.

Bugs

Commits fixing a bug should be have the following tag at the end:

Fixes: bug #42

"bug" can be replaced with "issue" or "crash report".

Feature/enhancement requests should use:

Implements: enhancement #42

"enhancement" can be replaced by "feature request".

If the commit only partially fixes a bug, mention that in the commit message and use this tag instead:

See: bug #42

Using these tags is not mandatory but makes the commit corresponding to a bug easier to find.

Commit references

To reference other commits either mention "commit $hash" in the message body or use:

See: commit $hash

You can use the full commit hash or any prefix that is at least 5 (preferably 7) characters long.

Sign-off

Use of the Signed-off-by tag (git commit --signoff) is not required but not forbidden either.