Review of John Ousterhout's "A Philosophy of Software Design"

6 hours ago 1

I pride myself on being the kind of blogger who writes what he truly believes, even when it’s unpopular. That’s part of why I’m so brash and come across as condescending. It’s cathartic for me, so if being correct is condescending, I don’t want to be wrong.

Having said that, it feels good when I find others whose opinions mostly resonate with mine. I very recently finished reading A Philosophy of Software Design, by John Ousterhout (I know I’m super late on that), and I’ve come to the conclusion that it should be mandatory reading for every software engineer.

The Good

First of all, it’s packed with content that isn’t easily found elsewhere. Both junior and senior engineers can learn a lot.

He explains complexity as a combination of dependencies and obscurity (though some problems have inherent complexity), and how it slowly creeps through incremental changes to code.

The concepts of interface depth and information hiding are excellent ways to explain what modularity looks like at the program, API, service, class, and even function level.

His use of the text editor example to explain many of his concepts felt natural and not forced at all.

In chapter 19, he gives his takes on some popular software trends such as OOP, Agile, Testing, etc.

My favorite take of his is on Test-Driven Development, in which he proclaims he’s not a fan because it’s too incremental and distracts from high-level design. He only likes it when fixing bugs. That, my readers, is the correct take, and it alone puts him leagues above most.

And best of all, he isn’t dogmatic about in his presentation. In fact, each of his points is full of nuance and discussion of specific caveats that I haven’t seen anywhere else. Throughout every chapter, I just kept thinking, “This guy knows his stuff.”

The Bad

The bad doesn’t outweigh the good, but I still have to point out what bothered me.

First of all, the ratio of code examples to advice is lacking. A lot of it will resonate with experts in the abstract, because we have lived experiences of said scenarios. But the less experienced may struggle to internalize some lessons without concrete examples to anchor them to. Ousterhout admits this in the introduction, but still.

But my real issues are with the presentation of these examples. A lot of the time, Ousterhout will present an interface and point out the flaws that lead to brittleness and complexity. But then, he’ll instantly come up with another interface and explain why it’s better.

His explanations are correct. It’s just that the way he presents them gives the impression that you can design a good interface from requirements alone, but then doesn’t explain how he reasoned his way to it. It feels like post-hoc rationalization of his expertise rather than a process anyone can deduce their way through.

The biggest culprit of this is the text editor example. I’ve never designed one of these before, so I was looking forward to hearing his thought process throughout. I was able to follow along, but a more junior developer would certainly struggle.

Am I supposed to believe that Ousterhout is so brilliant that he comes up with modular interfaces right off the bat? Of course not. You implement, your understanding grows alongside said implementation, and then you build your design. There’s not a single intermediate design iteration in the book. It goes straight from bad to good.

There were some other things that bothered me, too.

Chapter 10: Define Errors Out Of Existence had some great points, though it was a bit overstated. I rarely encounter instances of unjustified exceptions, though the examples he mentions, namely key deletion and substring, do fit the bill. I don’t know if it really warranted an entire chapter, though.

Chapter 11: Design it Twice is also not worth a chapter. Do you really need to tell people to try out multiple designs?

In Chapter 12, he “debunks” common excuses that people use to forgo writing comments and documentation.

He claims that self-documenting code is a myth because there are often too many details in an interface to communicate through code alone. The example he uses to support his stance is the ambiguity of Java’s built-in substring method. Specifically, whether its end is inclusive, and what happens if the start index exceeds the end index. Without its documentation, you’d have to read the method anyway to know these.

Now I don’t know what Ousterhout has been through, but surely this is an exaggeration. Yes, there may be details of a method that are difficult to communicate through its signature alone. But if the details are so significant, then that indicates bad design (assuming the functionality itself isn’t just unintuitive by nature). Ousterhout himself points this out in Chapter 10, so I don’t know why he uses this example. Java libraries aren’t the gold standard for code. People don’t actually believe that, right?

Right?

But there’s another factor to consider. Libraries, services, and APIs are in a different realm to classes and methods. The more granular you get, the less valuable documentation becomes, because it’s very hard to document these parts without just restating the implementation. Ousterhout acknowledges this in Chapter 16.

I don’t see people claiming that high-level documentation should be omitted, or that we shouldn’t write comments for confusing code in methods. In fact, I often see the opposite in code reviews, which is good.

