Skip to content

A Philosophy of Software Design

Cover of A Philosophy of Software Design

John Ousterhout

Software Engineering · 58,500 words · 21 chapters · added 2026-11-02

Ousterhout's case that software design is mostly about reducing complexity, with deep modules, information hiding, and good comments as the main tools.

software designcomplexityabstractioncode quality

Start reading →
  • Complexity is “anything related to the structure of a software system that makes it hard to understand and modify the system.”
  • Symptoms: change amplification (a simple change needs edits in many places), cognitive load (how much a developer must know), and unknown unknowns (you can’t tell what to change). Unknown unknowns are the worst.
  • Causes: dependencies (code that “cannot be understood and modified in isolation”) and obscurity (important information is not obvious).
  • Complexity is incremental, so adopt “zero tolerance.” Design’s job, all through a system’s life, is to eliminate complexity or hide it in modules.

2. Strategic vs. tactical programming (Ch 3)

Section titled “2. Strategic vs. tactical programming (Ch 3)”
  • Tactical programming gets the current task working fast with small kludges, so complexity piles up. A tactical tornado writes code fast and leaves messes for others.
  • Strategic programming: “Working code isn’t enough.” Invest about 10–20% of development time in design, both proactively (trying alternatives, writing documentation) and reactively (fixing problems you find). It pays back within months, at startups too.
  • A module’s interface is its cost and its functionality is its benefit. The interface is “everything that a developer working in a different module must know in order to use the given module,” including informal parts that only comments can state.
  • Deep module: simple interface, powerful functionality (Unix file I/O: five calls over a whole file system). Shallow module: an interface about as complex as what it does.
  • Classitis, the belief that more, smaller classes are better, produces shallow classes. Make the common case simple: provide buffering by default, unlike Java I/O.
  • Each module should hide a few design decisions, such as data structures, algorithms, and formats. Private variables aren’t enough if getters expose them.
  • Information leakage: one decision is reflected in several modules, through interfaces or through shared “back-door” knowledge such as a file format. Merge the classes or pull the knowledge into one class.
  • Temporal decomposition (modules that follow the order of operations) causes leakage, so decompose by knowledge instead. Overexposure: callers must learn rare features to use common ones.

5. Somewhat general-purpose modules are deeper (Ch 6)

Section titled “5. Somewhat general-purpose modules are deeper (Ch 6)”
  • Functionality should reflect today’s needs, but the interface should not. A text class with insert(position, string) and delete(start, end) is deeper than one with backspace and deleteSelection, which leak UI concepts.
  • Ask: “What is the simplest interface that will cover all my current needs?” “In how many situations will this method be used?” “Is this API easy to use for my current needs?”

6. Different layer, different abstraction (Ch 7)

Section titled “6. Different layer, different abstraction (Ch 7)”
  • Adjacent layers with similar abstractions are a red flag. Pass-through methods add interface without functionality: expose the lower class, redistribute features, or merge. Same signatures are fine for dispatchers and for multiple implementations of one interface.
  • Decorators tend to be shallow. Pass-through variables belong in a context object.

“It is more important for a module to have a simple interface than a simple implementation.” Handle related complexity inside the module, and avoid configuration parameters when the module can compute a good value itself. Don’t pull in unrelated features.

8. Better together or better apart? (Ch 9)

Section titled “8. Better together or better apart? (Ch 9)”
  • Join code that shares information, is used together, overlaps conceptually, or can’t be understood alone. Separate general-purpose from special-purpose code, with the special part above: a general undo History below, grouping policy in the UI.
  • Length alone is not a reason to split a method. Split only to extract a cleanly separable subtask or to divide unrelated functions. Conjoined methods are a red flag.

“Exception handling is one of the worst sources of complexity.” Reduce the number of places that must handle exceptions, in this order:

  1. Define errors out of existence: make the case normal (Tcl unset, Java substring, Unix file deletion).
  2. Mask them low in the stack (TCP retransmits lost packets).
  3. Aggregate them in one handler, such as one around a whole request loop.
  4. Just crash for rare, unrecoverable errors, such as running out of memory.

Apply the same idea to special cases (an empty selection instead of no selection), but don’t hide what callers need for robustness.

Sketch two or more radically different alternatives for each major decision, compare them (ease of use for callers first), then choose, combine, or invent a better one. It costs an hour or two for a class.

  • “Good code is self-documenting” is a myth: code can’t state the informal interface or the rationale. Comments cost about 10% of development time at most.
  • “Comments should describe things that aren’t obvious from the code.” Lower-level comments add precision (units, boundaries, null, ownership, invariants); higher-level comments add intuition.
  • Interface comments cover behavior, arguments, return value, side effects, exceptions, and preconditions, with no implementation details. Comment every class, class variable, and method.
  • Write comments first: a hard-to-write comment signals a bad abstraction. Put cross-module decisions in one place, such as a designNotes file, keep comments near the code, and check diffs.

12. Names, consistency, and obvious code (Ch 14, 17, 18)

Section titled “12. Names, consistency, and obvious code (Ch 14, 17, 18)”
  • Names should be precise and consistent. A hard-to-pick name hints at a design problem.
  • Consistency gives “cognitive leverage.” Document conventions, enforce them, follow local style, and don’t change a convention for a “better idea.”
  • Obvious code lets a reader’s first guesses be right, and reviewers decide what is obvious. Event-driven code, generic containers like Pair, and surprising behavior make code less obvious.
Section titled “13. Modifying code and software trends (Ch 16, 19)”
  • After each change, the system should look as if it had been designed with that change in mind. “If you’re not making the design better, you are probably making it worse.”
  • Interface inheritance and unit tests help; implementation inheritance leaks state. TDD is “tactical programming pure and simple,” except for writing a failing test before a bug fix. “The increments of development should be abstractions, not features.” Don’t overuse design patterns, getters, or setters.

