Skip to content

Programming Practices

Scott Shillcock edited this page Aug 27, 2010 · 1 revision

Introduction

About This Guide

This guide is divided into three sections. The first section, C++ Programming Style, covers the formatting style used when coding for the platform. The second section, C++ Programming Conventions, covers coding conventions specific to the C++ language. The third section, Platform Programming Conventions, covers coding practices specific to the platform.

Architectural Goals

The platform was created to provide a clean and modular code base for the development of interactive applications. The architecture was designed to minimize interdependencies and maximize code reuse. Performance must be balanced against design and reusability to ensure a clean and usable code base. The platform was designed to be very dynamic such that code that is not needed is not loaded. This dynamism is extended to the data used during runtime. Things like enumerations and data hierarchies are configured at runtime and not hardwired into the code.

Document Conventions

The following conventions are used in this document:

Italics Used for filenames, directory names, and URLs.

Constant Width

Used for code examples, system output, and command lines.

C++ Programming Style

Simple Source Code Formatting

To ensure that source code can be viewed on the widest range of text editors, a few simple guidelines are necessary.

Never use tabs for formatting. The amount of space a tab generates tends to vary from text editor to text editor and thus code that looks to be properly indented in one editor may look horribly mangled in another.

Each line of code should be no more than 90 characters wide. Not only does this improve readability, it also helps insure that formatting will be consistent regardless what text editor is used. The only exception to this rule is string literals which may span more than 90 characters.

Class and Namespace Name Formatting

All class and namespace names should use camel case. Use lowerCamelCase for a name is in the global scope. Use UpperCamelCase for a name defined in a namespace or class.

// Correctly formatted class names

namespace dmz {

   class TokenParser;
   class Position;
   class MunitionFactory;
};

class fooClass;
namespace fooSpace;

// Incorrectly formatted class names

class DMZ_Token_Parser;
class dmztokenparser;
class dmz_muntionFactory;

namespace FOOSpace;
class FooClass;

Function Name Formatting

Function names should use delimiter separated formatting. The delimiter should be the underscore character (i.e. _) and all letters should be lowercase. Public class member functions should start with the operation the function will be performing (e.g. get, put, set, store, etc. See Appendix A for details regarding proper operation names and definitions).

// Correctly named public member functions

class data {

   public:
      // ...
      dmz::String get_name ();
      dmz::Int32 get_count ();
};

// Incorrectly name public member functions

class data {

   public:
      // ...
      dmz::String GetTag ();
      dmz::Int32 Get_Data_Count ();
};

Protected member functions follow the same guidelines as the public member functions, with the exception that the name should start with the underscore character (i.e. _).

// Correctly named protected member functions

class data {

   protected:
      // ...
      data *_resolve_scope ();
      void _compact_table ();
};

// Incorrectly named protected member functions

class data {

   protected:
      // ...
      data *ResolvedScope_ ();
      void _compactTable ();
};

Private member functions follow the same guidelines as the public member functions, with the exception that the name should start with two underscore characters (i.e. __).

// Correctly named private member functions

class data {

   private:
      // ...
      void __invert_data ();
      dmz::Int32 __get_data_count ();
};

Non-member functions names, also know as 'C' style functions use the same naming as public member functions. If the function is in the global scope, the name should be prefixed with the word global. If the function is only visible with in the scope of the file then the function name should be prefixed with the word local.

Factory function names should be prefixed by create_ followed by the class name. the class name should be in CamelCase. If the class is defined in a name space, the class name should be prefixed with the namespace name.

// Correctly named factory functions

dmz::Plugin *create_dmzObjectModule ();
dmz::Plugin *create_dmzRenderModuleIsectOSG ();

// Incorrectly name factory functions

dmz::Plugin *CreateDmzObjectModule ();
dmz::Plugin *create_dmz_object_module ();

Variable Name Formatting

Variable names should be chosen carefully. The name should describe how the variable will be used. Try and avoid single letter variable names especially letters like i and l as they can easily be confused for numbers. When defining variables in for loops, use the variable name count, or if the loop is nested, use two letter variable names like ix, jy, and kz.

Non-const variables should use lowerCamelCase. Const variables should use UpperCamelCase. You should not use the underscore character _ between words in a variable name. See member variable naming for valid use of the underscore character.

When a variable is global to the scope of the file (i.e. a global static), it should start with the prefix local if it is non-const and Local if it is const.

You should never have a variable in the global scope and thus no naming convention is provided.

// Correctly named variables

