Essential Reading: A Philosophy of Software Design — Fighting Complexity One Module at a Time josedacruz, September 30, 2026September 30, 2026 TL;DR: John Ousterhout argues that software complexity doesn’t arrive as one big mistake — it accumulates in small increments, one shortcut at a time, until a codebase becomes hard to change without breaking something else. His fix is to design “deep” modules: pieces of code with a simple interface that hide a lot of useful functionality behind it, rather than “shallow” modules whose interface is nearly as complicated as what’s inside. For anyone who has ever been afraid to touch a file because three other files depend on its internals, this book names the disease and gives you a vocabulary for treating it. Cover: A Philosophy of Software Design by John Ousterhout. The core idea Ousterhout, who teaches the software design course at Stanford that this book grew out of, starts from a simple observation: nobody sets out to write bad software. Complexity creeps in gradually, through hundreds of individually reasonable decisions — a special case added here, a parameter threaded through three layers there, a “temporary” workaround that outlives the person who wrote it. He calls this a “complexity tsunami”: each drop of water is harmless, but the accumulated effect can drown a project. His central metric for fighting this is what he calls module “depth.” A module — a class, a function, a service, anything with a boundary — has an interface (what callers need to know to use it) and an implementation (everything behind that interface). A deep module has a simple interface hiding substantial functionality: think of a well-designed file-read function that lets you say “give me the contents of this file” without needing to know about buffering, disk blocks, or error retries. A shallow module is the opposite — its interface is nearly as complicated as its implementation, so using it barely saves you any thinking at all. Ousterhout’s go-to example is a poorly designed I/O library that forces you to compose four or five separate wrapper objects just to read a file, when a well-designed one would let you do it in a single call. The book also introduces “information hiding” as the mechanism that makes depth possible: a good module hides a design decision (like how it stores data internally) so completely that callers never need to know about it, and so that decision can change later without anyone else’s code breaking. When information hiding fails — when two supposedly separate modules both need to know the same underlying fact — Ousterhout calls that “information leakage,” and treats it as one of the most reliable early-warning signs of a design about to go wrong. What this means for IT work The most useful habit this book can give you isn’t a rule, it’s a question: when you’re about to write a new class, function, or API, ask whether you’re making the caller’s life simpler or just relocating the complexity. A pattern Ousterhout returns to again and again is “pull complexity downwards” — if complexity has to exist somewhere (and in most real systems, it does), it’s almost always better for the person implementing a module to absorb it once than to force every caller to handle it themselves. In day-to-day architecture and code review work, this shows up as a short checklist of red flags worth watching for: Pass-through methods — a function that does nothing but call another function with the same arguments, adding a layer without adding value. Repeated special-case handling — the same “if this edge case, then…” logic showing up in multiple callers instead of being absorbed once inside the module that owns the data. Configuration sprawl — a component that pushes dozens of options onto its caller instead of picking sensible defaults and hiding the complexity of choosing them. Information leakage — two classes or services that both need to be changed together whenever one underlying assumption changes, even though nothing forces them to admit that dependency out loud. Classitis — Ousterhout’s term for splitting code into so many tiny classes that the overhead of jumping between them outweighs any clarity gained. The book also draws a useful distinction between “tactical” and “strategic” programming. Tactical programming optimizes for getting the next feature or bug fix out the door as fast as possible; strategic programming accepts spending 10-20% more time on a change in order to leave the design a little better than you found it. Neither is wrong in isolation — a prototype or a genuine emergency fix is a fine place to be tactical — but a team that is tactical by default, on everything, is the one that ends up unable to ship anything without breaking three other things. Where it falls short The book’s examples lean heavily on Ousterhout’s own teaching material and skew toward statically typed, object-oriented languages like Java and C++, so some of the advice — particularly around comments and formal interfaces — translates less cleanly to dynamically typed or heavily functional codebases, where “interface” is a fuzzier concept. His strong stance that most code needs more comments than programmers instinctively write is also genuinely contested; plenty of experienced teams (large parts of the Go community, for instance) have had real success with a philosophy that favors self-explanatory code over explanatory comments, and treat heavy commenting as its own maintenance burden. And because the book’s design advice is presented with a fair amount of confidence, it’s worth reading with a critical eye rather than as settled law — Ousterhout is describing what worked well for the systems and students he’s seen, not a universal proof. This one is worth reading early in a career, and worth revisiting a few years in once you’ve felt the pain of a shallow, leaky interface firsthand. It’s especially valuable for anyone who reviews other people’s pull requests or owns a shared library, since “does this interface hide enough complexity to be worth adding?” turns out to be one of the more concrete, teachable questions in software design. Related architecture bookDecision Makingessential-readingsoftware architecture