Programming Style Guide


Naming

Statements

  1. Assignments within expressions are forbidden.
    1. Inadmissable: while ((c = getchar()) != EOF)
      n = a[++i];
      o = p = q = 0;
  1. The goto statement is not allowed.

User Defined Types

General Regulations

  1. User defined types should be used whenever possible..
    1. typedef int PRIORITY;
      typedef char ERRORCODE;
      typedef enum { SC_BELL = '\a', SC_TAB = '\t'} SPECIAL_CHARACTER;

  2. If pointers to instances of user defined types are needed, a pointer type is to declare by using typedef.
  3. Each declaration of a class or important struct type is directly followed by a pointer type declaration to the class or struct type. The name of this pointer type is indentical to the class name, but starts with a P instead of a C.

      class CComplex;
      typedef CComplex* PComplex; // mixed case despite typedef!

Klassendeklarationen

  1. Data elements must be declared private.
  2. (C++) Though obsolete, all polymorphic functions in derived classes are to be declared virtual.
  3. (C++) inline function members are inadmissable, because they publish the implementation they should hide. Exception: private classes.
  4. (C++) Polymorphic inline functions are not allowed. They are ignored by the compiler anyway.
  5. Function elements that do not change the instances state are declared const (usually selectors).

Data Objects

General Regulations

  1. (C, C++) Variables are declared as close as possible to the place, where they are used for the first time.
  2. (C, C++) A variable declaration without initialization is inadmissable.
  3. Exceptions:

    (Pascal, Delphi) Global data objects to be initialized when they are declared. Local data objects are initialized before any other statement is executed:

      function Maximum(const theQuantities: CQuantityList): double;
      var i: integer;
      begin

          result := 0.0;  // the return value comes first
          i := 0;         // then the local variables

          Statements;
      end {Maximum};

  4. Standard macroes are declared in several standard headers. User defined macroes must be avoided, if possible.
  5. Public Variables (extern) are inadmissable. A class/module memory is accessed by access functions. Public variables may be used by exception for efficiency reasons. If public variables are used, they have to be declared in a decleration file as extern. Private global variables and private functions are declared static.
  6. Constants are, to make them type safe, always declared as data objects and not created by using #define.
  7. Constants (particularly strings) are to be placed in the application's resource.
  8. Data objects, in particular intermediate results, should be declared const.
  9. Function local static objects are inadmissable. They have to be declared outside of the function as private global data objects.
  10. Bit fields are inadmissable. Using bit fields is allowed by exception for hardware level programming or if a foreign library demands their usage.

Functions

Parameters

  1. A function must use preconditions to check, that all input parameters are valid.
  2. A function must use postconditions to check, that the return value (and, if used, all output parameters) are valid.
  3. Functions should get exclusivly input parameters declared as const.
  4. Input parameters of a struct or class type should always be declared as references (const &).
  5. Functions using control parameters should be avoided. The idiom
    1. int ReadNumberOfErrors(void);
      int ReadNumberOfSuccesses(void);
      int ReadNumberOfEvents(void);

    is to prefer against

      enum STATE { STAT_ERRORS, STAT_SUCCESS, STAT_EVENT };
      int ReadNumber(const STATE stat);

  6. If control parameters have to be passed to a function, they have to be of an enum type. If it is a function element, the enum is to declare locally in the class which contains the function.
    1. enum STATE { STAT_ERROR, STAT_SUCCESS, STAT_EVENT };
      int ReadNumber(const STATE stat);
      int nNumber = ReadNumber(STAT_ERROR);

      class CDevice
      {
          enum STATE { STAT_ERROR, STAT_SUCCESS, STAT_EVENT };
          int ReadNumber(const STATE stat);
      };

      int nNumber = thePrinter.ReadNumber(CDevice::STAT_ERROR);

  7. Function declarations contain always the parameter names. The parameter names used for function declaration and function definition are identical.
  8. (C, Pascal) Functions of a module encapsulating an abstract data type, which could be considered as member functions of that data type, get a pointer to the target instance. This pointer is named this or pThis.
    1. LONG DifferenceInSeconds(PTime this, const PTime pTargetTime);

Overriding Functions

Functions must be overriden polymorhical only! Constructions like

are inadmissable, since they hold the danger that the function called actually is totally hidden. In the example above would

result in a call of CSuperclass::f(), leading to a different behaviour of pSubInstance because of the cast. CSubclass::f() would be completely inaccessible. Functions to be overridden in a descendant are therefore always declared polymorphic.

