Assertion density
An assertion is an executable claim about what must be true at a point in the program, and its worth is proximity: it fails at the cause, not three stack frames later where the bad value finally breaks something. Scattering asserts at random adds noise; placing them where invariants actually live turns a class of debugging sessions into one clear failure. The skill is knowing those places.
Method
- Assert preconditions at every public boundary. At the top of an exported
function, check the arguments the caller must honor:
assert n >= 0, non-null, index in range. A caller's mistake becomes a message at the door. - Establish class invariants in the constructor. After construction, assert
the object sits in a legal state:
assert self.balance >= 0, both ends of a range ordered. An object that cannot exist badly never spreads a bad state. - Guard the loop invariant inside the loop. Assert the property the loop maintains each pass: an index stays in bounds, a running sum stays non-negative, a heap stays ordered. The assert catches the pass that breaks it.
- Check postconditions before returning. Ahead of
return result, assert what you promised: the output is sorted, its length matches the input, no null slipped through. This defends the contract callers are about to rely on. - Assert the impossible branch. In an else or default you believe
unreachable, write
assert False, "unreachable: " + staterather than a silent pass. If reality disagrees, you learn at once and precisely. - Keep assertions cheap and side-effect free. Never
assert queue.pop(): builds that strip asserts, likepython -OorNDEBUG, would drop the pop too. Assert conditions; compute nothing the program itself depends on.
Litmus tests
- When this assert fires, does the message point at the cause or a symptom?
- Would stripping assertions in a release build change any behavior? It must not.
- Can you name the invariant each assert defends in a sentence? If not, cut it.
Boundaries
Assertions guard programmer errors, not user input or I/O failure: validate untrusted data with real error handling that survives in production, since asserts may compile out. Hot loops where an assert measurably costs throughput are a judgment call; some teams gate expensive checks behind a debug flag.