Programming Guidelines

The following conventions and guidelines are designed to help you, the developer, write better code. The number of conventions are small and the reasoning for each is clear. For examples of good coding style, we strongly suggest you study other components within our library. To conclude, the conventions presented here are not final. This document should be seen as the basis for an evolving standard. We would welcome your suggestions for improving it.

General Guidelines


General Guidelines

Write Clearly

Don't be overly clever. Just because you can do something in a single line of code doesn't mean you should. Do not write code just for the compiler; write the code for you and your fellow programmers. Remember it may be you who returns to this code days, months, or years later and can't remember what that complex statement does. Never sacrifice clear code for "efficient" code.

Remember: Clearly written code is 'self' commenting, there should be no need for blocks of comments to describe what is going on. If that isn't the case, consider using more descriptive variable names, and breaking the code up into more distinct modules.
top

Write Modular Code

Code should be broken down into smaller pieces in order to make testing easier and to facilitate re-use of code. Functions that span several pages of printed text are hard to follow, harder to debug and are less likely to be reusable in alternative applications. As a general rule, a function should not span more than 2 pages (or 100 lines). Furthermore, two or more functions can be adapted by others for a wider range of uses than one single function.
top

Break Complex Equations into Smaller Pieces

Complex equations containing many operators should be broken down into smaller pieces, with suitably name local variables to describe the individual portions of the code. This not only makes the code more understandable (self documented), but it is also easier to analyse the values of specific parts of the equation during debugging.

If only locally defined variables are used then writing code in this way is NOT less efficient - the code will run just as fast. However, it is important to constrain the scope of these local variables, which is achieved when you code in a modular fashion. To further aid the compiler you can encapsulate the locally used variables with { }.

// poor
double num=(A * 2 * cos(w * t)) * sin(k * x) * cosh(k * d) + 2 * B * sin(k * x - w * t);

// better
...
double num;
{
  double Amp_A  = A * 2 * cos(w * t);
  double Wave_A = Amp_A * sin(k * x) * cosh(k * d); 
  doule Amp_B  = B * 2;
  Wave_B = Amp_B * sin(k * x - w * t);
  num    = Wave_A + Wave_B
}
...

top

Overloading Functions

Overloading functions can be a powerful tool for creating a family of related functions that only differ in the type of data provided as arguments. If not used properly (such as using functions with the same name for different purposes) they can, however, cause considerable confusion. When overloading functions all variations should have the same semantics (be used for the same purpose).
top

Make it Work, THEN Make it Fast

Often during development, developers want to get the most "bang" for their money. If you write code that works, and the interface is clear and correct, you can always go back later and make it faster. Strive to make it correct and readable first and fast second. Fast wrong code is still wrong code.
top

Parenthesis

It is generally a good idea to use parentheses liberally in expressions involving mixed operators to avoid operator precedence problems. Even if the operator precedence seems clear to you, it might not be to others - you should not assume that other programmers know precedence as well as you do.

if(a == b && c == d)      // avoid
if((a == b) && (c == d))  // good

x >= 0 ? x : -x;			    // avoid
(x >= 0) ? x : -x;			  // good

top

Memory de-allocation

If you know that a pointer variable is no longer in use unallocate it and set the pointer to null.
top

Exception Handling

How to handle exceptions depends a lot on the application (console, windows application, etc) and a complete treatment is beyond the scope for this document. However, the basic guide line for all exception handling is:

A function that finds a problem it cannot cope with should throw or re-throw an exception, hoping that it's (direct or indirect) caller can handle the problem. A function that wants to handle that kind of problem should indicate that it is willing to catch that exception.
top

Error Catching - assert and verify

An assertion statement specifies a condition that you expect to hold true at some particular point in your program. If that condition does not hold true, the assertion fails, execution of your program is interrupted, and the Assertion Failed dialog box appears.

The key feature of the assert statement is that it is only included in 'Debug' executable code, with these statements being automatically removed for Release code. As such, assert only slows down Debug executables, but has no ill effect (either speed or size) on the Release code.