Functions With A Return Value

  1. Functions with a return value (except state return values) must not have any side effects.
    1. Inadmissable: SearchAndDeleteMinimum(int nCount, int naNumbers[]);
          // Looks up for the smallest value and deletes it.
    If side effects are indispensible, e.g. because an undividable has to be implemented (e.g. CStack::Pop() yields the top element of a stack and updates the stack pointer), a detailed comment is required!
  1. Functions yielding a pointer, return NULL in case of an error. A returned value different from NULL is a valid pointer!
  2. Functions return scalar types or class instances on the stack.
    1. CComplex Root(const CComplex& z);
          // returns the complex square root.

      CComplex Root(const CComplex& z);
      // comments
      {
          CComplex result(0.0, 0.0);
          …
          return result;
      }

      {
          cout << Root(CComplex(1.0, -3.0));
      }

  3. Functions returnig a new class instance stored on the heap, create that instance using the new operator (C++) or malloc() (C). The caller is responsible for deallocating the memory with the delete operator (C++),by calling free() (C) or with a Delete…() function as described later.
  4. The name of such function starts with New. The declaration should be followed by a comment that points out, that the caller is responsible for the memory deallocation.

    Each of these New…() functions should be accompanied by a corresponding Delete…() function that deallocates the used memory.

      PComplex NewRoot(const CComplex& z);
          // returns the complex square root.
          // The caller must free the allocated memory.

      PComplex NewRoot(const CComplex& z);
      // comments
      // RETURN VALUE a pointer to the complex root of z.
      // DESCRIPTION  The caller must free the allocated memory.
      {
          PComplex result = new CComplex(0.0, 0.0);
          …
          return result;
      }

      {
          PComplex p = NewRoot(CComplex(1.0, -3.0));
          cout << *p;
          DeleteRoot(p);
      }

  5. The return statement is allowed only at the very end of a function body. (C) The return statement may be used as an exit in case of an exception.
  6. If the return value is stored to a variable, the variable's name is result (with the corresponding type prefix). This variable's declaration is the function's very first statement. Such function has a single return statement in its very last line, which yields result.
    1. int Sum(const int naValues[], const nCount)
      {
          int nResult = 0;

          for (int i = 0; i < nCount; ++i)
          {
              nResult += naValues[i];
          }

          if (100 < nResult)
          {
              nResult = 100;
          }

          return nResult;
      }

    Inadmissable constructs are commented:

    int Sum(const int naValues[], const nCount)
    {
        int nResult = 0;

        for (int i = 0; i < nCount; ++i)
        {
            nResult += naValues[i];
        }

        // ------------------------ INADMISSABLE CONSTRUCTS FOLLOW

        if (Condition1())
        {
            return nResult;      // INADMISSABLE!
                                 // nResult, but not at the end
        }

        if (Condition2())
        {
            Statements();

            if (15 == nResult)
            {
                return -27;      // INADMISSABLE!
                                 // neither nResult nor at the end
                                 // and nested in a block
            }

            Anweisungen();
        }

        return 0;                // INADMISSABLE!
                                 // at the end, but not nResult
    }

Functions Without A Return Value

  1. Functions without a return value may cause side effects. Such side effects must be limited to tha class or module of which the function is a member.
  1. A function that is supposed to return more results than one, may do this in a struct or class instance having the values as attributes.
  2. If no class can be be defined, that compounds the values reasonably, it is very likely, that the function has more responsibilities than it should!

Functions With A State Return Value

  1. Functions with a state return value are usually inadmissable. Call a modifier that performs the operation and then a selector that returns the result of the operation.
    1. theBuffer.ReadData();
      bSuccess = theBuffer.HasGotNewData();

      Inadmissable:

      bSuccess = theBuffer.ReadDataAndYieldSuccess();

  2. State return values may be used only, if the execution of the operation is undividable connected to determining the state value or calling two functions would be too expensive, when programming on hardware level.
  3. Functions with a state return value may cause side effects. Such side effects must be limited to tha class or module of which the function is a member.
  4. A state value returned by a function is a signed integer (int, long, signed char). If the returned value is less than zero, an error has occurred. A returned value greater or equal to zero is a valid result.

Default Constructor

  1. (C++) Default constructors (constructors without parameters) are generated automatically for each class. If they are not defined explicitly for classes with other constructors, this may lead to certain problems (e.g. with vectors of objects). If a class defines any constructor,
    1. a default constructor must also be defined or
    2. the class declaration must contain a comment like
      1. // no default constructor, because…

    Attention: X::X(int i = 0) is a default constructor too!

  2. (Delphi) The name of the default construnctor is Create.
    1. constructor CClass.Create;

Copy Constructor

  1. (C++) Copy constructors are generated automatically for each class. They copy every element of a class's instance. The (implicit) declaration for a class X is X::X(const X&). Classes which contain pointer or reference elements or implicit references to system resources (e.g. file or window handles), must
    1. define a copy constructor explicitly or
    2. define a private dummy copy constructor or
    3. contain a comment like
      1. // default copy constructor works correctly, because…

    (Delphi) The name of the copy constructor is Copy.

      constructor CClass.Copy(const anInstance: CClass);

