← All blog posts

The power of ten: Rules for developing safety critical code

June 4, 2025 · Software Engineering

Generated by Gemini

Recently (even though that it was originally written in 2006) I found “The Power of 10: Rules for Developing Safety-Critical Code”.

The article argues for a concise set of ten coding rules to improve the reliability and verifiability of safety-critical software, particularly that written in C. Even though C is not the main programming language for many engineers these days of AI and cloud computing, I found these rules interesting and full of learnings.

The author contends that most existing coding guidelines are too lengthy, often ignored, and difficult to check with automated tools, thus diminishing their effectiveness. In contrast, a smaller, memorable, and mechanically checkable set of rules can offer significant benefits.

The proposed “Power of Ten” rules are:

  1. Simple control flow: Restrict code to simple constructs, avoiding goto and recursion. This enhances verification and code clarity, and ensures an acyclic function call graph without recursion
  2. Fixed loop bounds: All loops must have a fixed upper bound that is statically provable by a checking tool. This, along with no recursion, prevents runaway code.
  3. No dynamic memory allocation after initialization: This common rule for critical software aims to avoid unpredictable behavior from memory allocators and garbage collectors, and reduce memory handling errors. Stack memory use can be statically bounded without recursion.
  4. Short functions: Functions should not exceed the length of a single printed page (around 60 lines). This promotes understandable and verifiable logical units.
  5. High assertion density: Code should average at least two side-effect-free assertions per function to check for anomalous conditions. Assertions aid in defect interception and defensive coding.
  6. Minimize scope of data: Declare data objects at the smallest possible level of scope. This supports data-hiding and simplifies diagnosing issues.
  7. Check return values and parameters: The return value of non-void functions must be checked by callers, and parameter validity checked within functions. Exceptions must be explicitly justified. Note: this rule brought golang in my mind for some reason.
  8. Limited preprocessor use: Confine preprocessor use to header inclusions and simple macros. Avoid token pasting, variable argument lists, recursive macros, and limit conditional compilation. This prevents obfuscation and reduces the number of code versions to test.
  9. Restricted pointer use: Limit pointer dereferencing to one level and forbid function pointers. Pointers are prone to misuse and complicate static analysis. Note: probably a good thing than memory management is simplified (or non-existent) these days.
  10. Strict compilation and static analysis: All code must compile with all warnings enabled at the most pedantic setting without any warnings, and be checked daily with static analyzers, also with zero warnings. This leverages tools to find errors, even if it means rewriting code that confuses the tools.

The author reports that these rules are being used with encouraging results, leading to improved code clarity, analyzability, and safety. While initially perceived as strict, these rules are intended for software where correctness is paramount, akin to a seatbelt becoming an indispensable safety measure.