Index / 001 EN
A Philosophy of Software Design cover

Computer Science

Computer Science

A Philosophy of Software Design

John Ousterhout

A short book on the central problem of software design: managing complexity. Drawing on the author's experience teaching a design-focused software course, it argues for deep modules with simple interfaces, information hiding and careful naming and comments, and offers a list of warning signs that indicate a design is becoming harder to understand and change.

Prerequisites: Experience writing and maintaining programs of moderate size in any language, such as a substantial course project.

Difficulty Level
Beginner
Academic Level
Undergraduate
software designcomplexitymodularityabstractioncode quality

01 / Reading Guide

Reading Guide

Citation

Ousterhout, J. (2021). A Philosophy of Software Design (2nd ed.). Yaknyam Press.

Chapter Summary

A short book of twenty-two chapters (under 200 pages) about one problem: how to decompose software into modules, such as classes and methods, so that the system's complexity stays manageable. It grew out of a software design course the author teaches at Stanford, and many of its examples come from student projects such as a GUI text editor. The back matter collects a summary of design principles and a summary of "red flags", warning signs of design problems introduced throughout the text. According to the author, the second edition changes little: it adds Chapter 21, reworks and expands Chapter 6 (absorbing material from other chapters), and adds sections comparing the book's views with Robert Martin's Clean Code.

Chapters 1–3: Introduction; The Nature of Complexity; Working Code Isn't Enough

Chapter 2 defines complexity, names its symptoms (change amplification, cognitive load, unknown unknowns) and its causes (dependencies and obscurity), and argues that it accumulates incrementally. Chapter 3 contrasts tactical with strategic programming, an investment mindset in which a little more time spent on design up front saves time later, and discusses how much to invest, including in startups.

Chapter 4: Modules Should Be Deep

The central metaphor: a deep module provides powerful functionality behind a simple interface, a shallow one adds interface without hiding much. The chapter covers interfaces and abstractions, warns against "classitis" (many small classes), and compares Unix and Java I/O as examples.

Chapter 5: Information Hiding (and Leakage)

Explains information hiding and its failure mode, information leakage, including temporal decomposition. A series of examples built around an HTTP server covers too many classes, parameter handling and defaults in responses.

Chapter 6: General-Purpose Modules are Deeper

The author argues that over-specialization may be the single greatest cause of complexity. Using a text class from the editor project, the chapter shows a general-purpose API (insert and delete ranges) replacing methods tied to particular keys, lists questions for finding the right balance, explains pushing specialization up or down (device drivers as an example), separates a general-purpose undo history from special-purpose actions, and argues for eliminating special cases in code.

Chapters 7–8: Different Layer, Different Abstraction; Pull Complexity Downwards

Chapter 7 treats pass-through methods and variables, decorators and the difference between interface and implementation as signs that adjacent layers repeat the same abstraction. Chapter 8 argues that a module's implementer, not its users, should absorb complexity, with examples from the editor's text class and from configuration parameters, and warns against taking the idea too far.

Chapter 9: Better Together Or Better Apart?

Gives criteria for combining code (shared information, simpler interfaces, removing duplication), examples from the editor, and a discussion of splitting and joining methods. A new section disputes Clean Code's advice that functions should be very short: depth matters more than length.

Chapter 10: Define Errors Out Of Existence

