Guide

Software spends most of its life being changed by people who did not write it. The feature you ship this week will be edited next quarter by a teammate who has never opened the file, under a deadline, at 11pm, with half the original context gone. Maintainable code is not "pretty" code. It is code that keeps the cost of that next change low.

The enemy is complexity: anything that makes the system hard to understand or hard to modify. John Ousterhout's framing separates three symptoms that feel different but share a root cause: change amplification, cognitive load, and unknown unknowns.

Concepts in this section

Read them in order. Each one builds on the one before.

StepConceptWhat it answers
1Software complexityWhat are you actually fighting?
2Deep modulesHow do you hide complexity behind a small interface?
3Naming and commentsWhat does the next reader see first?
4Error handling and illegal statesHow do failures stay visible and rare?
5Duplication vs the wrong abstractionWhen is copy-paste the right call?
6Interfaces that age wellWhat can you not change cheaply later?

Prefer boring consistency, deep modules at the right boundaries, and tests that lock behavior (see testing strategy). Code review is where this craft shows up daily.

A one-line product change

Checkout applied a percentage discount, then tax, with names like d and t. A one-line product change — "discount applies after tax in EU, before tax in US" — forced reverse-engineering of units and order.

After a maintainability pass: subtotalCents, applyPercentDiscount, applyTax, explicit MoneyCents, validated rates. The next engineer changes EU vs US policy without decoding the original.

That is the whole topic in one picture: spend an hour now so the next stranger's change is calm. Beck's move under a deadline is the same idea: a tidy commit that makes the change easy, then a behavior commit reviewers can actually see.

Common mistakes

  • Shallow pass-through layers that add names without hiding decisions.
  • Clever one-liners and premature frameworks that raise cognitive load.
  • DRY on text that is not the same knowledge.
  • Swallowing errors so production looks like "no rows."
  • Two house styles in one repo.

Key takeaways

  • Optimize for the next change by a stranger: minimize change amplification, cognitive load, and unknown unknowns.
  • Prefer deep modules with small interfaces over shallow pass-through layers.
  • Names and function boundaries carry most of the documentation; comments explain why.
  • Match the codebase's conventions; two styles cost more than one imperfect style.
  • Fail fast, make errors explicit, and redesign away avoidable failure modes.
  • Duplicate until you know the right abstraction; YAGNI for imaginary futures.

Further learning

Ousterhout's Talks at Google session is the fastest way to internalize deep modules and complexity symptoms.

A Philosophy of Software Design | John Ousterhout | Talks at GoogleTalks at Google
A Philosophy of Software Design (Ousterhout)Software Design Bookweb.stanford.edu Tidy First? (Kent Beck)oreilly.com