Defensive Programming
Systematic approach to software that handles misuse, invalid inputs, and infrastructure failures
Tags
Overview
Purpose
Systematic approach to software that handles misuse, invalid inputs, and infrastructure failures
Rules
DEF-001: Define explicit contracts: every function has preconditions (what must be true before call), postconditions (what is guaranteed after), and invariants (what is preserved).
McConnell Ch.8 — "The best form of defensive coding is not inserting errors in the first place." Meyer DbC — formalize expectations between caller and callee.
Verification: Code review
DEF-002: Validate ALL external input at system boundaries. Internal code trusts typed data that has already passed boundary validation.
McConnell Ch.8 "barricades" — sanitize at entry points, trust inside. Hunt/Thomas — "Don't trust external data." ISO 25010 — reliability requires input faultlessness.
Verification: Code review + integration tests
DEF-003: Detect errors at the earliest possible point. Fail immediately with clear diagnostics rather than propagating corrupt state.
Hunt/Thomas — "Dead Programs Tell No Tales." Nygard — Fail Fast pattern prevents cascade. McConnell — catch bugs at compile/lint time, not runtime.
Verification: Code review + error scenario tests
DEF-004: Use assertions for conditions that should NEVER be false in correct code. Assertions document assumptions and catch programming errors during development.
McConnell Ch.8 — "Use assertions to document and verify assumptions." Hoare logic — {P}C{Q} formalizes invariant reasoning.
Verification: Code review
DEF-005: Handle errors explicitly with context. Never swallow errors (empty catch), never ignore returned errors, always wrap with operation context.
Martin Clean Code Ch.7 — exceptions over return codes, informative messages. McConnell — "Decide on a general approach to bad parameters" at project level.
Verification: Code review + linter rules (Go errcheck, TS strict)
DEF-006: Use the type system as first line of defense. Strict typing, discriminated unions, branded types, and explicit input structs prevent entire categories of bugs at compile time.
McConnell — catch errors at compile time, not runtime. ISO 25010 — faultlessness requires correct-by-construction approach where possible.
Verification: Compiler/type-checker + code review
DEF-007: Use guard clauses (early returns) for special cases and error conditions. The normal execution path should be the least-indented code.
Fowler Refactoring — Replace Nested Conditional with Guard Clauses. McConnell — "Use guard clauses to simplify complex logic." Hunt/Thomas — flat code is defensive code.
Verification: Code review
DEF-008: Establish clear trust boundaries. Code inside the barricade trusts its data. Code at the barricade validates ruthlessly. Never mix the two.
McConnell Ch.8 — barricade = sanitization layer at entry. Maps to FDD: SI layer is the barricade, PD layer trusts typed data inside.
Verification: Architecture review + integration tests
DEF-009: When non-critical components fail, continue operating in degraded mode. Serve cached data, return partial results, use fallback backends.
Nygard Release It — circuit breaker, bulkhead, shed load. ISO 25010 — fault tolerance and recoverability. McConnell — defensive design considers failure modes.
Verification: Chaos testing + fallback integration tests
DEF-010: Ensure resources are always released: use defer for cleanup, context for cancellation, bounded pools for connections, and timeouts for all external calls.
Nygard — timeout, steady state patterns. Go context package — propagate cancellation. McConnell — "Don't leave cleanup to the caller."
Verification: Code review + race detector + leak tests