“Good code is self-documenting” doesn’t mean, “No comments are allowed.” It means, “Comments shouldn’t be the default; they should be the last resort after code is made as clear as possible, yet there’s still confusion.”

I can tell he understands this because in Chapter 18, he says mentions comments as a way to compensate when code is nonobvious. So why devote so much of the book toward best practices regarding comments, when the majority of readers would already agree with his stance, assuming they can even understand it? It’s fragmented across four chapters, and not even consecutive, mind you, so I had a hard time piecing it together to explain to you here.

In Chapter 13, he actually gives good examples of low-level comments, but I think he prematurely generalizes those examples to claim that low-level documentation is more necessary than people believe. But the reason it’s valuable in these specific examples is because he’s dealing with an inherently complex and uninituitive topic (extremely low-level memory manipulation for RPC functionality), and it’s in C++. Interestingly, it’s at this chapter that he starts using C++ in his examples. A bit fishy, I have to say.

In Chapter 15, he advocates using comments as a design tool. That is, using comments to plan your code and filling them in with the abstractions that follow. He doesn’t explicitly say this, but it’s clear this is meant to be an alternative to TDD (*shudder*).

He even uses similar talking points like, “If you wait until after your implementation to write comments, you may decide not to write them at all,” and, “Comments are a design tool for interfaces.” I use comments to keep myself on track and plan out my implementations, but not the way he describes it.

Why would I document a class, method, or even a variable before I write it?

Why don’t I just write it and let the interface arise naturally as the implementation grows?

Code reviews can point out the need for comments, so does it matter what order I write them in?

What if comments are used as crutches to explain badly designed code?

Chapter 18: Code Should Be Obvious is super lackluster.

There are so many ways to increase the clarity of your code, yet he only mentions superficial aspects, like using white space judiciously and comments.

For things that decrease clarity, he mentions:

Event-driven programming (a.k.a indirect calls). I guess, but are people defaulting to event-driven calls so much that this is worth mentioning?

Generic containers (like Java’s Pair class). Once again, suspiciously specific. What about languages where multiple values can be grouped together without having to name the resulting object?

Different types for declaration and allocation (like List<Integer> = new ArrayList<Integer>();). Okay, this is starting to get silly. Is polymorphism bad now?

Violating readers’ expectations. This one’s alright.

Chapter 20: Designing for Performance barely scratches the surface. There are so many more performance optimizations to consider, and much more common than the low-level example he uses. What about bad queries and unnecessary API calls? Caching?

More generally, he barely talks about functional programming, whose principles strongly push you toward modular design right out the gate.

What about databases and SQL? Are they not software?

The book feels very C++ and Java-centric, even though it was first published in 2018. I’m not saying Ousterhout has to only use modern languages, but he should at least acknowledge that object-oriented languages carry a lot of legacy baggage, and that it might cause this book, and even his philosophy, to become outdated soon.

The Spooky

In Chapter 9, Ousterhout talks about decomposition. Specifically, when to separate modules into smaller components or bring them together to reduce complexity. In chapter 9.7 & 9.8, he discusses how and when methods should be decomposed. Sound familiar?

This was the topic of my article last week, and it spooked me how similar my points were to his, despite my never having read this book before, and not seeing my ideas expressed elsewhere with such precision:

  • He calls out LoC as a dumb metric for function decomposition, though much more politely than I do.
  • He explains the main cost of decomposition, the main one being the spread of complexity rather than its reduction.
  • He says that you should favor decomposition when the subfunction can be understood in isolation, and the parent function can be understood without the implementation of the child function, which is almost exactly what I said in Lesson 4 of my article.
  • He advocates organizing code into independent blocks.

I could go on. He explicitly pushes back on Clean Code‘s ridiculous function length recommendations, which I have to admit is pretty bold. I’d recommend people read the book for this alone. Treat it as a rebuttal to Clean Code.

Oh, and I’ll just drop this here.

Conclusion

I know I spent a lot of time on the bad, but believe me when I tell you that it’s not that significant compared to the rest of the book. My issues were more with how much time was spent on things I felt didn’t warrant such attention, and things I wished were discussed, rather than things that were outright wrong. I’m just a thorough guy. Anything I didn’t mention in the above sections, I consider good, or just not worth mentioning.

Overall, you should read this book if you haven’t already. And if you think Clean Code is good, then you REALLY NEED to read this.

But read my articles first so you can see just how on point I am.

Thank you.

Read my articles!

Read Entire Article