cat posts/2022-02-22-book-review-a-philosophy-of-software-design.md

Book review: A Philosophy of Software Design

· 5 min read · books programming design

The thing I’ve taken from this book that I use every week is a single word, “shallow”. As in, “this class is pretty shallow, what is it hiding?” in a code review. It’s become shorthand on my team in about a month, which is faster than any book I’ve recommended in years.

John Ousterhout is the Tcl person, and also, which I only connected halfway through, one of the two authors of the Raft paper, whose whole pitch was that understandability should be a design goal. A Philosophy of Software Design is that same idea applied to ordinary code. It grew out of a software design course he teaches at Stanford, where students write a big system, get it reviewed, and then rewrite it, and it’s short, under 200 pages. I read the second edition, from last summer, which adds a chapter and some explicit comparisons with Clean Code.

The core argument is that the main enemy in software is complexity, which he defines as anything about the structure of a system that makes it hard to understand and modify, and that it mostly sneaks in through dependencies and obscurity, a little at a time, so no single change ever looks like the one that did it. Most of the rest of the book is about how to keep it out.

Deep modules

This is the chapter everyone talks about, and with reason. Think of a module (a class, a function, a service) as a rectangle where the top edge is its interface, everything a caller has to know, and the area is the functionality it provides. A deep module has a small interface over a lot of functionality. A shallow one has an interface nearly as big as what it does. His example of deep is Unix file I/O: five basic calls, and behind them, disks, file systems, caching, permissions, devices, all of it. His example of shallow is Java’s I/O classes, where to read objects from a file with buffering you construct a FileInputStream, wrap it in a BufferedInputStream, and wrap that in an ObjectInputStream, and if you forget the middle one nothing tells you, it just goes slower.

As someone who spent the 2000s in .NET, I felt a little smug reading that, since FileStream buffers by default and you mostly never think about it. I then went and looked at a service I’d designed at work in 2019 and found eleven classes whose job was to pass a request from one to the next, each adding one field. Ousterhout calls this “classitis”, and there’s a name for the methods in them too, pass-through methods, which do nothing except call another method with the same signature. I’d defended that design in review at the time on the grounds that every class was small and easy to understand. Every class was, and the thing as a whole was not.

That’s where he and Clean Code really part ways, and the second edition says so directly. Uncle Bob wants functions of a few lines. Ousterhout says that splitting a function in pieces is only worth doing if the pieces are each something you can understand without the others, and that a lot of short-method code just turns one readable function into six you have to read in the right order. I think Ousterhout is right, and I’ve had that argument from both sides at different points in my career, so take that with whatever salt you like.

The rest

A few of the other chapters have stuck with me. “Define errors out of existence” says a lot of exception handling exists because an API was designed so that edge cases are errors, when it could have been designed so they’re just cases. His example is that deleting a file that’s still open is an error on Windows and isn’t on Unix, where the file disappears from the directory and goes away when the last handle closes. Having fought Windows file locks on deployments for a decade, I agree with every word.

“Design it twice” is just what it says: before you commit to a design, sketch a second, quite different one, even if you’re sure of the first. It’s the cheapest advice in the book and the one I’ve had the most luck getting people to actually do, because it takes twenty minutes.

He’s also firmly pro-comment, with the specific rule that comments should say what the code can’t, why it’s like this, what the units are, what the invariant is. He suggests writing them first, as part of designing the interface. I’ve tried that for a few weeks and I like it more than I expected to. Writing the comment for a function before it exists is a quick way to find out you can’t say what it does in a sentence.

His distinction between tactical and strategic programming is good too, with the “tactical tornado”, the engineer who ships features faster than anyone and leaves a mess behind that someone else spends months cleaning up. Every manager has had one, and they’re often the person management likes best. His suggestion that you spend something like 10–20% of your time on design improvements is a number I’d like to be able to defend to a CEO, and I’m not sure I can.

Where I argued with him is tests. He thinks test-driven development pushes people toward tactical programming, getting the next test green rather than thinking about the design, and he says so in a page or so. I can see it happening with some people, but my experience is that writing the test first is also a way of using your own interface before anyone else does, which is exactly the “is this module deep?” check he wants. I’d have liked him to spend more than a page on it.

The book’s weakness, if it has one, is that nearly every example is a single program, mostly the kind a student would write for his class. It says very little about systems made of services, or about the design decisions that happen in a database schema or an API contract between teams, which is where most of the expensive complexity I deal with lives. The ideas carry over (a service with forty endpoints that each do one small thing is a shallow module), but you have to do the carrying yourself.

I took more notes on this book than on anything in a while. They live in Obsidian, and the most-linked note is the one called “shallow”. Give it to whoever on your team writes the most classes.