The second edition of Dr. John K. Ousterhout’s A Philosophy of Software Design (2022)1 offers a fairly comprehensive view of software design that serves as an excellent guide for junior engineers and a helpful framing device for more senior engineers. It is important to note that the book is focused on how complexity can be controlled in a codebase through design methods; it is very much “in the code”. The topics are focused on modules (e.g., classes, functions, etc.) and are not concerned with higher-level system design (e.g., service architecture, databases, reliability engineering, etc.). There is also no discussion of how external factors such as business requirements, user needs, or stakeholders can affect software design. Although these are important topics for any software engineer to understand, these deliberate omissions serve to focus the text for a junior audience.

Ousterhout explains the key contributors to software complexity, provides some linguistic tools to understand and communicate it, and explains a number of design methods to manage it. These topics are of most value to a junior engineer who does not have the experience to intuitively understand how complexity spreads and how to manage it. A senior engineer will find that the linguistic tools and reference frames Ousterhout provides are excellent communication tools to explain things which are understood intuitively but can be difficult to express to others in, for example, code reviews.

Chapter 3, Working Code Isn’t Enough, introduces the strategic and tactical programming strategies, which explain how design decisions can affect the long-term health of a system. As someone who works mostly in startups, this framing is helpful when discussing proposed changes within a team. Although I agree with Ousterhout that it’s not always clear (for startups) which strategy is best, it is useful to be explicit about which strategy is in play for a given change. Are you programming with an investment mindset or are you trying to quickly get a change out that will incur some technical debt? Either way, there should be a clear motivation in mind.

Similarly, Ousterhout’s terms shallow and deep modules are great tools for describing interfaces. The idea that modules should have interfaces which are simple relative to their implementations (and therefore shallow) should be obvious to any software engineer with some experience, but Ousterhout’s terms shallow and deep provide such clear language with which to communicate these ideas that the utility can’t be understated. Ousterhout also discusses information hiding (and leakage) and makes clear an excellent observation that I see many junior engineers forgetting: rote encapsulation (e.g., simply adding a private access modifier) does not necessarily simplify that module’s interface. Cognitive load (another term from the book) will be unaffected if the user still needs an understanding of how that member is used. However, the cause of improper information hiding is not discussed. One possible reason for this misconception could be the way encapsulation is commonly taught in undergraduate programs 2, but it would have been interesting to hear his perspective on this issue as he has taught many such students.

The book is also filled with helpful guardrails which ground its instruction in Ousterhout’s pragmatic style. These guardrails take the form of callouts labelled “Red Flag” and sections titled “Taking it too far” which help to refine the reader’s nose for software design quality and keep any application of the practices grounded in reality. These sections often include examples from his students.

My main criticism of this book is that it misses a major piece of software development which affects design: testing. Ousterhout briefly introduces unit and integration testing, but essentially does not discuss the topic. Testing is critical for building maintainable software and has a heavy impact on software design. The book would have benefited from a more detailed exploration of testing: designing for testability and what can make that difficult, testing legacy code, different testing approaches, and the benefits of having a comprehensive test suite. All of these topics have connections to other chapters in the book. Testing is normally missed in a junior engineer’s education, so it would have made a great addition.

Overall, I think Dr. John K. Ousterhout’s A Philosophy of Software Design (2022) provides a refined, agreeable, and comprehensive (sans testing) view on software design. Junior engineers should find the book of great value to guide their understanding. Senior engineers will appreciate the framing and communication devices.

Footnotes

  1. Ousterhout, J. K. (n.d.). A Philosophy of Software Design. United States: Yaknyam Press. These notes are from the 2nd edition (v2.01), printed in February 2022. ISBN 978-1-7321022-1-7

  2. Students are usually taught (using Java) to make fields private and to add public accessors and modifiers without motivation of why this can be a good idea. This leads to blind application of these rules and can result in leaky interfaces because important information has been hidden artificially.