Through the liberal use of assertions in your code, you can catch many errors during development. A good rule is to write an assertion for every assumption you make. If you assume that an argument is not NULL for example, use an assertion statement to check for that assumption. In addition, the readability of code can be greatly improved through their use, as the reader can easily see what these assumptions are.

#include 

void add(double** data, int N)
{
  assert(data!=NULL);
  assert(N<MAXSIZE);
  ...
}

Because assert expressions are not evaluated in the 'Release' version of your program, you need to make sure that assertions does not have side effects.

#include <assert.h>
// Never do this
assert(nM++>0);    
// At this point, nM will have different values 
// in the debug and release versions of the code.

// Better
assert(nM>0);
NM++;

// UnSafe
assert(myFunc()>0);      // myFunc() might have side effects.

In MFC, there is a VERIFY macro that can be used instead of ASSERT. VERIFY evaluates the expression but does not check the result in the Release version, therefore there is now an overhead related to the evaluation of the expression. We feel VERIFY adds confusion to the code, so we strongly discourage its use. Expressions intended to be in the release code should always be evaluated as a part of regular code.
top

Goto Statements - Don't Use!

Use Goto sparingly, since they break the structured flow of the program and can make code difficult to follow. Two harmless places to use goto's are when breaking out of multilevel loops, or to jump to a common 'error catching' location. For example:

for(...)
{
  while(...)
  {
    ...
    if(disaster) 
      goto error;
  }
}
...


//*** ERROR CATCHING ****************************
//*** Fixing the mess after disaster happened ***
error: 
  ...
// *********************

Its advisable to accompany any goto label with eye catching header, that details its purpose.
top

Templates

Template are a powerful feature of C++ that allow a function to take on many different forms during compilation. We strongly encouraged their usage, since they can allow functions to become significantly more flexible, while remaining type safe.

In instances where you need to define several overloaded functions to deal with different types of data, even though the body of the functions are identical, you should consider using a template. Although #defines can be used to achieve the same results templates are infinitely more powerful and are far easier to debug.

The following example illustrates the implementation of a max function

// Poor examples
#define MAX(a,b) ((a)>(b)?(a):(b))     // This isn't type safe

// Multiple overloaded functions are hard to modify consistently
int max(int a, int b)
{
  if(a>b) return a;
  return b;
}

double max(double a, double b)
{
  if(a>b) return a;
  return b;
}

// Good example
template<class t>
T max(T a, T b)
{
  if(a>b) return a;
  return b;
}

top

Standard Template Libraries (STL)

The Standard Template Library establishes uniform standards for the application of data containers, and a small selection of algorithms. Iterators are provided to traverse the data containers, and all the algorithms are fully compatible with each container.

The STL template container classes include:

The STL algorithms includes methods for:

STLs are in all modern implementations of C++. They are extremely well written, well optimised and totally portable. They come highly recommended – to the point that you should rarely consider writing you own methods for managing these types of data.

#include <vector>

void main()
{
  using namespace std;
  vector<int> a(2);   // initial size of vector
  a.push_back(10);
  a.push_back(20);
  a.push_back(25);    // causes 'a' to dynamically increase in size;

  // this code is very fast, but is not portable to other container types.
  int lsize=a.size();           // local variable to increase speed
  for(int i=0;i<lsize;i++)
  {
    printf("\n value[%d]=%d",i,a[i]); 
  }

  // this code can be used on other container types, such as a que, list etc.
  for(vector <int>::iterator ii=a.begin(); ii!=a.end(); ii++)
  {
    printf("\n value=%d", *ii); 
  }
}

top

Variables

In the use of variables in a program the following guidelines should be followed:

 

// avoid - massObj creation may not be necessary
void doSomething(const int x)
{
  SomeMassiveObject massObj = new SomeMassiveObject();

  if(x < 5)
  {
	 massObj.run();
  }
}

// good - massObj is only created when necessary
void doSomething(const int x)
{
  if(x < 5)
  {
	  SomeMassiveObject massObj = new SomeMassiveObject();
	  massObj.run();
  }
}

The importance of declaring variables when they are needed cannot stressed enough. It represents one of the most significant failings of many programmers. In our opinion the code is not only harder to follow and debug, but there are also many well documents performance overheads associated with declaring all variables at the top of a function. Therefore NEVER declare a variable until it is needed.

