Skip to content

Instantly share code, notes, and snippets.

@neacsum
Last active January 29, 2024 00:54
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save neacsum/2abf84e818cf3fe06fe73a7640bf4703 to your computer and use it in GitHub Desktop.
Coding Syle Rules

Elements of Style

…with apologies to Strunk, White, Plauger, and Kernighan

©Mircea Neacsu 2016-2023

”... programs must be written for people to read, and only incidentally for machines to execute.” — Structure and Interpretation of Computer Programs second edition; Harold Abelson and Gerald Jay Sussman with Julie Sussman, foreword by Alan J. Perlis

These rules will make programs easier to read and easier to understand. They have been designed with an eye on legibility and respect for typographic conventions.

Note: None of these rules are cast in stone; they can always be bent if need arises. Just be prepared to give some good arguments why you didn't follow them ;-) Some of them are softer than others, though.


Table of Contents


Naming

DO NOT use leading underscores on variable names. Those are reserved for compiler, standard library, OS, etc. Use caps judiciously. Constants should be named using all-caps notation and starting with an uppercase letter (not an underscore - e.g. "MY_CONSTANT"), this holds for enum values too.

Whoever uses near, far, min, max, errno or the like as a variable or type name, will be punished and forced to use Pascal for at least four weeks.

Class names should be nouns. Basic classes should use simple nouns, related classes use the name of their base class plus their own name:

class Light;
class DirectedLight;

Methods should use the [<verb>][<adjective>]<noun> convention:

Light::getColor ();
Material::getSpecularColor ();

Get and Set methods for the same attribute may omit the verb:

RGB Light::color ();
void Light::color (RGB val);

No Hungarians Allowed!

DO NOT place any Hungarian prefixes to class name, structures name, or variables names. We no longer live in the times of FORTRAN-66 with default integer variables that start with I,J,K,… and the compiler is perfectly able to keep track of the type of a variable. Moreover, any modern IDE can show you the definition of a name just by hovering over it. If you cannot figure out what a name is, either the name is poorly chosen or you are in the wrong line of business.

DO NOT prefix class names with "namespace"-like qualifiers (T, C, NS, Qt or what not). Some of them (like Qt or NS) come from the days when C++ didn’t have namespaces or didn’t exist at all. Others (like T or C) are just disguised Hungarians.

This rule does not prevent you from using prefixes to indicate what kind of data is represented. For instance, if you deal with apples and oranges and both can be represented by integers, it is OK to have:

int aBasket, oBasket;

if you think that it will make it easier to see that a statement like:

if (aBasket < oBasket)

is comparing apples with oranges1.

Parameter Names and Member Names

DO use an underscore at the end of parameter name to distinguish it from the data member. Sometimes it is convenient to have (almost) the same name for a parameter and a data member of a class:

class Light
{
  //...
  void Color (int color_);
private:
  int color;
}

inline 
void Light::Color (int color_) {color = color_;};

In this case add an underscore to the parameter name. 2

Order of const Qualifier

DO place const, when needed, before the type name:

class Light
{
  //...
  void Source (const LightSource& source);
}

It is “The Constant Gardener” not “The Gardener Constant”. The alternative style:

  void Source (LightSource const & source);

is called East const and has certain advantages but we stick with the traditional style.

Constructors and Non-static Data Member Initialization

DO initialize every non static data member of a class through the mem-initializer-list in the order they appear in the class definition before the function body of the constructor is executed, .

Typecasting

DO use the “C-like” style when there is only a compiler generated conversion and use the function call style when your object has type conversion operator:

unsigned int us = (unsigned int)strlen(str);

If the LightSource class has defined a COLORREF operator:

LightSource source;
unsigned int color = COLORREF(source);

As COLORREF is just another name for an unsigned integer, the function call notation is not required at all (compiler can figure out the required casting) but it can make your code more readable.

Copy Constructor and Copy Assignment Operator

DO declare the copy constructor and copy assignment operator for every class. If they are not wanted, use the = delete specifier and place them at the end of the class definition:

 /// prohibit default functions 
 CLASSNAME(const CLASSNAME &source) = delete;
 void operator =(const CLASSNAME &source) = delete;

Namespaces

DO NOT place using namespace… directives in .h file. You can place them in the .cpp file right after includes.

Inlines

DO NOT put inline method code into the class definition; put it at the end of the .h file instead. You can place a line like this:

/*==================== INLINE FUNCTIONS ===========================*/

before beginning the code for inline functions.

The inline keyword is on a separate line than the function code:

inline
bool Classname::func () {return something;};

Ordering

Members and methods in classes should be in the order public - protected - private, to show people looking at the sources the stuff they can actually use before the internals that are not accessible from the outside. The functions inside the larger sections should be ordered as follows:

  • constructors
  • destructors
  • get/set methods
  • class specific methods (these will be different for every class)
  • class specific operators
  • comparison operators
  • assign operator

Sometimes a short private section is needed before everything else, e.g. for defining some internal types that are used for constants or enums. It's ok to do that, just keep it as short as possible.

The ordering in the .cpp files should be:

  • file comment block (in Qt style)
  • includes
  • full class documentation
  • class member variables documentation
  • class typdef/enum documentation
  • group definition(s) if needed
  • static variables
  • methods in the same order as in the header

Helper Classes

Helper classes containing only publicly accessible elements should be defined as structs.

Example:

class Foo
{
public:

  struct FooHelper
  {
    uint val1_;
    uint val2_;
  };
};

Indents and Formatting

This is the area where most religious struggles happen… This is my style.

Line length is 79 characters. No line should have more than that. This is however a very soft rule: displays can show much more than 79 characters and if you have to trade between a meaningful line break and respecting this rule, choose the meaningful line break.

Indents are 2 spaces; no tabs should be used for indenting as they will always confuse some people. To try to justify the use of tabs by space savings doesn’t make any sense when hard drives have terabytes of space. Besides, Developers Who Use Spaces Make More Money Than Those Who Use Tabs :).