static const dmz::Int32 LocalCount = 0;
static dmz::String localName;

void
foo () {

   dmz::Float32 timeStamp = 0.0f;
   const dmz::String VarName ("name");

   char array[9][9];

   for (dmz::Int32 ix = 0; ix < 8; ix++) {

      for (dmz::Int32 jy = 0; jy < 8; jy++) {

         array[ix][jy] = 0;
      }
   }
}

// Incorrectly named variables

static const dmz::Int32 aCount = 0;
static dmz::String Name;

void
foo () {

   dmz::Float32 time_stamp = 0.0f;
   const dmz::String varName ("name");

   char The_Array[9][9];

   for (dmz::Int32 i = 0; i < 8; i++) {

      for (dmz::Int32 l = 0; 1 < 8; l++) {

         The_Array[i][l] = 0;
      }
   }
}

Member variables follow the same naming convention as described for other variables with the exception that protected member variables must be prefixed with the underscore (i.e. _) and private member variables must be prefixed with two underscores (i.e. __).

// Correctly named member variables

class Container {

   public:
      static const dmz::Int32 Sides;
      // ...
      const dmz::String ContainerName;
            dmz::Float32 publicVarible;

        protected:
           static const dmz::Float23 _StartTime;
           // ...
           const dmz::Boolean _Init;
           dmz::Int32 _loopCount;

        private:
            static const dmz::Float32 __EndTime;
            // ...
            const dmz::String __ParentName;
            dmz::Float32 __finalTick;
};

// Incorrectly named member variables

class Container {

   public:
      static const dmz::Int32 sides;
      // ...
      const dmz::String container_Name;
           dmz::Float32 publicvarible_;

        protected:
           static const dmz::Float32 Start_Time;
           // ...
           const dmz::Boolean init;
           dmz::Int32 Loop_Count;

        private:
            static const dmz::Float32 end_time__;
            // ...
            const dmz::String parentName;
            dmz::Float32 finalTick;
};

Enumerations

Enumeration identifiers should use the same naming convention as class. The last word used in the identifier should be Enum. The underscore character _ should not be used. The enumerator should be named in the same fashion as constant variables where the first letter in each word is in uppercase. If the enum declaration will not fit on one line, the enum keyword and identifier should be on one line, followed by each enumerator on its own line.

// Correctly formatted enums

enum DataTypeEnum { Binary, RawText, ZippedText };

class foo {

   public:

      enum TypeEnum {
              Data,
              Comment,
              ExtraInfo,
              MaxTypeCount
           };
};

// Incorrectly formatted enums

enum _DATA_TYPE { binary, raw_text, zippedText };

class Foo {

   public:

      enum TYPE { data, comment, extra_info,
         MAX_TYPE_COUNT };
     
};

Semicolon

The semicolon, ;, should never have any space between it and the line it is terminating.

// Correctly formatted semicolons

dmz::Int32 bigNumber = dmz::Number::Big;

dmz::Float32 smallFloat = dmz::Number::SmallFloat;

// Incorrectly formatted semicolons

dmz::Int32 bigNumber = dmz::Number::Big  ;

dmz::Float32 smallFloat = dmz::Number::SmallFloat
;

When the semicolon is used in a for loop, there should be no space between the semicolon and the argument it is terminating and a space between the semicolon and the start of the next argument.

// Correctly formatted semicolons in a for loop
for (dmz::Int32 ix = 0; ix < 10; ix++) {;}

// Incorrectly formatted semicolons in a for loop

for (dmz::Int32 ix = 0 ; ix < 10 ; ix++) {;}
for (dmz::Int32 jy = 0 ;jy < 10 ;jy++) {;}

Scope Operator

The scope operator, ::, should never have space on either unless it is specifying the global scope .

// Correctly formatted scope operators

foo::TypeEnum type = foo::Data;
dmz::Int32 sides = container::Sides;
dmz::Float64 = ::timeGetTime ();

// Incorrectly formatted scope operators

Foo :: typeEnum type = Foo:: Data;
dmz ::Int32 sides = Container :: Sides;

Indentation

Standard indentation is three spaces. The code block should be indented within its enclosing curly brackets {}. Thus a code block enclosed in three sets of curly brackets should be indented nine spaces. Tab characters should never be used in formatting source code. Six-space indentation is used in function parameter lists, member variable initialization, and if/while/for expressions that require more than one line. The six spaces indentation rule is used to help visually separate expressions and function parameter lists from the code that follows.

