Documentation for Release 2024.2

Coding Standards Strategy

C++ Naming Conventions

Coding standards are crucial to enable humans understand the code – Both code they wrote, and code written by other developers. This page documents naming conventions we adhere to in Chaste. If you are writing Chaste code, please follow them!

Choosing names

Names are the key to program readability. If the name is appropriate everything fits together naturally, relationships are clear, meaning is derivable, and reasoning from common human expectations works as expected. Good names save time when debugging and save time when extending.

“Good” naming includes not using names which might be ambiguous in a particular situation:

// Does "complex" mean "complicated" 
// or "involving imaginary numbers"?
Problem complex_problem;

// Is this the solution of an electrical current 
// or the solution to the voltage/whatever *now*?
Vec mCurrentSolution; 

// Dangerous in situations where several objects are 
// indexed or there are global/local indices.
unsigned index;  

If you find all your names could be Thing and DoIt then you should probably revisit your design. Avoid the temptation to have short names everywhere, and avoid non-standard abbreviations.

Source code is meant to be read by humans. This is the most important thing to remember. As well as communicating your intent to the machine, you must make it clear what that intent is to those who will read the source code. This includes you! Code you’ve written more than about 3 weeks ago may as well have been written by somebody else.

A cryptic example:

Dog d;
Lion l;
l.dvr(d);

A more descriptive version:

Dog my_pet_dog;
Lion escaped_lion;
escaped_lion.Devour(my_pet_dog);

Class Names

Name the class after what it is. If you can’t think of what it is that is a clue you have not thought through the design well enough.

  • Use upper case letters as word separators, lower case for the rest of a word
  • First character in a name must be upper case
  • No underscores (_) are permitted
class OdeSolver
class ParameterFile

Method and Function Names

Usually every method and function performs an action, so the name should make clear what it does: CheckForErrors() instead of ErrorCheck(), DumpDataToFile() instead of DataFile(). This will also make functions and data objects more distinguishable. Each method/function should begin with a verb.

  • Classes are often nouns. By making function names verbs and following other naming conventions programs can be read more naturally.

  • Suffixes are sometimes useful:

    • Max - to mean the maximum value something can have.
    • Count - the current count of a running count variable.
    • Key - key value.

    For example: RetryMax to mean the maximum number of retries, RetryCount to mean the current retry count.

  • Prefixes are sometimes useful:

    • Is/Has - to ask a question about something. Whenever someone sees Is or Has they will know it’s a question.
    • Get - get a value.
    • Set - set a value.

    For example:

    if (HasHitRetryLimit())
    {
      // try something else
    }
  • Use the same naming rules as for class names:

    class OdeSolver
    {
    public:
      int SolveEquation();
      void HandleError();
    }

No All Upper Case Abbreviations

When confronted with a situation where you could use an all upper case abbreviation instead use an initial upper case letter followed by all lower case letters. No matter what.

Take for example

NetworkABCKey

Notice how the C from ABC and K from key are confused.

class FluidOz;       // NOT FluidOZ
class NetworkAbcKey; // NOT NetworkABCKey

Pointer Variables

Pointers should be prepended by a p in most cases. Place the * close to the pointer type rather than the variable name. Only one pointer type should be declared per line (with no non-pointer types) in order to avoid confusion. We generally only declare one variable per line.

Car my_car;
Car* p_your_car = new Car;

// NOT:
Car *p_your_car;

// AND NOT:
Car* p_your_car, p_my_car;
// since only p_your_car is a pointer here. 
// We declare only one pointer type at a time.

Class Attribute Names

  • Private attribute names should be prepended with the underscore character m.
  • After the m use the same rules as for class names.
  • m always precedes other name modifiers like p for pointer.
class CleaningDepartment
{
public:
  int ComputeErrorNumber();
private:
  int mCleanHouse;
  int mErrorNumber;
  String* mpName;
}

Reference Variables and Functions Returning References

References should be prepended with r. This applies to input arguments as well as method names, and establishes the difference between a method returning by value and a method returning by reference.

class Test
{
public:
  void TestConveyorStart(StatusInfo& rStatus);

  // returns by reference so requires the `r` prefix
  StatusInfo& rGetStatus();

  // returns by value so doesn't have the `r` prefix
  StatusInfo GetStatus();

private:
  StatusInfo& mrStatus;
}

Method Argument Names

The first character should be lower case. All word beginnings after the first letter should be upper case as with class names.

class WackyRace
{
public:
  int StartYourEngines(Engine& rSomeEngine, Engine anotherEngine);
}

Variable Names on the Stack

When variables are created in a C++ program (when the variables are in scope) the memory required to hold the variable is allocated from the program stack, and when the variable goes out of scope, the memory which was taken on the stack is freed. When memory is allocated dynamically (by the programmer – using the new keyword), or if variables are declared as class attributes memory is taken from the heap.

  • Use all lower case letters
  • Use _ as the word separator.

With this approach the scope of the variable is clear in the code. And now all variables look different and are identifiable in the code.

int ProcessMonitor::HandleError(int errorNumber)
{
  int error = OsErr();
  Time time_of_error;
  ErrorProcessor error_processor;
}

Global Constants

Global constants should be all caps with _ separators.

const double TWO_PI = 6.28318531;

Static Variables

Static variables should be prepended with s.

private:
  static StatusInfo msStatus;

Code Layout

You want to provide as much information as possible to the next person who reads the code – even if that person is you. Consistency, in the form of conventions, gives lots of extra information and makes the code more maintainable.

Consistent Brace Style

Braces should start and end on a new line, with 4 space indentation for the code block. This is easiest to read, makes matching braces easier to locate, and avoids problems with no-brace if statements.

A potentially buggy example:

if (valueToTest == comparison)
    DoTheGoodStuff();

C++ allows if statements without braces as above. This can lead to hard to spot bugs when adding another statement:

if (valueToTest == comparison)
    DoTheGoodStuff();
    someValue = 1.5;  //this will always be executed

If you always use braces, there is one less bug to find:

if (valueToTest == comparison)
{
    DoTheGoodStuff();
}
else 
{
    DoSomethingElse();
}

Comments

Comments describe the intent of the programmer, document tricky sections of the program, and provide a way of adding metadata e.g. physical units.

The aim should be to describe, rather than duplicate what is happening in the code.

A comment that isn’t very useful:

double voltage = 0.0; // Initalize voltage to zero

A more helpful comment:

double voltage = 0.0; // Transmembrane potential (mV)

Comments can also be used to lay out a skeleton for code you are about to write. This helps to solidify the algorithm in your mind and reduces the amount of commentary you need to add afterwards.

// Initialise stiffness matrix

  // Add boundary conditions

// Assemble linear system

  // Set initial conditions in Solution vector

// Solve linear system 

Comments are documentation. Doxygen generates documentation (HTML / PDF / XML) from specially formatted comments in the code. This helps to keep the documentation in sync with the code without having to write the same thing twice.

/** Computes the volume of a standard cone.
 *
 *  @param baseRadius The radius of the cone base (cm)
 *  @param height The height of the cone (cm)
 *
 *  @return The volume of the cone (cm^3)
 */
double ComputeConeVolume(double baseRadius, double height)
{
    ....
}

See the Code Structure Strategy for more on Doxygen documentation.

Usage of Language

C++ lets you be very very clever…

Don’t be very very clever.

Don’t do things like this:

struct X {
   static bool f( int* p )
   {
      return p && 0[p] and not p[1]>>p[2];
   };
};

Be as clear as you can, even if it takes more lines of code.