Reading notes: A Philosophy of Software Design
Here are a few notes on A Philosophy of Software Design by John Ousterhout.
It's a small book at 178 pages that says what it needs to say simply and to the point.
This isn't a review of the book, for that check out Pragmatic Engineer's great post and video. I'm just noting some parts that I relate to and that stand out to me.
Different layer, different abstractions
The interface of a class should normally be different from its implementation: the representations used internally should be different from the abstractions that appear in the interface.
Information leakage occurs when a design decision is reflected in multiple modules.
Better together or better apart?
When reading this chapter, I was reminded of a little side project I had worked on.
Bring pieces of code together is the most beneficial if they are closely related [...] Here are a few indications that two pieces of code are related:
- They share information.
- They are used together.
- They overlap conceptually.
- It is hard to understand one of the piece of code without looking at the other.
It's a small drawing tool that allows you to create vector networks out of nodes and connections, like in Figma. But unlike Figma, you can animate the lines in the network, making them all shaky and wiggly.
After a messy prototype, I tried to "clean" things up by splitting the code into generalised classes. These included a class to create the HTML canvas and a class to render the drawing. Another 2 classes handled data structures such as the graph for connections between nodes and spatial hashing for hit testing.
Setting it up looked a bit like this:
const [_, context] = createCanvas2D(); const drawingGraph = createDrawingGraph(); const renderer = createRenderer(context, drawing); const spatialStructure = createSpatialStructure(); spatialStructure.parseFromGraph(drawing);
But having these generalised classes made the project harder to work with. I had to switch between files constantly for simple features, and some parts relied on data from other bits. For example the
spatialStructure needs to be updated each time the
drawingGraph changes, which is happens often. It could get out-of-sync easily if I'm not careful.
A better solution would be to instantiate a generalized module inside another because they're used together and share information. Or worry about generalising later.
Many times in my career I've been told that "good code is self documenting", so don't write comments and just write code good!
I've always felt that this was the wrong way to think about things but couldn't succinctly say why. But now I can, and it's one of my favourite quotes in the book:
If users must read the code of a method in order to use it, then there is no abstraction.
Of course you'll still need to write good comments and there's a whole chapter dedicated on how to do that. I really liked this rule of thumb when writing comments:
A first step towards writing good comments is to use different words in the comment from those in the name of the entity being described.
It's not a silver bullet and shouldn't be taken to the extreme. But it's a decent starting point to get you thinking about how best to explain something in code.