I’ve recently finished the “A Philosophy of Software Design” by John Ousterhout and I think it’s the best book on programming I’ve read so far. One of the principles, referenced all through the book, is “modules should be deep”. Deep module is one with a simple interface providing a lot of functionality. If you heard about Go Proverbs, that’s very close to:
The bigger the interface, the weaker the abstraction.
In this post I’m going to list a few things I understand better thanks to the book and in particular that principle.
Short functions are not necessarily better
Through my career and practice I’ve learnt that it’s much easier for me to handle a code with shorter functions. Then I’ve become quite obsessive about it, almost mechanically splitting any function longer than half of my screen into two or three related. Over the years I’ve also heard quite a few people saying that when an implementation takes more than 10-20 lines, it should be reorganized.
What “A Philosophy of Software Design” says is that functions/modules shouldn’t be too short and, unlike I thought, splitting a module/class/function can actually add complexity. Shorter it is, less functionality it provides and a person using that code gets an only new interface/API to learn with a little benefit. The best implementation may be two screens long and splitting it may break the abstraction or introduce redundant dependencies.
Temporal decomposition is worse than no decomposition
Then I read about the “temporal decomposition”:
the structure of a system corresponds to the time order in which operations will occur
and that was a kind of shock for me, as that’s exactly how I sometimes ended up splitting my code, for example when working with some particular format. I created a part for reading, then a part for writing and they either provided very weak abstractions, or they had duplicated parts for parsing. In the book the latter is called the “information leakage” and John advises:
when designing modules, focus on knowledge that’s needed to perform each task, not the order in which tasks occur
So, as I understand it, “cut along the layers of abstraction”, what I’ve already knew, but I’ve found it hard to act on that. Now I’ve got also a name and clear explanation of the anti-pattern. I hope the decomposition problems will be easier to spot now for me.
Comments are a great tool for creating abstractions
The last lesson related to “modules should be deep” principle is about comments. I met quite a few people saying the best code is self-explaining and a need for comments is a symptom that it can be written in a better way. That’s not always correct, although I couldn’t find a clear border between messy implementation and details that cannot be expressed with the code. Often stated rule is “comments should tell why, not what” and that’s what I got in my mind while coding. “A Philosophy of Software Design” takes it even further by treating comments as design tool and a means of describing abstractions (telling “what, not how”).
First, “comments serve as a canary in the coal mine of complexity”. Written before the code may tell, how good the abstractions is, whether the role of the given part is clear, and, definitely save time that would be spent on wrong approach. That’s the “better to think twice before you start coding than code three times before you start thinking” tip I learnt pretty early and a reminder/extension of the Readme Driven Development.
Beside that, well written comments describing functionality provided by a module/class/function allow treating it as a black-box (John calls them “interface comments”). To quote the book:
an abstraction is simplified view of an entity, which preserves essential information but omits details that can safely be ignored. If users must read the code of a method in order to use it, then there is no abstraction
Again, it can be linked to “Documentation is for users.” from Go
Proverbs. While using Go I usually verify
whether my comments make sense by opening godoc (e.g.
godoc -http=:8080) and
reading them there. Without implementation it’s easier to see, whether they help
to understand the purpose of a package/interface/function/etc.
Minor remarks on Agile and TDD
I have mixed feelings about both Agile and TDD. While that’s not a good place to talk about them, I’d like to note a thing or two on them from the book for future reference. So the main thing “A Philosophy of Software Design” criticizes them for is shifting focus from “strategic programming” (focus on great design, investment mindset) to “tactical programming” (focus on working code). I especially liked the part:
developing incrementally is generally a good idea, but the increments of development should be abstractions, not features
As a member of a scrum team a few years ago I’ve noticed that with Agile it’s far too easy to avoid spending a few weeks on a good design by accussing the proponents of introducing the Waterfall. Unsurprisingly, real engineers after switching to software engineering notice that too:
I’d like to see a lot more thought and planning go into stuff. I’m sure the Agile people are gonna freak out and be like, “You’re doing waterfall!” No, we’re not. We’re just thinking about what we want to build and why. -Matt (chemical)
The most efficient team I’ve been part of so far planned features months ahead and I believe the truth lies somewhere in between.
That’s roughly 1-2 of the 15 design principles presented in the book and I definitely recommend reading the whole. I’d say it’s similar to the “Practice of Programming” by Rob Pike and Ken Thompson or maybe even “Clean Code” by Robert C. Martin, but for “intermediate developers”. By that I want to say that with around five years of professional experience I’ve learnt the most from John’s book.