Know what’s expensive (network round trips, storage I/O, allocation, cache misses) and prefer naturally efficient designs that are just as simple. Measure before modifying. Prefer a fundamental fix; otherwise design around the critical path, with one test that diverts all special cases. The RAMCloud Buffer redesign became about 2x faster and 20% smaller.

Shallow Module, Information Leakage, Temporal Decomposition, Overexposure, Pass-Through Method, Repetition, Special-General Mixture, Conjoined Methods, Comment Repeats Code, Implementation Documentation Contaminates Interface, Vague Name, Hard to Pick Name, Hard to Describe, and Nonobvious Code. Any of them suggests a design problem. The table with symptoms and the 15 design principles are in ch21.


# Title Key Frameworks
ch01 Introduction (+ Preface) Complexity as the limit, design as continuous, red flags
ch02 The Nature of Complexity Definition, three symptoms, dependencies and obscurity, incremental complexity
ch03 Working Code Isn’t Enough Tactical vs. strategic, tactical tornado, 10–20% investment, startups
ch04 Modules Should Be Deep Interface vs. implementation, abstraction, deep vs. shallow, classitis, Unix and Java I/O
ch05 Information Hiding (and Leakage) Hiding, leakage, temporal decomposition, HTTP example, defaults, overexposure
ch06 General-Purpose Modules are Deeper Somewhat general-purpose, text class APIs, three questions, push specialization out
ch07 Different Layer, Different Abstraction Pass-through methods, dispatchers, decorators, pass-through variables, context object
ch08 Pull Complexity Downwards Simple interface over simple implementation, configuration parameters, going too far
ch09 Better Together Or Better Apart? Join vs. split criteria, general vs. special code, undo history, splitting methods
ch10 Define Errors Out Of Existence Define away, mask, aggregate, crash, special cases, NFS and RAMCloud
ch11 Design it Twice Radically different alternatives, comparison criteria, cost
ch12 Why Write Comments? The Four Excuses Self-documenting myth, time, staleness, worthless comments, comments as abstraction
ch13 Comments Should Describe Things that Aren’t Obvious from the Code Four comment types, precision vs. intuition, interface comments, cross-module docs
ch14 Choosing Names Create an image, precision, consistency, distance rule, Go style
ch15 Write The Comments First Comments as a design tool, early warning, cost
ch16 Modifying Existing Code Stay strategic, maintaining comments, check diffs
ch17 Consistency Cognitive leverage, documenting and enforcing, When in Rome, don’t change conventions
ch18 Code Should be Obvious Names, whitespace, event-driven code, generic containers, reader expectations
ch19 Software Trends Inheritance, agile, unit tests, TDD, design patterns, getters and setters
ch20 Designing for Performance Expensive operations, measure first, fundamental fixes, critical path, Buffer
ch21 Conclusion (+ Summaries) 15 design principles, 14 red flags with symptoms
  • Abstraction → ch04
  • Agile development → ch19
  • Classitis → ch04
  • Code reviews → ch01, ch17, ch18
  • Cognitive load → ch02
  • Comments, cross-module and designNotes → ch13, ch16
  • Comments, excuses → ch12
  • Comments, interface vs. implementation → ch13
  • Comments, maintaining → ch16
  • Comments, writing first → ch15
  • Complexity, definition and symptoms → ch02
  • Configuration parameters → ch08
  • Conjoined methods → ch09
  • Consistency and conventions → ch17
  • Context object → ch07
  • Critical path → ch20
  • Decorators → ch07
  • Deep vs. shallow modules → ch04
  • Defaults → ch04, ch05
  • Define errors out of existence → ch10
  • Dependencies and obscurity → ch02
  • Design it twice → ch11
  • Design patterns → ch17, ch19
  • Dispatchers → ch07
  • Event-driven programming → ch18
  • Exception masking and aggregation → ch10
  • General-purpose vs. special-purpose → ch06, ch09
  • Generic containers (Pair) → ch18
  • Getters and setters → ch19
  • Information hiding and leakage → ch05
  • Inheritance → ch19
  • Invariants → ch13, ch17
  • Just crash → ch10
  • Measuring performance → ch20
  • Modifying existing code → ch16
  • Names → ch14
  • Obvious code → ch18
  • Overexposure → ch05
  • Pass-through methods and variables → ch07
  • Pull complexity downwards → ch08
  • Red flags (full list) → ch21
  • Splitting methods → ch09
  • Startups and design → ch03
  • Strategic vs. tactical programming → ch03
  • Tactical tornado → ch03
  • Temporal decomposition → ch05
  • Test-driven development → ch19
  • Undo history example → ch09
  • Unit tests → ch19
  • Unknown unknowns → ch02
  • White space → ch18

This skill covers the book’s content only: synthesized summaries, not the book text. It reflects the first edition (v1.01, November 2018), so material added in later editions is not included. Chapter 1 includes the Preface, and chapter 21 includes the book’s Summary of Design Principles and Summary of Red Flags. The text extractor did not read the EPUB’s 17 images, so the 15 content images were viewed directly and their content was added to the chapters. They are the 14 numbered figures (the Web-site banner, the tactical vs. strategic curve, deep and shallow modules, the HTTP request, the pass-through method and pass-through variable diagrams, the packet-logging code, method splitting, the Web-server exception code, and the three Buffer figures) and the complexity formula in chapter 2. The other 2 were the cover and the red-flag icon. Performance figures are the author’s 2018 numbers. The book does not recommend any book already in this library, so there are no cross-references. For topics beyond this book, check related skills or ask the agent directly.