Programming Style Guide
Statements
- Assignments within
expressions are forbidden.
| Inadmissable: |
while ((c = getchar()) != EOF)
n = a[++i];
o = p = q = 0; |
- The goto statement is not allowed.
User Defined Types
General Regulations
- User defined types should be used whenever possible..
typedef int PRIORITY;
typedef char ERRORCODE;
typedef enum { SC_BELL = '\a', SC_TAB = '\t'} SPECIAL_CHARACTER;
- If pointers to instances of user defined types are needed, a pointer
type is to declare by using typedef.
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
- Data elements must be declared private.
- (C++) Though obsolete, all polymorphic functions in derived classes
are to be declared virtual.
- (C++) inline function members
are inadmissable, because they publish the implementation they should hide.
Exception: private classes.
- (C++) Polymorphic inline functions are not allowed. They are
ignored by the compiler anyway.
- Function elements that do not change the instances state are declared
const (usually selectors).
Data Objects
General Regulations
- (C, C++) Variables are declared as close as possible to the place,
where they are used for the first time.
- (C, C++) A variable declaration without initialization is inadmissable.
Exceptions:
- Vectors with much more than 10 elements (strings will allways be initialized
– at least with ""),
- (C++) class instances having an explicitly declared default constructor,
- (C) Programming on hardware level (e.g. time critical interrupt handlers).
Function local variables may be uninitialized by exception. The resons
are to be explained in a comment.
(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};
- Standard macroes are declared in several standard headers. User defined
macroes must be avoided, if possible.
- 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.
- Constants are, to make them type
safe, always declared as data objects and not created by using #define.
- Constants (particularly strings) are to be placed in the application's
resource.
- Data objects, in particular intermediate results, should be declared
const.
- Function local static objects are inadmissable. They have
to be declared outside of the function as private global data objects.
- 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
- A function must use preconditions to check, that all input
parameters are valid.
- A function must use postconditions to check, that the return value
(and, if used, all output
parameters) are valid.
- Functions should get exclusivly input
parameters declared as const.
- Input parameters of a struct or class type should always be declared
as references (const &).
- Functions using control
parameters should be avoided. The idiom
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);
- 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.
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);
- Function declarations contain always the parameter names. The parameter
names used for function declaration and function definition are identical.
- (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.
LONG DifferenceInSeconds(PTime this, const PTime pTargetTime);
Overriding Functions
Functions must be overriden polymorhical only! Constructions like
class CSuperclass
{
public:
void f(const int i);
};
class CSubclass : public CSuperclass
{
public:
void f(const int i);
};
are inadmissable, since they hold the danger that the function called
actually is totally hidden. In the example above would
typedef CSuperclass* PSuperclass;
typedef CSubclass* PCSubclass;
PErbe pSubInstance= new CSubclass;
PBasis pSuperclass = PSuperclass(pSubInstance); // valid cast
pSubInstance->f();
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.
class CSuperclass
{
public:
virtual void f(const int
i);
};
class CSubclass : public CSuperclass
{
public:
virtual void f(const int
i);
};
Functions With A Return
Value
- Functions with a return value (except state return values) must not
have any side effects.
| 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! |
- Functions yielding a pointer,
return NULL in case of an error. A returned value different from
NULL is a valid pointer!
- Functions return scalar types or class instances on the stack.
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));
}
- 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.
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);
}
- 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.
- 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.
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
- 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.
- 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.
| 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
- 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.
theBuffer.ReadData();
bSuccess = theBuffer.HasGotNewData();
Inadmissable:
bSuccess = theBuffer.ReadDataAndYieldSuccess();
- 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.
- 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.
- 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
- (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,
- a default constructor must also be defined or
- the class declaration must contain a comment like
// no default constructor, because…
Attention: X::X(int i = 0) is a default constructor too!
- (Delphi) The name of the default construnctor is Create.
constructor CClass.Create;
Copy Constructor
- (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
- define a copy constructor explicitly or
- define a private dummy copy constructor or
- contain a comment like
// 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
- (C++) Classes which contain pointer or reference elements or implicit
references to system resources (e.g. file or window handles), must
- define a destructor explicitly or
- contain a comment like
// 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
- 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
- define an explicit assignment operator or
- define a private dummy assignment operator or
- contain a comment like
// default assignment operator works correctly, because…
- The assignment a = a must be implemented correctly.
- Since assignments within
expressions are forbidden, assignment operators should be declared
as
void operator =(const CClass& anInstance)
Exceptions
- A functions raises an exception in case of an error. Returning error
codes is forbidden, except for languages that do not support exceptions.
- All exceptions risen or passed by a function are to be listed at the
function's declaration.
- Checking the preconditions preceeds generally the function's code.
If there are no preconditions to check, this is to point out in a comment.
- 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.
- 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:
- If and only if a function didn't raise an exception, its postconditions
are fulfilled.
- 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.
- 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):
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
- All programmes are to be designed for a multi tasking environment.
- A module's user is not responsible for the protection of critical sections.
Classes should be designed as monitors.
Functions
- Functions must be designed reentrantly. Otherwise a mutex must
be used to make sure that critical sections are entered more than once.
Additional Guidelines
- 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.
- Public variables are forbidden.
- Public non-member functions should be avoided.
- friend constructs should be avoided, except to prevent the
former problems.
- (C++) Assignments to this are forbidden (don't confuse
with *this!).
- (C++) Using malloc() and free() is not allowed. Use
new and delete instead.
- Selectors returning a reference to a data element, may be defined inline.
- Access to data members of different instances is not allowed (except
within copy constructors and assignment operators).
- Pointers and references having const semantics, have to be
declared const.
- If the compiler does not optimize this behaviour, defining invariant
instances inside loops should be avoided, because the constructor is called
for each run.
- 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