Library · Summary & review

A Philosophy of Software Design

By John Ousterhout. The Stanford professor's case for deep modules, and the most argued rebuttal to Clean Code.

FR EN
A Philosophy of Software Design book cover, John Ousterhout

A Philosophy of Software Design

A Philosophy of Software Design, 2nd Edition

8 /10

« A hundred and ninety-six pages against complexity, not one word wasted. »

  • AuthorJohn Ousterhout · « créateur de Tcl/Tk »
  • OriginalYaknyam Press, 2021 · 196 pages
  • Edition2nd edition (2021), English only
  • This page~10 min read
Book rating across 5 dimensionsIdeas9/10Practical7/10Readability9/10Aged well9/10Examples6/10

The counterweight to Clean Code: deep modules, simple interfaces, by a Stanford professor.

Why this book

Once you've read Clean Code, you eventually run into Ousterhout's name. His is the book people quote to contradict you: where Robert Martin preaches three-line functions, Ousterhout demonstrates, with examples, that chopping code too finely makes complexity worse, not better. The two books disagree, and that's exactly why you should read both.

Ousterhout isn't a consultant: he's a Stanford professor who built real systems (the Tcl language, the log-structured file system, the RAMCloud storage system). The book grew out of his software design course, where students write a program, critique it, then rewrite it. The result: 196 pages, zero filler, and a single obsession from start to finish, complexity. He warns you himself: "every problem described here is one that I have experienced personally, and every suggested technique is one that I have used successfully in my own coding."

The ideas that stick

The book runs 196 pages across 22 short chapters. Here is what stays with you once it's closed.

1Complexity has a definition, and it's sneaky

Ousterhout refuses to talk about complexity "in general". He defines it: anything that makes a system hard to understand and modify. It shows up three ways:

  • Change amplification: changing a banner's color forces you to edit every page that copies it.
  • Cognitive load: everything a developer must hold in their head to touch one line without breaking something.
  • Unknown unknowns (the worst): you don't even know which piece of code needs changing, you find out when the bug appears.

Only two things feed all of this: dependencies and obscurity. And the line that hurts: "complexity is more apparent to readers than writers" (p. 9). The person who writes the code doesn't feel it; the one who inherits it does.

2A good module is deep

This is the core idea of the book, and the sharpest counter to Clean Code. A deep module packs a lot of functionality behind a simple interface: "the best modules are those whose interfaces are much simpler than their implementations" (p. 22). The perfect example: Unix's five I/O calls (open, read, write, lseek, close). Five trivial signatures hiding hundreds of thousands of lines (permissions, disk caches, scheduling, drivers).

The opposite, a shallow module, has an interface almost as complicated as what it does: it costs you in learning without saving you anything. Ousterhout names the disease: "classitis", the belief that "classes are good, so more classes are better". Stacking one-method mini-classes simplifies nothing, it just adds interfaces to learn. That's why he stands flatly against the cult of the tiny function.

3Tactical vs strategic: beware the tornado

Working code isn't enough. The tactical programmer focuses on the next feature, ships fast, and piles up complexity with every shortcut. The strategic programmer treats good design as the goal, not a bonus.

Ousterhout describes a figure everyone has met: the "tactical tornado", the ultra-productive developer who pumps out code faster than anyone, adored by management, and who leaves behind "a wake of destruction" that others spend months cleaning up. His advice: invest 10 to 20% of your time in design, continuously. Because technical debt, unlike a bank loan, is almost never repaid.

A programmer hero lifted on the shoulders of a cheering crowd in the foreground, while behind them a tornado of tangled cables and flying paper leaves a trail of toppled office chairs and desks
The tactical tornado: cheered up front, a trail of damage behind.

4Hide information, don't decompose by time

The main way to get deep modules: information hiding, a principle David Parnas already laid out in 1972. Each module encapsulates a design decision that doesn't show in its interface (a file format, a network protocol, a tree structure).

The opposite mistake is temporal decomposition: structuring code by order of execution. You read a file, you modify it, you write it back, so you create three classes: Read, Process, Write. Except the class that reads and the one that writes both need to know the file format. The same decision lives in two places: that's a leak, "one of the most important red flags in software design" (p. 31). The day the format changes, you edit two classes instead of one. The right answer: a single module that knows the format, full stop.

5Pull complexity downwards

Faced with unavoidable complexity, you have two choices: handle it inside the module, or push it onto everyone who uses it. Ousterhout is blunt: "it is more important for a module to have a simple interface than a simple implementation" (p. 61). In other words, "it is better for the developers to suffer than the users" (p. 63): one team absorbs the difficulty inside, rather than a thousand callers suffering it outside.

A concrete example, in code:

# ✗ complexity PUSHED out: every caller has to guess the value
client = Client(retry_interval=0.5)   # 0.5? 2? nobody knows how to tune it