In our opinion, debugging this type of code is considerably easier as the scope (impact) of the variable is very clearly defined. The exception to this arises with dynamically created objects, or even with an array that is dynamically allocated. The 'new' operator not only consumes a lot of time allocating space for the object, but it also calls the constructor for each object it initialised. This should therefore be done as infrequently as possible.

//good use of local variables for simple data types.
for(int i=0;i<10;i++)
{
  for(int j=0;j<10;j++)  
  { 
    double a=sin(PI*i)*cos(PI*j)
    doSomething(a); }
  }
}

// bad - this is potentially less efficient
int i,j;
double a;
for(i=0;i<10;i++)
{
  for(j=0;j<10;j++)  
  { 
    a=sin(PI*I)*cos(PI*j)
    doSomething(a); }
  }
}

top

Public Member Class Variables

A public variable represents a violation of one of the basic principles of object-oriented programming, namely, encapsulation of data. For example, if there is a class of the type BankAccount, in which accountBalance is a public variable; any user of the class may change the value of this variable. However, if the variable has been declared protected, it's value may be changed only by the member functions of the class (e.g. setAccount() and getAccount()). This prevents some other arbitrary function changing data; which may lead to errors that are difficult to locate.

If public data is avoided, it's internal representation may be changed without users of the class having to modify their code. This is a core principle of class design, where the existence of a public interface for the class allows the internal implementation of a class to be changed without affecting the users.

Conversely, for classes or structs that act as containers for a collection of data variables, without providing significant additional functionality (beyond perhaps initialisation, copying and destruction), it is simply ludicrous to provide a setX() and getX() for each member variable. Therefore, when designing a class great care should be taken to ascertain its purpose and intended use.
top

Functions

Functions having long lists of arguments that look complicated and are difficult to read. In general, if a function has more than five parameters then perhaps it should be redesigned. One alternative is to package the parameters into a class or structure that is then passed to the function.

Try to keep functions short. One compelling reason is that if an error situation is discovered at the end of an extremely long function, it may be difficult for the function to clean up after itself before reporting the error to the calling function. By always using short functions such an error can be more exactly localized.
top

Constant Correctness

In the declaration of input variables for a function, a const keyword should precede the type definition of all variables that are not changed during the execution of the function.

It is usual to pass basic type variable (such as int, double etc) by value. In this instance, the use of the const keyword is merely to aid readability – variables defined as const will never change in the function.

For large variables/objects it is common to pass these by either reference or by pointer. Through the use of a const keyword, variables/objects that are not changed by the function can be specifically declared within the function definition. The compiler will always ensure adherence to this rule, so anyone using a function can instally see the potential for a variable to be changed. This also simplifies debugging.

Note, although a standard ‘object reference’ can be converted into a ‘const object reference’, this conversion cannot be performed the other way around. However, as you might expect, a function that takes an object by value (it makes its own local copy), will accept both const reference and nonconst variables.
double setZ(const Coordinate &point); 
double setY(Coordinate &point);
double setX(Coordinate point);

double init(const int x, const Coordinate &A, Coordinate &B)
{
  int a=x;      //ok
  x++;          //illegal

  point.m_x=x;  //illegal,  where m_x is a member variable of Coordinate

  SetX(A);  //ok
  SetY(A);  //illegal 
  SetZ(A);  //ok

  SetX(B);  //ok
  SetY(B);  //ok
  SetZ(B);  //ok
}

Within a class, the const keyword can also be used to designate member functions that do not alter other members of that class. This has the same distinct benefits for debugging as those discussed above, and also assist readability. Note that for obvious reasons, a const member function cannot call a nonconst member function.

class A
{
  int m_size;

  int getSize()            // bad - doesn't alter any member functions, so should be const
  { return m_size; }

  
  int getTheSize() const   // good
  { return m_size; }

  int doSomething() const  
  {
    return m_size++;       // illegal
  }
  
  int add() const
  {
    return getSize()+1;    // illegal, getSize() isn't const, so theoretically my alter class
  }
};

top

Index