Book report: A Philosophy of Software Design, John Ousterhout
I’ve recently been re-reading A Philosopy of Software Design by John Ousterhout.
The origin of the book is interesting. The author taught a university course on software design in which students worked together to build software projects, then perform code reviews and retrospectives to analyze weaknesses and improve on the design. The book is then based on common themes and patterns that the author observed after teaching the class three times.
In this post I’m not going write a full review. It wouldn’t be especially timely (the author published this in 2018) and I don’t want to try to summarize the whole thing. Instead I’m just going to highlight some of the book’s points that I’m carrying with me.
The book’s main theme is “complexity”: how to recognize unnecessary complexity, and what you can do to reduce it. What does the author mean by complexity?
Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system.1
This definition makes sense to me: it emphasizes both perceiving a system and then, on the more practical side, changing it.
Ousterhout’s view is that complexity arises gradually through the introduction of dependencies (one cannot understand or change a piece of code in isolation) and obscurity (when important information is not obvious).
Working code isn’t good enough
Every software engineer has been there–you’ve worked hard to track down and fix a bug, or complete a feature within a deadline. You push the patch and call it a day. Ousterhout’s point is to not stop at working code, or not even really to have this as your primary mindset. He calls the “just get it working” approach as tactical programming whereas to avoid accumulation complexity, we need to focus on strategic programming:
“You should not think of “working code” as your primary goal […]. Your primary goal must be to produce a great design, which also happens to work. This is strategic programming.2
One thing that follows from this mindset is that you should also be revisiting your previous work and reworking / refactoring to better support the current use cases. This is something that’s easily lost in most software development teams.
Modules should be deep
Chapters 4 through 9 speak to this theme, at different levels. The high-level idea is that when designing “modules” (which you could think of usually as classes, but could also be a service, or a subsystem), the best ones will powerful functionality and a simple interface.3
In my view, a nice example from my team’s primary codebase, GrowthExperiments, is the
WikiPageConfigLoader. The module’s description is:
/** * This class allows callers to fetch various * variables from JSON pages stored on-wiki * (the pages need to have JSON as their content * model). It is currently used for configuration * of NewcomerTasks (see * [[:w:cs:MediaWiki:NewcomerTasks]] as an * example). */
And the interface to load the config:
/** * Load the configured page, with caching. * @param LinkTarget $configPage * @param int $flags bit field, see self::READ_XXX * @return array|StatusValue The content of the * configuration page (as JSON * data in PHP-native format), or a StatusValue * on error. */ public function load( LinkTarget $configPage, int $flags = 0 );
Ousterhout’s idea for how to achieve deep modules is by hiding information. Looking at the code above, the developer can pass a
LinkTarget for a configuration page, and get back an array of configuration data. What’s hidden: caching, including invalidation logic; configuration validation; fetching from a local or remote source; database access; and parsing the JSON.
The book is worth reading. You might disagree with some of the author’s takes but at least you’ll then have formed a stronger opinion about design questions that are otherwise easy to push to the background in day-to-day software engineering. For me, I’m keeping the “Summary of Design Principles”4 and “Summary of Red Flags”5 on my mind as I’m writing code, reviewing it, or working on system design.