Coding Guidelines

From Testbit Wiki
Jump to: navigation, search

Coding Guidelines

Herein we're addressing some guidelines for making direction and design decisions during project development.

Coding Style

This section provides a list of rules that should be followed when writing code to become part of Beast or Rapicorn. The development of a consistent coding style is work in progress, so these rules may not be comprehensive. Also not all existing code might follow these rules, in these cases it's usually a candidate for cleanups for which patches are happily accepted.

However, new code should generally follow each of these rules and if no explicit rules exists, try to look at existing practice or ask, when in doubt.


Names and Identifiers

Names and identifiers should ideally be self explaining. Good terminology is very important for readable code, so it pays off to invest some thinking time into picking good variable names. This is even more important for functions or class names that become part of a public interface and need to maintain backwards compatibility.

Identifiers

Functions, methods, members, arguments and variables are written in lower case, using an underscore as word separator. Example: lower_case_underscore_names

  • Rationale: Without special training, many people recognize lower case words with separator space better than CamelCase words.

Type Names

Class names, type definition and aliases use "camel case", where words are spelled with one leading upper case letter and placed adjacently. Example: GtkScrolledWindow

  • Rationale: While less readable, using CamelCase for type names allows easy differentiation between type names and identifiers. Type names generally occour less often than identifier names which mitigates the readability impact. Also used in GTK+ coding style.

Constants

Constants, enum value names and macros are spelled with underscored in all upper case. Example: BSE_ERROR_FILE_NOT_FOUND.

  • Rationale: This is done for easy differentiation from type names and especially assignable identifiers. Also used in GTK+ coding style.


Indentation and Formatting

The following describes several styles of indentation and formatting to improve code readability and navigation.

Newlines around Function Bodies

Function implementations spanning multiple lines should use a newline preceeding the function name. This generally pushes function return type, visibility keywords or template args ont their own line. Similarly, the function body should be enclosed by curly braces on their own lines. Exceptions are empty function bodies where both curly braces should go on the same line. Example:

template<class T> bool			// newline before function name
frobify_two_things (const T &thing1, const T &thing2, bool repeat)
{					// single line for opening curly brace
  // function body
}					// single line for closing curly brace
void
dummy_callback ()
{}					// empty function body
  • Rationale: Function names are easily recognized as they start a line. Also used in GNU Coding Style.

Multiline Comments

There are generally two kinds of multi-line comments: documentation comments and non-documentation comments. Documentation comments should start with "/**" on a seperate line, while ordinary comments should not start with an extra empty line.

/**
 * This function checks whether the universe still exists; it is unclear however how
 * a caller could call it if the universe ceased, so you probably won't need it.
 */
bool
does_the_universe_still_exist()
{
  /* We can assume that if the program still runs, then the universe can
   * not possibly be gone.
   */
  return true;
}
  • Rationale: Documentation comments need to be parsable by Doxygen, ordinary comments don't need to waste space.

Declaring Pointers and References

In variable declarations, operators for references and pointers should be declared next to the identifier. This matches the C++ parsing rules which will only attribute the operator to the first identifier. Arguments in function declaration should follow he same style for consistency. Example:

int *pointer1, a_number, *pointer2;
void function (int *ptr1, Object  &ref);
  • Rationale: Putting the operator adjacent to the type might lead to misinterpretations in declarations of multiple variables.

C++ References

Output arguments for functions that are not object types are passed by pointer, not by reference. This may be contrary to other common C++ uses, but is consistent with how output arguments must be passed in plain C.

bool
parse_xyz (const std::string &input,
           float             *x,
           float             *y,
           float             *z)
{
  *x = *y = *z = 0;
  // parse input, assign x, y, z
}
  • Rationale: Generally using pointers for output arguments is meant to improve the readability of the code (output arguments are clearly labeled in function calls) in a mixed C/C++ code base.

Declare Data Members First

In class definitions, data mambers should be moved to the start of the class definition where possible. Usually, data members should be private which is achieved by placing these at the top of a class definition before opening a public: section. Example:

class NodeLink {
  NodeLink *m_prev, *m_next;
public:
  void prepend (NodeLink *other);
  void append  (NodeLink *other);
};
  • Rationale: Grouping the data members at the start of a class definition aids understanding by emphasizing the data structure aspects of the class. It also correlates syntactically with declaring class members as private.

Data Members are prefixed

Data members declared in class definitions should generally be prefixed with m_. Example:

class IntArray {
  int         m_initial_size;
  vector<int> m_integers;
public:
  IntArray (uint n_integers) :
    m_initial_size (n_integers),
    m_integers (n_integers)
  {}
}
  • Rationale: The prefixing is meant to distinguish class members from local variables, arguments or function names. It also helps to prevent ambiguations for constructor arguments which are often named similarly to some data members.

Constructor Initializer Indentation

Initializer calls in constructor definitions should go on their own lines, the colon following the constructor function declaration should be followed by a newline. Example:

struct Foo : public Parent
{
  int  m_x, m_y;
  Foo (int x, int y) :
    Parent (x * y),
    m_x (x), m_y (y)
  {}
};
  • Rationale: This notation is our preference for readability.