We are not amused to see block opening brackets ({) the K&R way. Block opening brackets should be placed on the next line:

class Foo
{
};

void bar ()
{
  if (fooBar == true)
  {
    ...
  }

  while (fooBar == false)
  {
    ...
  }
}

There is no space between opening parentheses and the next argument, neither between the last argument and the closing parentheses. There are spaces between arguments, though.

a = (1 + 2) * (3 + 4);

There is a space between if and the parenthesis. Opening and closing braces should really be in the same column (ANSI style).

if (5 == (something + 2))
{
  doSomething ();
}

If the if clause consists of a single, simple expression it can be written in the next line without braces.

if (5 == (something + 2))
  something = something * 3 + 2;

This only applies to simple expressions. if is not a simple expression, and it does not apply if there is an else clause; in these cases, use braces:

if (5 == (something + 2))
{
  if (4 == (something + 1))
    doSomethingSimple ();
}
else
{
  doSomethingElse ();
}

Long conditions can be split on separate lines that start with the connecting logical operator3:

if ((5 == (something + 2))
 && (4 == (something + 1)))
{
  ...
}

There should be a space between a ’,’ and the next function argument and a space between function name and opening parenthesis:

void bar (int val1, int val2);
void foo (int val1, int val2)
{
  bar (val1, val2);
}

Functions having no argument should be written as

void foo ();

not

void foo (void);

There should be no space between & or * and the type name:

void foo (int* p, int& r);

The type is "pointer to int" and the variable is p or the type is "reference to int" and the variable is r.

Comparison

Despite a long tradition to the contrary, try writing the equality comparison with the constant first4:

if (5 == something)

The compiler will generate an error if you accidentally type just one equal sign changing the comparison into an assignment. Otherwise you are in for some debugging until you find the typo.

This is again one of those weak rules and no one will be very upset if you break it.

Switch/Case

Case labels are outdented 2 spaced. Leaving an empty line before case labels helps keeping them visible.

If you need local variables, those cases have to be enclosed by {}, but don't do it for every case, it confuses more than it helps.

switch(hugo)
{
case HUGO0:    
  doSomethingCool ();
  break;

case HUGO1:    
  {
    UInt32 tempint = getTempInt ();
    doSomethingCooler (tempint);
  }
  break;

case HUGO2:    
  veryShort ();  
  break;

case HUGO3:    
  veryShort2 (); 
  break;

default:       
  nothingCoolToDo ();
  break;
}

For very long class/method names line breaks need to be used judiciously…

typedef MyClass::HelperClass HelperClass;

switch(hugo)
{
case HelperClass::HUGO0:
  {
    doSomethingVeryCoolWithUnsuspectingClass (
     getUnsuspectingClass ());
  }
  break;

case HelperClass::HUGO1:
  {
    UnsuspectingClass& victim = getUnsuspectingClass ();
    suspectClassInstance->makeInconspicious (victim,
                                             HelperClass::NOW);
  }
  break;
 
default:
  break;
}

Clearly mark fall-through cases:

switch (action)
{
case LONG_ACTION:
  do_some_stuff ();
  // FALL THROUGH TO NEXT CASE DO NOT SEPARATE!!

case SHORT_ACTION:
  do_more ();
  break;
}

Commenting/Documentation Rules

We use Doxygen for all code commenting. Use the Qt comment style (/*!) for full comment blocks and C# comment style (///) for brief comments. Comment as much as needed, but keep the headers clean.

General Principles

Documenting comments should show the usage information. This is the bare minimum users of the code should know in order to be able to use it. Internal comments should show mostly the why information. As the saying goes, think that the next person reading your code is a serial killer with an ax: you don't want to get him angry. The how information, the mechanics of the implementation, should be easy to grasp from reading the code.

Here is an example illustrating these principles.

In the .h file

/// Evaluate a polynomyal
double poly (std::vector<double> coeff, double x);

The header file is clean and barely informs you that this function exists.

In .cpp file:

/*!
  \param coeff polynomial coefficients starting with lowest degree (constant term)
  \param x evaluation point
  \return value of polynomial

  Computes coeff[0] + coeff[1]*x + ... + coeff[n] * x^n
  \note The coefficients' vector cannot be empty.
*/

This block will be extracted by Doxygen and included in documentation.

double poly (std::vector<double> coeff, double x)
{
  int n = coeff.size();
  assert (n > 0);

Below is an internal comment showing what we plan to archive and why.

  // Use Horner scheme to cut down on number of multiplications
  double p = coeff[--n];
  while (n >= 0)
  {
    p = p*x + coeff[--n];
  }
  return p;
}

Would be of little benefit to further comment the code. The reader has been advised what we plan to do and can just read the code to see how it's done.

Classes

  • Only the brief comment should be in the header
  • The full comment should be in the source code
  • Add classes to their respective group(s). Groups are an important aid for useful structuring, use them.

Example: In .h file:

/// Nodes form the backbone of a tree
class  Node : public AttachmentContainer
{

In .cpp file:

/*!
  \class     Node
  \ingroup   theTreeStuff

  Detailed description for node class here.
*/

Parts

The public, protected and private parts can be marked with a line like this:

/*========================  PUBLIC  =============================*/

before the keyword.

In general comments like this can be used to differentiate different parts, for instance get/set methods, data members etc.

Methods

Method shouldn't be documented in the header, good method and parameter names should speak for themselves. Instead use Doxygen \name to group the methods.

Example:

/*------------------------------------------------------------*/
/*! \name              Constructors                           */
/*! \{                                                        */

    BoxVolume(void);

/*! \}                                                        */

Put detailed documentation in the .cpp file.

Comment every method/function to explain what it does and what's special about it.

If the method is reimplemented from another class, only the original class's version should have documentation (Doxygen will take care of repeating the documentation form the original class) unless something has changed in the behavior for this implementation. If you want to expand the original class's documentation, you can use \copydoc to get the original documentation.

For pure virtual methods you can use an explicit \fn section in the .cpp, if it exists.

Please use the \param tag to give a quick explanation of the parameter's use. They should mention the legal ranges/values of the parameter and their unit.

If sensible, reference the documentation pages for general concepts from the functions/methods in a see also (\sa) section.

Use the \return tag to give details of the return value for the method.

If sensible, do associate free-standing functions with classes they're related to (\relates).

You have to give warnings (\warning) for non-intuitive results or side effects.

Do use \warning to talk about unintuitive side-effects or constraints of a method. Use \post commands to mention non-obvious postconditions (i.e. effects on objects other than the one a method is called on and the parameters).

A Style Guide

The following are useful tips and conventions for writing descriptions in doc comments.

Omit parentheses for the general form of methods and constructors

When referring to a method or constructor that has multiple forms, and you mean to refer to a specific form, use parentheses and argument types. For example, ArrayList has two add methods: add(Object) and add(int, Object).

The add (int, Object) method adds an item at a specified position in this arraylist.

However, if referring to both forms of the method, omit the parentheses altogether. It is misleading to include empty parentheses, because that would imply a particular form of the method. The intent here is to distinguish the general method from any of its particular forms. Include the word "method" to distinguish it as a method and not a field:

The add method enables you to insert items. (preferred)

The add() method enables you to insert items. (avoid when you mean "all forms" of the add method)

Use phrases instead of complete sentences, in the interests of brevity

This holds especially in the initial summary and in \param tag descriptions.

Use 3rd person (descriptive) not 2nd person (prescriptive)

The description is in 3rd person declarative rather than 2nd person imperative.

Gets the label. (preferred)

Get the label. (avoid)

Method descriptions begin with a verb phrase

A method implements an operation, so it usually starts with a verb phrase:

Gets the label of this button. (preferred)

This method gets the label of this button. (avoid)

Class/interface/field descriptions can omit the subject and simply state the object

These API often describe things rather than actions or behaviors:

A button label. (preferred)

This field is a button label. (avoid)

Use "this" instead of "the" when referring to an object created from the current class

For example, the description of the getToolkit method should read as follows:

Gets the toolkit for this component. (preferred)

Gets the toolkit for the component. (avoid)

Add description beyond the API name

The best API names are "self-documenting", meaning they tell you basically what the API does. If the doc comment merely repeats the API name in sentence form, it is not providing more information. For example, if method description uses only the words that appear in the method name, then it is adding nothing at all to what you could infer. The ideal comment goes beyond those words and should always reward you with some bit of information that was not immediately obvious from the API name.

Avoid - The description below says nothing beyond what you know from reading the method name. The words "set", "tool", "tip", and "text" are simply repeated in a sentence.

/*!
 * Sets the tool tip text.
 *
 * \param text  the text of the tool tip
 */
  void setToolTipText(String text)
  {

Preferred - This description more completely defines what a tool tip is, in the larger context of registering and being displayed in response to the cursor.

/*!
 * Registers the text to display in a tool tip. The text
 * displays when the cursor lingers over the component.
 *
 * \param text  the string to display. If the text is null,
 *              the tool tip is turned off for this component.
 */
  void setToolTipText(String text) 
  {

Avoid Latin

Use "also known as" instead of "aka", use "that is" or "to be specific" instead of "i.e.", use "for example" instead of "e.g.", and use "in other words" or "namely" instead of "viz."

Required Tags

A \param tag is "required" (by convention) for every parameter, even when the description is obvious. The \return tag is required for every method that returns something other than void, even if it is redundant with the method description. (Whenever possible, find something non-redundant (ideally, more specific) to use for the tag comment.)

Additional guidelines for tag comments

\param

The \param tag is followed by the name (not data type) of the parameter, followed by a description of the parameter. By convention, the first noun in the description is the data type of the parameter. (Articles like "a", "an", and "the" can precede the noun.) An exception is made for the primitive int, where the data type is usually omitted. Additional spaces can be inserted between the name and description so that the descriptions line up in a block. Dashes or other punctuation should not be inserted before the description.

Parameter names are lowercase by convention. The description begins with a lowercase letter if it is a phrase (contains no verb), or an uppercase letter if it is a sentence. End the phrase with a period only if another phrase or sentence follows it.

Example:

\param ch        the character to be tested
\param observer  the image observer to be notified

When writing the comments themselves, in general, start with a phrase and follow it with sentences if they are needed. When writing a phrase, do not capitalize and do not end with a period:

 \param x  the x-coordinate, measured in pixels

When writing a phrase followed by a sentence, do not capitalize the phrase, but end it with a period to distinguish it from the start of the next sentence:

 \param x  the x-coordinate. Measured in pixels.

If you prefer starting with a sentence, capitalize it and end it with a period:

 \param x  Specifies the x-coordinate, measured in pixels.

When writing multiple sentences, follow normal sentence rules:

 \param x  Specifies the x-coordinate. Measured in pixels.
\return

Omit \return for methods that return void and for constructors; include it for all other methods, even if its content is entirely redundant with the method description. Having an explicit \return tag makes it easier for someone to find the return value quickly.

Whenever possible, supply return values for special cases (such as specifying the value returned when an out-of-bounds argument is supplied).

Use the same capitalization and punctuation as you used in \param.

Footnotes

1See also Making Wrong Code Look Wrong by Joel Spolsky

2Rationale: the parameter has function scope so the “uglier” name with underscore has shorter lifespan.

3Rationale: it makes easier to spot the logical operator as opposed to placing it at the end of the line.

4This technique is also known as Yoda conditions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment