Core Topics route · TFA-104

Defensive Programming

Design code that detects broken assumptions, contains faults and moves the system towards a defined recoverable or safe condition.

Intermediate7 min reading + 25 min practiceWorked example

← Back to the learning library

Before you begin: Basic C/C++ arithmetic, interfaces and error handling; TFA-101 is helpful.

Reading time and practice time are estimates. Examples use fictional products and illustrative engineering values, not approved product limits.

Learning objectives

By the end of this module, you should be able to:

  • Turn assumptions into executable checks and interface contracts.
  • Use timeouts, plausibility checks and assertions appropriately.
  • Design containment, degradation and recovery behaviour.
  • Connect defensive mechanisms to hazards, requirements and tests.

Lesson 1

1. Start with assumptions

Defensive code begins by naming what must be true: a length fits the destination, a state permits the request, a sensor value is physically plausible, a response arrives before its deadline. Validate untrusted or changeable inputs at the boundary and avoid repeating inconsistent checks deep in the implementation.

Distinguish a caller error, an environmental fault and an internal invariant failure because each needs a different response and diagnostic.

Lesson 2

2. Use the right detection mechanism

Range checks protect data boundaries. Timeouts detect missing progress. Sequence numbers reveal loss or replay. Watchdogs detect failure to service an independent monitor. Assertions expose impossible internal conditions during development and may be converted into controlled fault handling in production.

A check without a defined response only moves the problem. Specify what is made safe, what is retained for diagnosis and whether continued operation is allowed.

Lesson 3

3. Contain before recovering

Recovery is safe only after the fault's effect is bounded. Stop or isolate an actuator, invalidate partial results and reset the smallest affected component. Automatic retry is appropriate for transient faults only when repetition cannot create harm, exhaust a resource or hide deterioration.

Safe state is system-specific. It may mean power removed, motion stopped, current operation completed, data marked invalid or control transferred to another layer.

Lesson 4

4. Diagnostics are part of the design

Record stable fault identifiers, relevant state, occurrence count and timing without exposing secrets or exhausting storage. Diagnostics should distinguish the first cause from later consequences. A flood of secondary errors can overwrite the evidence needed to understand the event.

Lesson 5

5. Validate a transaction before changing state

Consider a command containing mode, length, target and checksum. Validate framing, length, authentication where applicable, state permission and semantic range before committing any product change. Rejecting a bad target after already changing the mode leaves a partial transaction.

Separate parsing from execution. Parse into a bounded temporary representation, validate the complete request, then ask the owning component to commit it. Define duplicate-command behaviour: a retry caused by a lost acknowledgement must not repeat an irreversible physical action. A transaction identifier can support deduplication, but needs a retention and restart policy.

For a destination of capacity capacity, validate offset <= capacity before checking length <= capacity - offset. This avoids overflowing an unchecked offset + length. The types and conversion rules still require review.

Lesson 6

6. Make timeouts and watchdogs meaningful

An unsigned elapsed-time comparison can handle one timer wrap when the elapsed interval is less than a full counter cycle and checking is frequent enough. A 32-bit millisecond counter wraps in roughly 49.7 days. That does not make an arbitrary long-running timeout safe without an explicit interval bound.

/* Illustrative: uint32_t tick, bounded interval, frequent polling. */
bool expired(uint32_t now, uint32_t started, uint32_t limit) {
    return (uint32_t)(now - started) >= limit;
}

A watchdog should indicate useful system progress, not just interrupt activity. A supervisor may collect independently updated progress tokens from critical tasks. Refresh only after required progress and timing checks pass. Account for legitimate long operations and specify how reset leaves physical outputs safe.

Lesson 7

7. Contain escalation and diagnostic load

Fault handling can itself fail through recursive logging, retries or allocation. Use bounded diagnostic storage and stable fault IDs. Preserve the first fault and the context needed to reconstruct its cause; later symptoms can be counted or summarised.

Specify the difference between recoverable error, degraded operation and latched fault. Automatic recovery requires evidence that the triggering condition has cleared and that re-entry is safe. If repeated recovery is permitted, set a rate or attempt budget and escalate when it is exhausted. Do not use assertions as the sole validation of untrusted inputs when assertions may be disabled in production.

Decision guide

What response should follow a failed check?

Failure typeTypical responseEvidence needed
Invalid external inputReject request; preserve current safe state.Boundary and negative tests.
Transient peripheral faultContain, retry within a fixed budget, then escalate.Fault-injection and retry-limit tests.
Broken internal invariantMove to controlled fault handling; capture diagnostics.Review of assertion policy and forced-fault test.

Worked example

Worked example — motor position command

Problem

A command requests a position beyond the calibrated travel while a previous move is still active.

Engineering approach

The command interface validates range, device state and command freshness before changing any target. Rejected commands return a reason code. Encoder plausibility and movement timeout are monitored independently. On disagreement, drive power is removed and recalibration is required.

Result

Invalid commands cannot partially alter state, actuator faults are contained and recovery is deliberate rather than an uncontrolled retry.

Engineering evidence

What should the work produce?

Defensive coding rules

Defines mandatory checks and prohibited practices.

Fault-response specification

Maps detections to containment and recovery.

Diagnostic catalogue

Provides stable identifiers and retained context.

Fault-injection results

Shows abnormal paths behave as designed.

Common failure patterns

What goes wrong?

  • Silently clamping an invalid value and hiding the fault.
  • Retrying indefinitely.
  • Using a watchdog as the only fault-control mechanism.
  • Logging every consequence but losing the initiating error.

Applied exercise · approximately 25 minutes

Repair a non-atomic actuator command

A handler sets a new mode, copies a variable-length payload, checks the requested speed, and then starts motion. The sender retries when acknowledgement is missing.

  1. Order the validation steps and identify an overflow-safe payload check.
  2. Define the commit point and duplicate-request behaviour.
  3. Choose diagnostics for invalid length, prohibited state and repeated recovery failure.
Review a suggested approach

Validate packet bounds before copying, then parse and check mode, speed, current state and authority without altering the live controller. Commit the complete validated request through the motion owner. A request ID can let a duplicate return the original acceptance or completion status rather than starting a second motion; stale IDs and restart behaviour must be specified.

Use distinct stable diagnostic codes so support can tell a malformed request from a valid request in a prohibited state. Repeated recovery should exhaust a bounded policy and enter the system-defined safe or degraded condition. Tests should assert unchanged state after each rejection, including a rejection near the end of validation.

Completion criteria

  • Rejected requests leave the old state intact.
  • Lengths are checked before arithmetic, copying or allocation can fail.
  • Retries cannot silently duplicate a physical action.

This is a self-directed engineering exercise, not an accredited examination. Compare the rationale as well as the final answer.

Practical check

Questions for your next design review

  • Are interface assumptions stated and checked once at the boundary?
  • Does every timeout have a defined value and rationale?
  • Is the system made safe before recovery begins?
  • Are retries bounded and observable?
  • Can diagnostics identify the initiating fault without sensitive data?
Key takeaway

Defensive programming is the designed response to violated assumptions—not a collection of extra if-statements.

References and further reading

Educational guidance only. Apply the authorised standards editions, regulations and approved procedures relevant to your product and market. Linked publications support further study; the fictional exercises do not demonstrate compliance.