Constructor Behaviour

If any operation of the constructor fails (e.g. memory allocation for a data element), the constructor has to clear the garbage and undo previous operations (e.g. deallocate memory of other data elements). In this case the constructor raises an appropriate exception.

Destructor

  1. (C++) Classes which contain pointer or reference elements or implicit references to system resources (e.g. file or window handles), must
    1. define a destructor explicitly or
    2. contain a comment like
    3. // default destructor works correctly, because…

    (Delphi) The name of the default destructor is Destroy.

      destructor CClass.Destroy; virtual;
      destructor CClass.Destroy; override;

Polymorphic Destruktor

The destructor is not polymorphic by default, causing the superclass' destructor to be called. A class that contains polymorphic functions, has to define a polymorphic destructor.

Assignment Operator

  1. Assignment operators are generated automatically for each class. They copy all data elements of a class's istance. The (implicit) declaration for a class X is X& X::operator=(const X&). Classes which contain pointer or reference elements or implicit references to system resources (e.g. file or window handles), must
    1. define an explicit assignment operator or
    2. define a private dummy assignment operator or
    3. contain a comment like
    4. // default assignment operator works correctly, because…

    5. The assignment a = a must be implemented correctly.
  2. Since assignments within expressions are forbidden, assignment operators should be declared as
    1. void operator =(const CClass& anInstance)

Exceptions

  1. A functions raises an exception in case of an error. Returning error codes is forbidden, except for languages that do not support exceptions.
  2. All exceptions risen or passed by a function are to be listed at the function's declaration.
  3. Checking the preconditions preceeds generally the function's code. If there are no preconditions to check, this is to point out in a comment.
  4. A function's code is always followed by the postcondition checks. If there are no postconditions to check, this is to point out in a comment.
  5. Functions raise exceptions, if and only if the preconditions were fulfilled, but the postconditions could not be achieved. Exceptions must not be used to return state values. The following is always true:
    1. If and only if a function didn't raise an exception, its postconditions are fulfilled.
    2. If and only if a function raised an exception, its postconditions are not fulfilled.

    This means in particular, that a function must not raise an exception, if its postconditions are fulfilled and that a function must raise an exception, if the postconditions are not fulfilled. A function raises also an exception, if its preconditions are not fulfilled. The function's actual implementation can trust on the preconditions to be fulfilled. The function's caller is responsible for asserting the preconditions. No function tries itself to achieve the preconditions.

  6. If a function can handle an exception, the whole function body is executed again after the exception was handled (and the problem that led to the exception was cleared):
    1. double Reciprocal(const double x)
      // The function returns 1/x, if (0 != x) else 0.0.
      // The example (taken from [Meye88]) is of course constructed by
      // random, because it's clear, that (0.0 == x) is a precondition!
      {
          double result = 0.0;
          bool bDivisionByZero = false;

          do
          {
              try
              {
                  result = 0.0;

                  if (!bDivisionByZero)
                  {
                      result = 1.0 / x;
                  }
              }

              catch (xmath)
              {
                  bDivisionByZero = true;
                  continue;
              }
          } while (false);

          return result;
      }

Assertions

Functions must be guarded by pre- and postconditions [Meye88]. It is recommended to use appropriate macroes, which could be deactivated by an #ifndef DEBUG. Since the variety of compilers use different standard macroes for this purpose, a detailed discussion is useless here.

Concurrent Programming

Classes And Modules

  1. All programmes are to be designed for a multi tasking environment.
  2. A module's user is not responsible for the protection of critical sections. Classes should be designed as monitors.

Functions

  1. Functions must be designed reentrantly. Otherwise a mutex must be used to make sure that critical sections are entered more than once.

Additional Guidelines

  1. All data elements are initialized within all constructors. It is allowed to call a common initialising function from within each constructor. In this case, no constructor is allowed to initialize data elements individually, except the constructor's semantics will require this behaviour.
  2. Public variables are forbidden.
  3. Public non-member functions should be avoided.
  4. friend constructs should be avoided, except to prevent the former problems.
  5. (C++) Assignments to this are forbidden (don't confuse with *this!).
  6. (C++) Using malloc() and free() is not allowed. Use new and delete instead.
  7. Selectors returning a reference to a data element, may be defined inline.
  8. Access to data members of different instances is not allowed (except within copy constructors and assignment operators).
  9. Pointers and references having const semantics, have to be declared const.
  10. If the compiler does not optimize this behaviour, defining invariant instances inside loops should be avoided, because the constructor is called for each run.
  11. Implicit type casts should be avoided. In doubt call conversion functions or define cast operators.


[back] | [next] | [TOC] | [Introduction] | [Layout] | [Annexes]


Copyright © 1997-98 by Uwe Sauerland