This page defines and justifies the project-specific coding rules that were applied to the
implementation of the OBS Framework. These coding rules complement those
taken over from the "C and C++ Coding
Standard" prepared by the ESA Board for Software Standardisation and Control (BSSC).
The rules are grouped in categories of logically related rules.
PR2.1 | Objects shall be handled only through their pointers.
|
This and the next rules try to impose a java-like programming style upon C++ code.
Direct handling of object instances is onerous in terms of both memory and processing time and
should therefore be avoided. The next rules make the manipulation of object pointers safe
by, essentially, dictating that all objects are created statically and that they can never
be destroyed. Note that this rule also implies that objects should not be passed by value
to functions and methods. Thus, no destruction of objects on the stack can occur.
|
PR2.2 | All the objects required by an application shall be created during
application initialization and shall never be destroyed afterwards.
|
This policy prevents occurence of out-of-memory and double-delete errors during application normal operation and
increases timing predictability. Where dynamic objects creation might be useful, pools of
pre-allocated objects can be set up at initialization.
|
PR2.3 | There shall be no creation of objects on the stack.
|
This rule complements the previous one. Taken together, the two rules ensure that objects are never
destroyed. This makes handling of objects through their pointers safer and essentially removes the
danger of dangling pointers.
Note that this rule implies that objects cannot be passed by value in function and method calls and
that they cannot be used as local variables in methods or function bodies.
|
PR2.4 | The class destructor shall be declared to have "protected" visibility for all
non-final classes.
|
This ensures that some violations of the previous two rules will be caught by the compiler that will flag
any attempts, direct or indirect, to dynamically destroy objects outside the class tree. This rule is best implemented at the level of the root class.
|
PR2.5 | There shall be no assignment of objects.
|
This rule particularizes previous rules on object management. Object assignment normally requires
re-definition of the assignment operator which is often difficult to do without errors. Object
assignments can moreover be an expensive operation to perform requiring extensive data copying and
substantial amounts of code.
|
PR2.6 | For all classes, the assignment operator shall be re-defined to report an assert error.
|
This helps detects violation of the previous rule. This rule is best implemented
at the level of the root class (see PR1.1).
|
PR2.7 | For all classes, the copy constructor shall never be invoked.
|
Use of the copy constructor has the same drawbacks as the use of object assignment and should
be avoided for the same reasons.
|
PR2.8 | For all classes, the copy constructor shall be re-defined to report an assert error.
|
This helps detects violation of the previous rule. This rule is best implemented
at the level of the root class.
|
PR2.9 | The class destructor shall be declared to have "private" visibility for all
classes that are intended to be final (i.e. classes that are not meant to be further extended
through class inheritance).
|
This helps enforce the rule on the non-destruction of objects. It also ensures that final classes
are not extended (extending a class with a private destructor causes a compiler error).
|
PR3.1 | Pointers to variables of primitive type shall be avoided as far as possible.
|
An earlier rule dictates that class instances should only be manipulated
through pointers. That rule was complemented by other rules that ensured that objects are
created at initialization time and never destroyed afterwards (not even on the stack). This
makes their manipulation through pointers safe. It is not possible to adopt similar
restrictions to the creation and destruction of variables of primitive types and hence, in
their case, the safest policy is to avoid, as far as practical, the use of pointers.
|
PR3.2 | Pointers to variables of primitive type shall not be passed as public or
protected method parameters.
|
This rule is a looser version of the previous one. Deviations will sometimes be necessary
from the previous rule for reasons of run-time efficiency or when manipulating low-level
data structures (e.g. memory mapped hardware registers). The present rule dictates that
such deviations should be confined to using pointers internally to a class. Localized use
of pointers is less dangerous than use across classes.
|
PR3.3 | Arrays shall not be passed as method or function parameters.
|
This rule particularizes the previouos one. It is introduced because in C/C++ there is
no run-time check on access to array elements. Use of out-of-bounds array indices
is especially likely when an array is used far from the point where it is declared.
Use of an array parameter within a method or function is such a case and should
therefore be avoided.
|
PR7.1 | Methods shall report errors they encounter by creating
event reports that are stored in some globally accessible data structure.
They shall normally not use return codes to report errors.
|
Use of return code implies the need for the caller to always check the value of
the return code on every activation. It also implies that the caller must
know what to do in case an error is detected. This introduces overheads that
are normally not acceptable in an embedded environment. The proposed approach
instead allows to limit the error checking overheads to components that are
truly interested in it and that know how to handle the error.
|
PR7.2 | No checks shall be implemented to detect errors due
to erroneous configuration operations.
|
Configuration operations are performed in the application initialization phase.
These operations are under the full control of the application developer and
are easy to test because they tend to be sequential and do not rely on complex
logic. It is therefore reasonable to assume that no errors will be performed
in this phase. Error checking would also be problematic because it is unclear
how the error information about the detected errors should be used.
|
PR7.3 | Checks shall be performed on the valitidy of method and function
parameters only if illegal values might cause corruption of internal data
structures. In other cases, responsibility for ensuring their correctness
rests with the caller.
|
Performing a systematic check of the legality of all method and function parameters is judged
to be too onerous. Additionally, it is difficult to give general rules for what
should be done when an illegal
parameter value is detected (reporting the fact but executing the method/function
anyway? Reporting the fact and executing some dummy default action? Commanding
an application reset? Throwing an exception?). On the other hand, performing no checks
is dangereous because illegal parameter values might corrupt the internal state
of a component and thus make the component unusable for all successive callers.
The policy prescribed by this rule is a minimalist one that keeps an external
caller from corrupting the internal data structures of the callee but otherwise
leaves overall responsibility for ensuring the legality of the parameters with the caller.
The basic principle is that a callee need not trust its callers but well-behaved callers
should always be able to trust their callees.
This rule is only applied to public methods. It is assumed that protected and private
methods are only used by "trusted" callers and can therefore dispense with the error check.
|
PR7.4 | Memory that is dynamically allocated shall be initialized immediately after
allocation.
|
This rule is useful both because it ensures that freshly allocated memory is initialized with some
sensible values and because it catches "out-of-memory" errors. In the OBS Framework, memory is allocated with the
"new" operator and this normally throws an exception if the heap is full. However, the OBS Framework
is intended to be usable with an EC++ compiler and the EC++ subset of the C++ language does not
include exception handling. One alternave way to catch "out-of-memory" errors is to check that the value
of the pointer returned by "new" is different from null. In an on-board context, this represents a fatal error. Hence,
initializing the memory and allowing the program to fail through an "illegal memory access" error
is an equivalent and slightly more efficient solution.
It should also be noted that, in the OBS Framework, dynamic memory allocation is only used during the
application configuration phase (see PR2.2). The number of calls to "new" and the amount of memory that they
require is therefore statically (and easily) predicatable. Out-of-memory errors therefore should only
arise during the program development phase.
|
PR8.1 | Objects shall be endowed with the capability of performing
a configuration check upon themselves.
|
Objects are configured as part of the application instantiation process.
A configuration check verifies that an individual object is configured.
It might check that all plug-in objects have been loaded and that all
user-defined parameters have legal values.
A configuration check is best implemented as an overridable method at
the level of the root class. This allows an application to
construct a list of all framework objects it uses and to perform the
configuration check systematically upon all of them.
|
PR8.2 | Configuration operations shall be as far as possible
implemented as setter methods with a single argument following the
JavaBeans conventions for property setter methods.
|
Adherence to this rule makes it easy to identify and recognize
configuration methods. The presence of individual setter methods for
each configuration parameter, additionally, leaves the possibility
open of selectively changing their values at run time. Finally,
the use of naming conventions for configuration methods might
one day facilitate the construction of an automatic instantiation
environment upon the framework.
It is of course inevitable that some objects might require
configuration operations that cannot be implemented as property setter
methods. These number of such operations should however be minimized
and their presence shall be mentioned in the class documentation.
|
PR8.3 | Configuration operations shall be as far as possible
independent of the order in which they are executed.
|
The intention behind this rule is to implement the application
configuration process as a sequence of atomic operations (ideally,
as stated by the previous rule, as a sequence of property setting
operations) that are as far as possible independent of each
other. The simplicity of the configuration operations and their
mutual independent are valuable as means to simplify the application
instantiation process and to reduce the chances of configuration
errors. Additionally, they might one day facilitate the construction
of an automatic instantiation environment upon the framework.
Where the configuration operations to be performed on a certain
object are order-dependent, the fact shall be documented in the
class documentation.
|
PR8.4 | All framework classes shall implement a parameterless
constructor.
|
The intention behind this and the previous two rules is that the
application instantiation process should be representable as a
sequence of object instantiation and object configuration operations
and that the two types of operations should be distinct.
The advantage of separating object instantiation from object
configuration is that this leaves the option open of dynamically
re-configuring selected object during application operation.
The disadvantage of doing so is that there is a greater risk
that an object may be incompletely configured. If configuration
is performed as part of the object instantiation process (by passing
the configuration parameters as constructor parameters), it is
less likely that users will omit some configuration actions.
Rule 8.1, however, mandates the implementation of a configuration
check service which is designed to catch such omissions.
In view of the above, application of this rule shall be waived for
all classes that, for whatever reason, do not implement a configuration
check as foreseen by rule 8.1.
Adoption of this rule has an additional advantage
in case the framework must be ported to C for use on targets where
no C++ compiler is available (e.g. DSPs). In this case, the
restriction to parameterless constructors would facilitate the
development of an automatic translator from C++ to C.
|