A Philosophy of Software Design
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.
Start reading →Core Frameworks & Mental Models
Section titled “Core Frameworks & Mental Models”1. Complexity is the enemy (Ch 1–2)
Section titled “1. Complexity is the enemy (Ch 1–2)”- 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.
3. Modules should be deep (Ch 4)
Section titled “3. Modules should be deep (Ch 4)”- 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.
4. Information hiding and leakage (Ch 5)
Section titled “4. Information hiding and leakage (Ch 5)”- 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)anddelete(start, end)is deeper than one withbackspaceanddeleteSelection, 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.
7. Pull complexity downwards (Ch 8)
Section titled “7. Pull complexity downwards (Ch 8)”“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
Historybelow, 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.
9. Define errors out of existence (Ch 10)
Section titled “9. Define errors out of existence (Ch 10)”“Exception handling is one of the worst sources of complexity.” Reduce the number of places that must handle exceptions, in this order:
- Define errors out of existence: make the case normal (Tcl
unset, Javasubstring, Unix file deletion). - Mask them low in the stack (TCP retransmits lost packets).
- Aggregate them in one handler, such as one around a whole request loop.
- 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.
10. Design it twice (Ch 11)
Section titled “10. Design it twice (Ch 11)”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.
11. Comments (Ch 12, 13, 15, 16)
Section titled “11. Comments (Ch 12, 13, 15, 16)”- “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
designNotesfile, 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.
13. Modifying code and software trends (Ch 16, 19)
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.
14. Designing for performance (Ch 20)
Section titled “14. Designing for performance (Ch 20)”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.
15. The red flags (Ch 21)
Section titled “15. The red flags (Ch 21)”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.
Chapter Index
Section titled “Chapter Index”| # | 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 |
Topic Index
Section titled “Topic Index”- 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
Scope & Limits
Section titled “Scope & Limits”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.