Author: Gerard J. Holzmann
Institution: NASA/JPL Laboratory for Reliable Software
Location: Pasadena, CA 91109
Most software development projects adopt coding guidelines, but many fail to enforce them effectively. Often, these guidelines are extensive and lack justifiable rationale, making them hard to remember or apply. The key to effective guidelines is:
- Small number of rules
- Tool-verifiable constraints
- Clear and specific definitions
This paper presents 10 strict yet practical rules aimed at improving the reliability and verifiability of safety critical code, especially when written in C.
Avoid
goto
,setjmp
,longjmp
, and all forms of recursion.
- Ensures acyclic call graphs.
- Simplifies verification and testing.
- Improves code clarity.
Every loop must have a statically provable iteration limit.
- Prevents runaway code.
- Enables strong static analysis.
- Exception: non-terminating loops (e.g., schedulers) must be proven not to terminate.
Avoid
malloc
,free
, garbage collection, etc., beyond program start.
- Prevents issues like:
- Memory leaks
- Dangling pointers
- Fragmentation
- Enables bounded memory use checks.
Functions must fit on one printed page, approx. 60 lines (1 statement or declaration per line).
- Encourages modularity and clarity.
- Discourages overly complex logic blocks.
Assertions must be side-effect free Boolean tests.
- Detects anomalous conditions early.
- Boosts defensive programming.
- Assertion example:
if (!c_assert(p >= 0) == true) {
return ERROR;
}
Macro definition:
#define c_assert(e) ((e) ? (true) : \
tst_debugging("%s,%d: assertion '%s' failed\n", \
__FILE__, __LINE__, #e), false)
Variables should have minimal visibility.
- Encourages data hiding.
- Simplifies fault diagnosis.
- Prevents variable misuse.
All function return values (non-
void
) must be checked.
All parameters must be validated inside functions.
- Prevents silent failures.
- Exceptions must be explicitly justified or casted to
(void)
. - Increases defect detection.
Use preprocessor only for:
-
Header file inclusion
-
Simple macros (no token pasting, recursion, ellipses)
-
Minimal conditional compilation
-
Prevents obfuscation.
-
Averts combinatorial test explosion due to too many configurations.
Only one level of pointer dereferencing allowed.
No function pointers or hidden pointer operations via macros/typedefs.
- Aids static analysis.
- Prevents data flow confusion.
- Eliminates potential recursion tracking issues.
Code must:
-
Compile with all warnings enabled.
-
Pass static analyzers with zero warnings.
-
Promotes early bug detection.
-
Avoids ignoring “harmless” warnings that may not be.
-
Encourages rewriting ambiguous code.
These 10 rules:
- Improve control flow clarity
- Eliminate dynamic memory risks
- Promote safe, verifiable, and readable code
Though strict, they are akin to a seatbelt: uncomfortable at first, indispensable later. These rules are in experimental use at JPL and have shown encouraging results in mission-critical software.