// Correctly indented code

void
_foo_function (
      // These function parameters are indented six
      // spaces
      const dmz::Int32 StartTime,
      const dmz::Int32 Endtime,
      dmz::Float32 &delta) {

   // The first line of code in the function is
   // indented three spaces

   const dmz::Int32 Diff = Endtime - StartTime;

   if (Diff > 2) {

      // This is indented six space because it is
      // encapsulated by two sets of curly brackets
      delta = dmz::getTime ();
   }
   else {

      if (Diff < 0) {

         delta = -dmz::getTime ();
      }
      else {

         delta = 2.0f;
      }

      out << "delta set to: " << delta
         << dmz::endl;
   }
};

// member variables initialization in a class
// constructor are indented six space
Foo::Foo (const dmz::Int32 InitValue):
      _initValue (InitValue),
      _name ("Default"),
      _deltaTime (0.0f),
      _lastTime (0.0f),
      _StartTime (dmz::get_time ()) {

   // Code is only indented three spaces
   internalinit ();

   while (_initValue < MaxValue) {

      _initValue += 10;
   }
}

Curly Brackets

The opening curly bracket, {, should be on the same line as the expression that necessitates enclosing the code block in curly brackets. There must be a space between the opening curly bracket and the end of the expression. If the code block fits on one line, the closing curly bracket, }, can be on the same line as the opening curly bracket. There must be a space between the end of the code block and the closing curly bracket. If the code block is multiple lines long, the closing curly bracket should be on its own line and have the same amount of indentation as the expression that started the code block (e.g. if, while, for, etc.). There should be an additional carriage return between the opening curly bracket and the start of the indented code block.

// Correct use of curly brackets

if (foo != 0) { foo = 0; }
else {

   foo = _get_count ();
   foo = foo / MaxCount;
}

do {

   out << "Waiting" << dmz::endl;

   dmz::sleep (1.0f);

} while (dmz::get_time () < nextTime);

for (dmz::Int32 ix = 0; ix < MaxCount; ix++) {

   if ((ix == RealyLongVaribleName) &&
         (loopCount > 10) &&
         (smallCounter != 1)) {

      out << "FOO!" << dmz::endl;
   }
}


// Incorrect use of curly brackets

if (foo != 0)
{
   foo = 0;
}
else
   {
   foo = _getCount ();
   foo = foo / MaxCount;
   }

do
{  out << "Waiting" << dmz::endl;
   dmz::sleep (1.0f);}
while (dmz::getTime () < nextTime);

for (dmz::Int32 ix = 0; ix < MaxCount; ix++){
   if ((ix == RealyLongVaribleName) &&
         (loopCount > 10) &&
         (smallCounter != 1))
   {

      out << "FOO!" << dmz::endl;
}}

Square Brackets

Square brackets, [], should always have a space following the closing square bracket, ], unless it is followed by a semicolon, an opening square bracket, [, or a closing parenthesis, ), in which case there should be no trailing space.

// Correct square bracket formatting

dmz::Int32 intArray[10];
dmz::Float64 floatArray[10][20];

for ( /* ... */ ) {

   intArray[ix] = ix;
}

if (boolArray[ix]) {;}

// Incorrect square bracket formatting

dmz::Int32 intArray [10] ;


for ( /* ... */ ) {

   intArray[ ix ]= ix;
}

if (boolArray[ix] ) {;}

Parenthesis (Round Brackets)

Parenthesis should have space on the convex side and no space the concave side. If more than one parenthesis of the same type is together, there should be no space between them. If a closing parenthesis, ), is followed by a semicolon, there should be no space between them

// Correct parenthesis formatting

if ((a == b) && (c == d)) {;}

while (!foo && (goo != 0)) {;}

dmz::Int32 value = func (x, y, z);

// Incorrect parenthesis formatting

if( ( a == b )&&( c == d ) ){;}

while ( !foo && ( goo != 0 ) ) {;}

dmz::Int32 value = func( x, y, z );

Operators

Unary operators (e.g. ++, --, !, &, *) should contain no space between themselves and the variable they are operating on and one space on the opposite side unless followed by a semicolon.

// Correctly formatted unary operators

foo = ++ix;

goo = -ix;

hoo = ix--;

float *floatPtr = &floatValue;

dmz::Boolean value = !otherValue;

// Incorrectly formatted unary operators

foo = ++ ix;

goo = - ix;

hoo = ix-- ;

float * floatPtr =& floatValue;

dmz::Boolean value = ! otherValue;

