John Ousterhout's A Philosophy of Software Design proposes a single, central framing that most other design advice implicitly assumes but rarely states directly: the primary goal of good software design is managing complexity, and every design decision should be evaluated primarily by whether it makes the system's overall complexity better or worse — not by whether it follows a specific named principle or pattern, which the book treats as secondary and sometimes even in tension with the actual goal. The book and its central theme are introduced on the official page for A Philosophy of Software Design.

Defining complexity concretely

The book's most useful contribution is refusing to leave “complexity” as a vague, unmeasurable feeling, and instead breaking it into specific, recognizable symptoms: change amplification (a simple conceptual change requires modifying code in many different places), cognitive load (understanding a piece of code requires holding a large amount of unrelated context in your head at once), and unknown unknowns (it's not obvious what code needs to change to accomplish a given task, or what the consequences of a change will be). Naming these three symptoms specifically gives designers something concrete to evaluate a design against, rather than a general sense of whether the code “feels” complex. Documentation and tools often act as external memory; this guide explains the related idea of cognitive offloading.

This framing also explains why well-intentioned design principles sometimes make things worse: a principle applied to reduce one symptom of complexity can increase another. Splitting a large class into many smaller ones, for instance, can reduce cognitive load for any single piece but increase change amplification, if a common conceptual change now requires touching several of the newly split-out pieces instead of one. The book's insistence is that the actual measure of success is the net effect on complexity, not adherence to any individual rule.

Deep modules over shallow ones

A specific, influential idea from the book is the distinction between deep and shallow modules. A deep module has a simple interface that hides substantial, genuinely complex functionality behind it — the complexity exists, but it's contained, and a caller doesn't need to understand it to use the module correctly. A shallow module has an interface roughly as complex as its implementation, meaning it doesn't actually reduce the caller's cognitive burden much — it's a wrapper more than an abstraction. The book argues that many small, narrowly-scoped classes, produced by applying principles like Single Responsibility (discussed in the SOLID guide on this shelf) too literally, tend to produce shallow modules: individually simple, but numerous enough that using them correctly requires understanding how many of them fit together, which doesn't actually reduce overall complexity, just redistributes it.

Where this pushes back on other advice on this shelf

This framing puts A Philosophy of Software Design in productive tension with some of the more rule-based advice discussed elsewhere — SOLID's Single Responsibility Principle and Clean Code's preference for very short functions, both discussed elsewhere on this shelf, can, applied too literally, produce exactly the shallow-module problem Ousterhout describes: many small, individually clean pieces whose collective complexity is higher than one well-designed, deeper module would have been. This isn't a rejection of those principles so much as a reminder that they're heuristics in service of a larger goal, and the larger goal — less overall complexity — is the thing to actually check a design against.

Complexity, not any specific bad practice, is what makes software hard to work with. A design principle is only as good as its actual effect on change amplification, cognitive load, and unknown unknowns — which sometimes means fewer, deeper modules rather than more, smaller ones.

Read alongside the other design guides on this shelf, this book functions less as a rival methodology and more as a check on the others: a useful question to ask of any specific piece of advice — SOLID, a named pattern, a strict function-length rule — is whether applying it here actually reduces complexity, or just relocates it somewhere less visible.