Introduction
Coding style is important. A clean, consistent style leads to code that is more readable and maintainable. We strive to write elegant code that will not only perform its desired function today, but will also live on, to be re-used and improved by other developers for many years to come.
C++
We try to follow the C++ Core Guidelines. Moreover consider these preferences:
-
indent style: we prefer the Allman style (aka ANSI-style or BSD style)
- two spaces indentation (if you don't want to over-work your thumbs hitting the space bar, by all means use the tab key, but please, set your editor to replace tabs with spaces)
- against overall consensus braces are omitted for simple one-statement blocks: it's more succinct, more readable, more visually appealing and easier to type
-
raw pointers: every object passed as a raw pointer is assumed to be owned by the caller, so that its lifetime is handled by the caller. Viewed another way: ownership transferring APIs are relatively rare compared to pointer-passing APIs, so the default is "no ownership transfer"
-
unsigned: C++ standard libraries use
unsigned
and so we useunsigned
(see also Andrew Koenig's "The Case Against int", Dr Dobb's Feb/Mar 2012).Types are much about communication: by using explicitly an
unsigned
you tell that signed values are not valid values. This adds some information when reading the code in addition to the variable name.I'm not sure if I'm alone here, but I find it surprisingly rare that I need signed integers when developing applications. Almost all of the time what I need is either an (unsigned) natural number (a positive size, usually), or a signed floating-point number. Exceptions are things like currency, but those are very rare; to me, unsigned integers are the norm and signed integers are the exception!
-
naming
- type names are all lowercase, with underscores between words
- function names are all lowercase, with underscores between words
-
const goes before the type
-
typedef: we prefer the new
using
keyword for type alias since it's a superset oftypedef
and, being much more similar to regular variable assignment, it improves readability -
parameter names in function declaration: don't use any name in the declaration, unless it adds info, as you don't need to.
Also, please consider that there are situations in which we prefer public data members. Widespread opinion is that public data members are evil, because if you have to change the way the data is stored in your class you have to break the code accessing it. On the other hand code that uses:
x = obj.data_get();
...
obj.data_set(x);
is uglier, longer to write and more difficult to read than
x = obj.data;
...
obj.data = x;
Furthermore, chances that, in future, the implementation will need to be changed are in general rather small: probably less than 10% of classes with data elements will really need to be modified in the future by putting code in the place of raw data members. To avoid problems with that 10%, the current widespread practice is to use getters and setters.
As Jerry Coffin says, when/if we run into situations in which the public variable must be changed (e.g. calculated or obtained from some database) there is a simple solution: turn the member variable into a class of its own. Provide that class with conversion operators to the original type, and voilà the code gets executed as necessary. E.g.:
struct whatever { int x; };
turns into:
class X
{
public:
operator int() {/* Access database/whatever to determine value */}
X operator=(int val) {/* Set value in database/whatever*/}
};
struct whatever { X x; };
(also take a look at If a variable has getter and setter, should it be public?)
Ultra header policy
Header files are the place where a library comes into contact with user code and other libraries. To co-exist peacefully and productively, headers must be "good neighbors".
Here are the standards for Ultra headers. Many of these are also reasonable guidelines for general use.
- Header filenames should have a .h (lowercase) extension.
- Unless multiple inclusion is intended, wrap the header in
#if !defined
guards. Use a naming convention that minimizes the chance of clashes with macro names from other's code. The sample header uses the Ultra convention of all uppercase letters, with the header name prefixed by the project name and suffixed with H, separated by underscores. - Wrap the header contents in a
namespace
to prevent global namespace pollution. - The preferred ordering for class definitions is public members, protected members and finally private members.
Sample header:
/**
* \file
* \remark This file is part of ULTRA.
*
* \copyright Copyright (C) 20xx-20xx EOS di Manlio Morini.
*
* \license
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/
*/
#if !defined(ULTRA_FURBALL_H)
#define ULTRA_FURBALL_H
#include "kernel/common.h"
namespace ultra
{
///
/// ...
///
class furball
{
public: /* ... */
protected: /* ... */
private: /* ... */
}; // class furball
} // namespace ultra
#endif // include guard
Template source code is similarly organized:
/* ... */
#if !defined(ULTRA_FOO_H)
#define ULTRA_FOO_H
/* ... */
namespace ultra
{
///
/// ...
///
template<T>
class foo
{
/* ... */
}; // class foo
#include "kernel/foo.tcc"
} // namespace ultra
#endif // include guard
Note the final include directive (the .tcc
file contains the implementation of templates declared in a header).
Checking class invariants
Classes should specify their invariants: what is true before and after executing any public method.
is_valid()
is the member function used to check the consistency of an object.
A common pattern to implement invariants in classes is for the constructor of the class / non const method to ensure the invariant is preserved:
class A
{
public:
A(/* arguments */)
{
// ...build the object...
Ensures(is_valid());
};
int do_stuff(/* ... */)
{
/* ... */
Ensures(is_valid()); // some invariant could have been modified
}
[[nodiscard]] bool is_valid() const;
};
Since methods preserve the invariants, functions can assume the validity of the invariant and need not explicitly check for it:
int A::do_stuff(B &b, int v)
{
Expects(b.is_valid()); // REMOVE. Redundant check
Expects(v > 0); // REQUIRED
// ...embarrassing code...
Ensures(b.is_valid()); // REMOVE. Redundant check
Ensures(v > 0); // REQUIRED
Ensures(is_valid()); // REQUIRED. `do_stuff` could have modified some invariant of A
return v;
}
Doxygen comments
- Prefer comment blocks starting with an additional slash (
///
). A/** ... */
comment block doesn't nest, which is quite annoying when for testing purpose you wish to comment out a whole block of code (it isn't a issue for boilerplate). - Use Markdown-formatted comment block.
- The first sentence of each comment should be a summary sentence, containing a concise but complete description of the item (member, class, interface or package description).
- Use 3rd person (descriptive: "gets the label") not 2nd person (prescriptive / imperative: "get the label").
- Avoid
\author
,\date
and\version
tag (they're handled by the revision control system). - The description of a parameter (
\param
) begins with a lowercase letter and do not end with a period. - Some operations on a class, particularly arithmetic operators, must be implemented as standalone functions even though they are conceptually part of the class. These functions should have the
\related
tag, followed by the name of the appropriate class.
///
/// A one sentence description of the function's purpose.
///
/// \param[in] arg0 a description of parameter 0
/// \param[in] arg1 a description of parameter 1
/// \param[in,out] param a description of parameter 2. This description is
/// particularly long so we do our best to align it and
/// make it readable
/// \return the meaning of the value returned
///
/// A detailed description. Sometimes, it might be nice to include a simple
/// one or two line code snippet if the method is complex.
///
int func(int arg0, int arg1, int *param)
{
/* ... */
}
References
- C++ Core Guidelines
- Design by Contract
- Design by Contract Programming in C++
- JSF C++ Coding Style: a very interesting guide. It's more for embedded use, but it shows how things should be done to be clear and functional; anyway you don't have to take anything into account: it's a guide, not an order book
Python
Style Guide for Python Code (PEP8) and Docstring conventions (PEP257).
General recommendations
Before all use common sense and BE CONSISTENT.
If you are editing code, take a few minutes to look at the code around you and determine its style. If they use spaces around their if
clauses, you should, too. If their comments have little boxes of stars around them, make your comments have little boxes of stars around them too.
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. If 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.
OK, enough writing about writing code; the code itself is much more interesting. Have fun!
References
Miscellaneous
- Please don't put your name in the code you contribute. Our policy is to keep contributors' names in the project site, not scattered throughout the codebase itself.