-
Notifications
You must be signed in to change notification settings - Fork 11
Programming Practices
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.
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.
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.
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.
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 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 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;
};
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 };
};
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++) {;}
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;
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;
}
}
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, []
, 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 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 );
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; }
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
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) { /* ... */ }
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;
};
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) {;}
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 ()
.
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
.
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.
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.
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.
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.
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.
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.
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.
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.
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.