Operators (e.g. +, -, *, /, %, =, &&, ||, &, |, <, >, <=, >=, ==, etc.) should contain space on both sides.

// Correctly formatted operators

foo = goo + hoo;

goo = foo * hoo;

if (foo && goo) { hoo = foo - goo; }

// Incorrectly formatted operators

foo=goo+hoo;

goo= foo*hoo;

if (foo&&goo) { hoo =foo-goo; }

C++ Programming Conventions

Header File Layout

Every header file should enclose its content with ifndef, define, endif macros to prevent multiple inclusion of the header file. The defined symbol should be the file name in all capitols with each word separated by the underscore character and have _DOT_H appended. For example the file dmzRenderModule.h would have the defined symbol name of DMZ_RENDER_MODULE_DOT_H. The header file should be laid out in the following order:

  • ifndef/define filename macro symbols
  • Headerfiles (in alphabetical order)
  • Forward class declarations (in alphabetical order)
  • Class definitions
  • Inline functions
  • Function prototypes
  • endif macro

Only the ifndef/define/endif macros are required. The remaining header file can be composed of any number of the other parts. For example a header file might only have function prototypes defined while another may only have class definitions and inline functions defined.

The layout of a header file named "dmzExampleClass.h" would be as follows:

#ifndef DMZ_EXAMPLE_CLASS_DOT_H
#define DMZ_EXAMPLE_CLASS_DOT_H

// Headerfiles
#include "dmzExampleLocalDefs.h"
#include <dmzPosition.h>
#include <dmzZed.h>
#include <stdio.h>

// Forward class declarations
class configData;

// Class definitions
class DMZ_EXAMPLE_CLASS_EXPORT_SYMBOL exampleClass {

   public:
      // ...
      void print ();
      // ...
};


// inline functions
inline void
exampleClass::print () {

   out << "Print!" << dmz::endl;
}


// Function prototypes
DMZ_EXAMPLE_CLASS_EXPORT_SYMBOL void
global_example_function (configData &data);


#endif // DMZ_EXAMPLE_CLASS_DOT_H

Source File Layout

The source file should be laid out in the following order:

  • Headerfiles (in alphabetical order)
  • Static local functions
  • Static local variables
  • Static member variables
  • Class member functions
  • Exported functions

Example header file layout

// Headerfiles
#include <dmzExampleClass.h>
#include "dmzExampleClassLocal.h"
#include <dmzConfig.h>
#include <dmzTypesBase.h>

// static local functions
static void
local_work_func () { /* ... */ }

// static local variables
static const dmz::String LocalName ("The Name");

// Static member variables
const dmz::String exampleClass::Name ("ExampleClass");

// Class member functions
exampleClass::exampleClass () { /* ... */ }

// Exported Functions
DMZ_EXAMPLE_CLASS_EXPORT_SYMBOL void
global_example_function (Namespace &ns) { /* ... */ }

Class Definition

A class should have only one of each type of access control (e.g. public, protected, private). All public members must be declared first. Protected members must be declared after public members. Private members must be declared last. Class members should be laid out in the following order:

  • Enums
  • Internal classes and typedefs
  • Static member functions
  • Static member variables
  • Constructors
  • Destructors
  • Inherited virtual functions
  • Class specific member functions
  • Member variables

Example class definition

// Class definition example
class exampleClass : public baseExampleClass {

   public:
      enum ExampleEnum {

         First,
         Second,
         Third,
         Forth,
         Last
      };

      class linkClass {

         // ...
      };

      typedef dmz::Boolean flag;

      static void init ();

      static const float32 InitTime;

      exampleClass ();
      exampleClass (configData &ns);

      virtual ~exampleClass ();

      virtual void print_base_example ();

      void print_example ();

   protected:
      virtual void _do_inheritedFunc ();

      void _work_func ();

      dmz::Float32 _start_time;

      flag _isOn;

   private:
      flag __secret;
      dmz::String __className;
};

Constructor Member Variable Initialization

Member variables that are initialized in the class constructor must be declared in the same order in which they appear in the class definition. Base constructors of inherited types that require initialization parameters should be initialized before member variables and should appear in the same order as they appear in the class definition.

// Example of Constructor initialization of member variables
class fileExampleClass : public exampleClass {

   public:
      fileExampleClass (configData &ns);
      // ...

   protected:
      dmz::String _fileName;
      dmz::Int32 _fileCount;
      dmz::Boolean _errorState;
      dmz::Float64 _lastParseTime;
};

