Auto Ads by Adsense

Booking.com

Thursday, October 28, 2021

Review: A Philosophy of Software Design

 Over time, I've learned to be very suspicious of thick programming books. The thicker they are, the more likely it is that they be crammed with worthless code listings, and pages after pages of diagrams and pontification that's worthless. Thin books like K&R tend to be much more useful. When Steve Grimm recommended A Philosophy of Software Design, I was very happy to discover that it was a thin book.

You probably know many of the things Ousterhout points out, but even if you do, you've probably never seen them articulated this way before. For instance, he mentions that you should aim for API designs that have depth. His expression of "depth", however, might be different in principle from what you've heard before, which is that you should have few APIs/interfaces that are composable in ways that provide maximum functionality and abstraction.  He explicitly compares the UNIX file "open" type system calls vs the Java InputStream set of classes and finds that the InputStream type much to his distaste --- using it requires creating multiple classes stacked on top of one another, leading to verbosity, while the UNIX api just requires an "open."

There are many other common design mistakes that Ousterhout points to, including avoiding pass through methods, avoiding decorators, putting different abstractions for different layers of an API design, and taking on as much configuration complexity as possible as part of the design of the lowest layer of the API, rather than exporting it as tunable knobs at higher levels. In particular, he also points out that the error API like exceptions and return codes are part of a system's interface, and that programmers and designers need to think carefully before exposing them as part of a design --- if you can eliminate an error code, you've simplified the design considerably, and it's worth implementation complexity to do so.

I loved the sections of commenting ("write the comments first" is counter intuitive advice that might be worth trying), and naming hygiene, where he describes a bug that took him 6 months to find because a the same variable name was used to hold both physical blocks and logical blocks, and that one piece of code that confused the two slipped by multiple code reviews and inspections while silently corrupting files in a file system. These are design issues that can only be learned through hard fought experience, and Ousterhout has the scars to prove it.

I also enjoyed his perspective on object-oriented programming, agile development, and test-driven development. (Spoiler: he thinks that test driven development is bad because by writing the unit tests first, you've unconsciously made the cost of changing a design during development higher, thereby making it more likely that you'll try to patch a bad design than radically refactor it)

The book does have limitations. For instance, a lot of the complexity in modern APIs are because of the need to support legacy applications. Ousterhout has no experience with this (and neither do his reviewers, many of whom are Google engineers), and so doesn't comment on it, but it's a major issue in any company that's built a platform or is building a platform, since usually those are built in a hurry and only after success do you realize that you screwed up (the failures never need to be fixed since they have no customers). Many modern programming environments and systems make it easy to pull in libraries and dependencies from open source or other repositories, but this explosion of dependencies actually makes it really hard to debug and fix code. Ousterhout doesn't comment on that either, since most of his designs were base layer operating system type code. Another source of complexity I've found in modern systems is a penchant for putting behavioral controls in config files rather than in code. For instance, many Java logging systems assume you'd want to control logging in a properties file, which I've found to be backwards --- the logging systems should expose a programmer API, and if the application needs a config file to control behavior then there should be libraries available to do that reading and configuration, but making it hard to control behavior through an API actually makes it harder. SpringBoot (where you can spend all your time debugging inscrutable config files) is another example of this sort of misdesign.

No matter the limitations, this is a great book and well worth your time to read if you're an engineer. It's a great discussion of microarchitecture (not systems design) and good API design as well as good programming practices that makes you think. Recommended.

Update: The above was a review of the first edition of this book. There's now a second edition, and Prof Ousterhout has kindly published the additional chapters as a PDF on his web-site.

1 comment:

Peter said...

Be sure to get the second edition -- Amazon has been pointing to the first edition. (There's a discussion here, so the problem might be fixed by now: https://groups.google.com/g/software-design-book/c/H-3sC61ZN9E )