Programming for a Hostile World
Real programs receive bad input, are called in unexpected ways, and run in imperfect environments. Defensive programming is writing code that anticipates these problems and behaves safely rather than silently producing garbage or crashing obscurely.
The first tool is input validation: check that inputs meet the assumptions before using them. A function that divides should check for a zero divisor; one that indexes a list should check the index is in range. Rejecting bad input at the boundary, with a clear error, is far better than letting it corrupt state deep inside.
Assertions state a condition the programmer believes must be true at a point in the code (assert n >= 0). If the assertion fails, the program stops immediately with a clear signal — turning a silent, far-downstream corruption into an obvious, local failure. Assertions document assumptions and catch violations early. A related idea is failing fast: detect an error as close as possible to where it arises, rather than letting it propagate.
Two more principles: handle errors explicitly (through error codes or exceptions) rather than ignoring them, and be clear about preconditions — what a function requires of its inputs — and postconditions — what it guarantees about its outputs. Code that states and checks its contract is easier to use correctly and to debug when misused.
Common pitfall: trusting that inputs will always be valid ("no one would ever pass a negative here"). Unvalidated assumptions are exactly where programs break in production. Validate inputs at boundaries, assert your assumptions, and fail fast with clear errors — a bug caught at the boundary is far cheaper than one that corrupts data deep inside.