FileExampleClass (configData &ns) :
      ExampleClass (ns),
      _fileName ("default.txt"),
      _fileCount (0),
      _errorState (0),
      _lastParseTime (0.0f) {;}

Function Names and Multiple Inheritance

When designing a mix in class, care must be taken when choosing the function names. For example, if two mix in classes have a pure virtual member function named get_handle (), they will not be able to be combined into a derived class. Instead choose names that are more descriptive such as get_sound_handle () and get_entity_handle ().

Function Arguments and Results

Functions arguments should be ordered such that variables with values that will be used in the function should come first. Variables that are passed in as either references or pointers so that their value may be changed should follow. Finally, variables that have default values should always be last. A variable should only be used for its value or have its value set but not both.

// function argument layout examples
void
example_func (
   // Variables to be used
   const dmz::Int32 Value,
   const dmz::String &Name,
   // Variables to have their value set
   dmz::String &newName,
   // Variables with default values
   const dmz::Boolean Default = dmz::False);   

Every function should contain at most one return. That return should be the last thing executed in the function. Multiple returns can cause confusion over function flow and can often introduce bugs when code is not executed due to a premature return. If the function has a return type, the type of the variable returned should match. The returned value should always be name result.

Copy Constructor and Assignment Operator

Every class should have the copy constructor and assignment operator declared in the class definition. If the copy constructor and assignment operator are not to be used they should be declared as private and no function body should be provided.

Size of Function Body

Functions should never be overly large. If a function is over 60 lines in length, try and re-evaluate the function and determine if the function can be broken down into small parts. It is not always possible to keep functions short. If you have a function you feel is excessively long and are unsure how to proceed, consult with a senior software engineer.

Avoid Cut and Pasting Code

When coding, avoid cutting and pasting code from else where. If another portion of code contains functionality need in a piece of code being developed, break the common code out into a reusable class or function.

The C Preprocessor

Do not use the C preprocessor to define constants or inline functions. The const and inline keywords are safer because they are done at compile time.

Loops: for vs. while

Use for loops when the number of iterations in the loop is known before the start of the loop execution or the loop is expect to complete regardless of the results of each iteration. Use while loops when the number of iterations is determined during the execution of the loop.

Things to Avoid

Never use break except in case statements. Breaking in a loop can cause confusion and problems much like multiple returns.

Never use goto. C++ isn't BASIC.

Never create a for loop with no arguments. (i.e. for (;;)). That sort of construct is just asking for an infinite loop.

Avoid the use of void pointers. If you really think you need to use a void pointer, discuss it first with a senior software engineer.

Avoid returning pointers to member variables.

Avoid declaring friend classes. If a class needs access to the protected member variables of another class you should probably rethink your design, as it is probably flawed in some form.

Platform Programming Conventions

What is a Plugin? What is a Module? What is an Extension?

Dynamically loaded code is divided into three conceptual types in the platform: Plugins, Modules, and Extensions. While all three are code that is dynamically loaded at runtime, each one fulfills a different role. A Module exports one or more interfaces that other parts of the system may discover and manipulate. A Plugin does not export an interface. Its primary role is to discover module interfaces of interest and to manipulate them in a meaningful way. Since a module interface should be designed to encapsulate implementation details, it is not possible to do fine grained manipulation of a module via the exported interface. Extensions are loaded by a Module or Plugin so that it may have access to the internal details of the Module or Plugin. Extensions tend to be designed for a particular implementation and are usually not compatible between two Modules that support the same interface but are internally different.

Standard C++ Library & STL Usage

The use of the Standard Template Library (STL) and Standard C++ Library are not permitted in the platform kernel. Their use out side of the platform kernel is at the developer's discretion.

Appendix A – Function Operation Definitions

The following is a list of the most common accepted function operation verbs. The list should not be taken as complete.

  • append – Add data to the end of a list.
  • create – Create an instance of a class.
  • destroy – Destroy an instance of a class.
  • get – Get value of class attribute.
  • get_first – Get the first item in a list.
  • get_last – Get the last item in a list.
  • get_next – Get the next item in a list.
  • get_prev – Get the previous item in a list.
  • insert – Insert item into a list.
  • lookup – Look up item associated with a given key.
  • release – Release and remove an item from a list or table but do not delete it.
  • remove – Remove an item from a list or table and delete it.
  • reset – Resets attribute or state to the default value.
  • set – Set the value of a class attribute.
  • store – Store an item associated with a given key.