login as:
~/abapcraft.dev — code, crafted in SAP
florin@s4hana:~/abap/posts/book-philosophy-of-software-design $ cat README.md

A Philosophy of Software Design — John Ousterhout

A professor and software engineer's case for fighting complexity as the central challenge of software development — with some deliberately unconventional positions.

What the book is about

Written by John Ousterhout — a professor at Stanford and the creator of the Tcl scripting language — A Philosophy of Software Design argues that the central challenge of software engineering is managing complexity. Not performance, not correctness, not feature delivery. Complexity.

Ousterhout draws on decades of academic research and real-world engineering experience to make his case, and he is not afraid to challenge widely accepted conventions along the way. Some of his positions are deliberately contrarian — and the arguments he makes for them are worth taking seriously, even where you disagree.

What is complexity?

Ousterhout defines complexity precisely, in terms a developer can recognise immediately:

  • A change requires modifying code in more than one place. When a single logical decision is scattered across the codebase, every change to that decision is multiplied. This is complexity as fragility.
  • Developers need to keep a lot of information in their heads to complete a task. When understanding one part of the system requires understanding many others simultaneously, the cognitive load of every task grows beyond what a human mind can comfortably hold.
  • It is not obvious what information or changes are needed. When the effect of a change is unpredictable — when you cannot tell what will break or what needs to be updated — the system has become opaque.

These three symptoms — change amplification, cognitive load, and obscurity — are how complexity manifests in practice. The rest of the book is about avoiding them.

How systems become too complex

Complexity rarely arrives all at once. It accumulates through two root causes:

Dependencies — when one module knows too much about another, changes propagate. The more tightly coupled the system, the more any change ripples through it.

Obscurity — when the code does not make its intent, behavior, or assumptions visible, every developer who works on it carries the cost of rediscovering what was already known. Obscurity is complexity hidden in plain sight.

The right mindset: tactical vs. strategic

One of the book’s most important ideas is the distinction between tactical and strategic programming. A developer with a tactical mindset optimises for getting things done as quickly as possible — the fastest path to working code, regardless of the design it produces. A developer with a strategic mindset invests in design and understanding upfront, accepting a slower start in exchange for a system that stays manageable as it grows.

Ousterhout’s argument is that tactical programming is a trap. The time saved today is borrowed from the future, with interest. Teams that optimise for speed consistently produce systems that slow them down — not because they were careless, but because they never invested in the design that would have kept complexity under control.

How to design good modules

The heart of the book is Ousterhout’s theory of module design:

Favour deep modules over shallow ones. A deep module has a simple interface but hides a large amount of complexity behind it. A shallow module has an interface nearly as complex as its implementation — it adds a layer without reducing complexity. The goal is to push complexity downward, behind clean interfaces, so that callers do not need to know about it.

Provide good defaults. A module that does the right thing automatically — without requiring the caller to configure or understand its internals — reduces the cognitive load of every caller. Defaults should represent the most common, most correct use.

Hide inessential information. Everything a module exposes is something the caller must understand and the designer must maintain. Expose only what callers genuinely need.

Eliminate unnecessary exception handling. Ousterhout takes a strong position here: many exceptions represent complexity pushed outward to the caller. If an exception can be handled internally — absorbed, ignored, or defaulted — it should be. Exceptions that callers cannot meaningfully handle should not be raised at all.

Write good comments. This is one of Ousterhout’s most contrarian positions: he argues directly against the common advice that good code does not need comments. His view is that comments capture the why and the what at a higher level — the intent, the invariants, the design decisions — in a way that code cannot. Comments are not a sign of unclear code; they are a complement to it.

Personal statement

This book illustrates great ideas about high-level software design from someone who has thought about them both academically and in practice. Ousterhout is willing to argue against conventions — the case for comments, the critique of exception-heavy APIs, the preference for deep modules over the shallow decomposition that clean code orthodoxy sometimes encourages — and he makes those arguments carefully.

It is a book for developers who already have experience and are ready to encounter solid opinions that challenge what they think they know. The ideas are not always easy to accept, but they are always worth engaging with.

Among the books in this series, it stands slightly apart — less focused on technique and more focused on the mental model behind good design. Read after the others, it adds a useful counterpoint.