# ✓ complexity PULLED in: the module measures real times and decides
client = Client()                     # bare interface, one team handles it, a thousand callers relieved

6Define errors out of existence

The most original and most underrated idea in the book. Exception handling is "one of the worst sources of complexity". The best defense isn't catching them better: it's redefining the semantics so the error can't happen.

His own confession: designing the Tcl language, he made deleting a nonexistent variable raise an error. The result, everyone wrapped the call in a catch to ignore it. The right definition: "ensure that a variable no longer exists". With that, deleting an absent variable becomes normal, the error is gone. Same logic for Java's substring, which throws if the index exceeds the length, forcing you to write guard checks everywhere. Python chose the opposite: an out-of-range slice simply returns an empty result. No exception, no guard code, a deeper method.

// Java: throws IndexOutOfBoundsException, needs guards everywhere
"hi".substring(0, 10);   // 💥 exception

# Python: out-of-range slice = empty result, no error
"hi"[0:10]               # → "hi"

7The head-on clash with Clean Code

This is where the two books collide. Ousterhout quotes Robert Martin word for word: "the first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that." His answer: past a certain size, cutting further helps no one.

// ✗ split too finely: you can't grasp one without jumping to the other
function readPrice(c) { return c.price }         // "conjoined methods"
function withVat(c)   { return readPrice(c) * 1.2 }

// ✓ one "deep" function: self-contained, readable in one block
function priceWithVat(c) { return c.price * 1.2 }

Functions split too finely lose their independence and become conjoined methods (you can't understand one without reading the other), which is worse than one slightly long function. His line: "depth is more important than length".

Same fight over comments. Martin writes "comments are always failures"; Ousterhout is a fierce defender of comments, because they capture what code can't: the abstraction, the intent, the why. He mocks Martin's fix (replace a comment with a very long method name), which ends up producing names like isLeastRelevantMultipleOfNextLargerPrimeFactor.

He goes further: write the comments first, before the code. Not for documentation, as a design tool. If a comment gets long and painful to write, it's "the canary in the coal mine": a sign your abstraction is bad.

8The opinions that sting, and the red flags

The book's most contrarian chapter runs every trend past a single question: does it reduce complexity? The verdict is sharp. Against Test-Driven Development (except to fix a bug): "it focuses attention on getting specific features working, rather than finding the best design. This is tactical programming pure and simple". Against getters and setters, "shallow methods that clutter the interface without providing much functionality". Wariness toward design patterns, whose real danger is over-application: don't force a problem into a pattern when a custom solution would be cleaner.

And above all, the book's practical gift: its list of red flags, usable in code review tomorrow:

  • shallow module;
  • information leakage;
  • temporal decomposition;
  • pass-through method;
  • conjoined methods;
  • comment repeats the code;
  • vague name;
  • nonobvious code.

When one of these signals shows up, stop: there's probably a hidden design problem behind it.

Three things I didn't know

My take, honestly

To me, this is the best software design book of the last twenty years, and by far the most pleasant to read. Where Clean Code is verbose and dogmatic, Ousterhout is short, clear, and argues instead of asserting. The deep-module idea changed how I judge a class: I no longer count lines, I look at the ratio between what the interface costs to learn and what it gives me back.

What keeps it from being perfect: the examples. They almost all revolve around the same two or three cases (a text editor, the RAMCloud system), and it smells of the lecture hall. You wish for more real-world code, more variety. And some ideas, like "make your modules deep", are easier to understand than to apply: they take judgment, not a recipe. It's not a checklist, it's a way of thinking, which makes it both deeper and less immediate than Clean Code.

The right move is to read both and make up your own mind. Where they contradict each other, on function size, on comments, Ousterhout is almost always right.

Odilon

Still relevant in 2026?

More than ever, for a reason nobody anticipated. The book depends on no language and no trend: it's about complexity, the one problem that will never go away. And at a time when AI generates a good share of the code, telling a deep module from a shallow one becomes a superpower. AI happily produces chatty interfaces and shallow classes; that's exactly what this book teaches you to spot and reject. Ousterhout's grid is precisely the critical eye a machine that outputs plausible code is missing.

Who is it for?

Read it if

  • You've read Clean Code and want to hear the other side, better argued
  • You design libraries or APIs and want to make them simple to use
  • You inherit code that "works" but nobody dares touch, and you want to name why
  • You review AI-generated code and want criteria to judge an interface

Skip it if

  • You're a complete beginner: learn to code first, design comes after
  • You want ready-to-copy recipes: this is a book of judgment, not a catalogue
  • You want varied examples from the modern web: here it's the text editor and RAMCloud

Going further

Several ideas from this book can be practiced in my free courses: splitting responsibilities and designing interfaces in the OOP course, naming and structuring in the testing course, and the reflex of reviewing generated code in Coding with AI.

Comments (0)

Browse the whole library

More book notes coming: one book at a time, the marrow only.