Summary of A Philosophy of Software Design by John Ousterhout
Source: danlebrero.com
These are notes by Daniel Lebrero Berna on John Ousterhoutâs A Philosophy of Software Design.
Some advice in the book goes against the current software dogma. The current dogma is the result of previous pains, but has now been taken to the extreme, causing new pains.
What the author solves with âComment-First Development,â others solve with Test-Driven Development. The excuses for not writing comments mirror those for not writing tests.
Key Insights
Itâs easier to see design problems in someone elseâs code than your own.
Total complexity = ÎŁ(complexity of part Ă time spent on that part).
Goal of good design: make the system obvious.
Complexity accumulates incrementally, making it hard to remove. Adopt a âzero toleranceâ philosophy.
Better modules: interface much simpler than implementation (Deep modules).
Design modules around required knowledge, not task order.
Adjacent layers with similar abstractions are a red flag.
Prioritize simple interfaces over simple implementations.
Each method should do one thing and do it completely.
Long methods are fine if the signature is simple and the code easy to read.
Difficulty naming a method may indicate unclear design.
Comments should add precision or intuition.
If you arenât improving the design when changing code, youâre probably making it worse.
Comments belong in the code, not commit logs.
Poor designers spend most of their time chasing bugs in brittle code.
Preface
The most fundamental problem in computer science is problem decomposition.
The book is an opinion piece.
The goal: reduce complexity.
1. Introduction (Itâs All About Complexity)
Fight complexity by simplifying and encapsulating it in modules.
Software design is never finished.
Design flaws are easier to see in othersâ code.
2. The Nature of Complexity
Complexity = what makes code hard to understand or modify.
Total complexity depends on time spent in each part.
Complexity is more obvious to readers than writers.
Symptoms: change amplification, cognitive load, unknown unknowns.
Causes: dependencies, obscurity.
Complexity accumulates incrementally; remove it aggressively.
3. Working Code Isnât Enough
Distinguish tactical (short-term) from strategic (long-term) programming.
The âtactical tornadoâ writes lots of code fast but increases complexity.
4. Modules Should Be Deep
A module = interface + implementation.
Deep modules have simple interfaces, complex implementations.
Interface = what clients must know (formal + informal).
Avoid âclassitisâ: too many small classes increase system complexity.
Interfaces should make the common case simple.
5. Information Hiding (and Leakage)
Information hiding is key to deep modules.
Avoid temporal decomposition (ordering-based design).
Larger classes can improve information hiding.
6. General-Purpose Modules Are Deeper
Make modules somewhat general-purpose.
Implementation fits current needs; interface supports future reuse.
Questions to balance generality:
What is the simplest interface covering current needs?
How many times will it be used?
Is the API simple for current use? If not, itâs too general.
7. Different Layer, Different Abstraction
Adjacent layers with similar abstractions are a red flag.
Pass-through methods and variables add no value.
Fix pass-throughs by grouping related data or using shared/context objects.
8. Pull Complexity Downwards
Prefer simple interfaces over simple implementations.
Push complexity into lower layers.
Avoid configuration parameters; compute reasonable defaults automatically.
9. Better Together or Better Apart?
Combine elements when they:
Share information.
Are used together.
Overlap conceptually.
Simplify interfaces or eliminate duplication.
Developers often split methods too much.
Methods can be long if they are cohesive and clear.
Red flag: one component requires understanding anotherâs implementation.
10. Define Errors Out of Existence
Exception handling increases complexity.
Reduce exception points by:
Designing APIs that eliminate exceptional cases.
Handling exceptions at low levels.
Aggregating exceptions into a common type.
Crashing when appropriate.
11. Design It Twice
Explore at least two radically different designs before choosing.
12. Why Write Comments? The Four Excuses
Writing comments improves design and can be enjoyable.
Excuses:
âGood code is self-documenting.â False.
âNo time to write comments.â Itâs an investment.
âComments get outdated.â Update them.
âComments are worthless.â Learn to write better ones.
13. Comments Should Describe Things That Arenât Obvious
Comments should add precision and intuition.
Document both interface and implementation.
14. Choosing Names
Names should be precise and consistent.
If naming is hard, the design likely isnât clean.
15. Write the Comment First
Like TDD, comment-first helps design, pacing, and clarity.
16. Modifying Existing Code
Always improve design when changing code.
Comments belong in code, not commit logs.
17. Consistency
Donât âimproveâ existing conventions without strong reason.
19. Software Trends
Agile and TDD often promote tactical programming.
20. Designing for Performance
Simpler code tends to be faster.
Design around the critical path.
21. Conclusion
Poor designers spend their time debugging brittle systems.