Argues that exceptions are a major source of complexity and shows how to reduce them: define APIs so that errors cannot occur (with file deletion in Windows and Java's substring method as examples), mask exceptions at a low level, aggregate their handling, consider when simply crashing is acceptable, and design special cases out of existence.

Chapter 11: Design it Twice

A short chapter recommending that designers consider more than one design before settling on one.

Chapters 12–16: Why Write Comments? The Four Excuses; Comments Should Describe Things that Aren't Obvious from the Code; Choosing Names; Write The Comments First; Modifying Existing Code

The longest block of the book is about documentation. Chapter 12 answers four common excuses for not commenting and, in the second edition, rejects Clean Code's view that comments are failures. Chapter 13 explains what comments should add: precision at a low level, intuition at a high level, interface documentation, and cross-module design decisions. Chapter 14 asks for precise, consistent names that create a clear image. Chapter 15 argues for writing comments before code as a design tool. Chapter 16 applies the strategic mindset to modifying existing code and keeping its comments current.

Chapters 17–18: Consistency; Code Should be Obvious

Chapter 17 gives examples of consistency, ways to ensure it and a warning against taking it too far; Chapter 18 lists things that make code more and less obvious to a reader.

Chapter 19: Software Trends

Evaluates object-oriented inheritance, agile development, unit tests, test-driven development, design patterns, and getters and setters in the light of the book's principles.

Chapter 20: Designing for Performance

Covers how to think about performance, measuring before modifying, and designing around the critical path, with an extended example from the author's RAMCloud system.

Chapters 21–22: Decide What Matters; Conclusion

The new Chapter 21 presents good design as separating what matters from what does not: look for leverage, minimize what matters, emphasize important things through prominence, repetition and centrality, and avoid both treating too much as important and missing what is. The book closes with a brief conclusion.

Key Concepts

Chapters 1–3: Complexity and Mindset

  • Complexity: the problem the whole book is about; Chapter 2 traces it to dependencies and obscurity.
  • Change amplification, cognitive load, unknown unknowns: the three symptoms by which complexity shows itself.
  • Strategic versus tactical programming: investing in design continually rather than only making the next feature work.

Chapters 4–10: Module Design

  • Deep module: a module whose simple interface hides substantial functionality.
  • Information leakage: a design decision reflected in several modules, so that a change affects all of them.
  • Somewhat general-purpose: an interface general enough for several uses while the implementation meets current needs.
  • Pull complexity downwards: let the implementer handle complexity rather than every caller.
  • Define errors out of existence: design interfaces so that error cases do not arise.

Chapters 11–18: Process, Comments and Readability

  • Design it twice: consider alternative designs before choosing one.
  • Comments as a design tool: writing comments before the code, so that they shape the design rather than describe it afterwards.
  • Obvious code: code a reader can understand quickly without much thought.

Chapters 19–22: Trends, Performance and Priorities

  • Critical path: the code that must run in the performance-critical case, which the book's RAMCloud example keeps free of unnecessary method calls and special-case checks.
  • Red flags: named symptoms, collected at the end of the book, that indicate a design problem.
  • Deciding what matters: identifying the few important aspects of a design and structuring the system around them.

Critical Analysis

Strengths

  • One idea, consistently applied. Every chapter returns to complexity and its causes, which makes the book easy to remember and its advice easy to apply in code review.
  • Concrete, classroom-tested examples. The editor, HTTP-server and undo examples come from the author's course and show both the flawed student design and a better one; the red-flag list turns the principles into a usable checklist.
  • Willing to take positions. The book argues against common advice, most explicitly in the second edition's disagreements with Clean Code on method length and on comments, and in its assessment of trends such as test-driven development and getters and setters. It gives students something to test against their own experience.
  • Short. At under 200 pages it can be read alongside a project course without displacing other work.

Limitations

  • Module-level design only. The book concerns classes, methods and interfaces inside a program. It does not cover requirements, architecture at the scale of distributed systems, testing practice, teams or process, which other books in the course address.
  • Grounded in one person's experience. The author presents it as a philosophy drawn from his teaching and systems work, and the evidence is examples and argument rather than empirical study; readers may disagree with specific positions, as the Clean Code exchanges show.
  • Little for readers without project experience. The examples assume familiarity with classes, interfaces and exceptions, mostly in Java-style code, and the advice lands best after struggling with a program of some size.
  • Few changes from the first edition. The author himself notes that owners of the first edition may not need the second, and makes the new material available as a free extract.

Where It Fits in the Course

In the software-engineering course this is the book about the design of code itself. Thomas and Hunt's The Pragmatic Programmer is the closest companion: it covers a wider range of working habits and practices in short topics, while Ousterhout goes deeper on one question. Reading the two together gives both breadth and a consistent design vocabulary.

Sommerville's Software Engineering covers the whole life cycle, from requirements to evolution; Software Engineering at Google deals with culture, process and tooling at organisational scale; Bass, Clements and Kazman's Software Architecture in Practice treats design decisions at the level of whole systems and quality attributes; Jackson's Software Abstractions introduces formal modelling of designs with Alloy. Ousterhout prepares students for all of them by making the cost of complexity concrete, and is best read early, during or just before a substantial programming project.

How to Use This Book

The book suits students who have written and maintained programs of moderate size, such as a substantial course project, and working programmers. No particular language is required, although many examples use Java-like code.

  • Core: Chapters 1–10, which build the vocabulary (complexity, deep modules, information hiding, general-purpose interfaces, errors) used everywhere else.
  • Documentation: Chapters 12–15 are the most directly practical; read them before a project's first code review.
  • Optional or later: Chapters 19–20 on trends and performance, and Chapter 21 as a summary of the book's priorities.

Read with your own code open. After each chapter, review a module you wrote against the red flags listed at the end of the book, and try "design it twice" on the next interface you write. The comparison with Clean Code makes a good discussion exercise: read Martin's position and Ousterhout's reply and decide which applies to your codebase.