Many people have given many different descriptions of well written software, I of course, have my own.
Good software has “Unlimited changeability”
That’s the clickbait version anyway… but I personally feel this to be true. When working with a good codebase, it feels like you can change it into anything - note I didn’t say easy, it probably isn’t, but it feels doable, or at least within the realm of possibility. And I really mean anything - programming language, cloud provider, anything. It’s as if the code has inertia… the code almost writes itself.
In a “bad” codebase, the opposite feels true. Everything feels stuck - you try to remove some gunk as you go, but there is just too much, you’re really just moving it around. For these codebases, you have to be very systematic and have good long term planning if you want to make any real progress.
In this post, I will try to quantify this feeling of momentum and come up with a real answer to “what is well written code?”.
Note that I am strictly talking about just the code. There are many other equally important aspects to consider when developing software - people, for one.
Feedback loops
I really like this analogy of code inertia and stickiness - it feels real and it makes sense. The simple explanation is that you can write code in a way that either enhances or diminishes your ability to make further changes.
This quality of the code manifests in my day to day as a programmer as the feedback loop - writing software is one big feedback loop that has many smaller inner loops, depending on your working style. A tight loop with high fidelity feedback can sustain bigger, more frequent changes. A long and lossy feedback loop produces the opposite - fewer changes, less confidence.
So changes that improve the feedback loop are high quality changes, because they give you more capacity and confidence to make more. But this is all vague, right? It doesn’t actually tell us what to change or give us a direction to move towards.
Bulkheads, Vacuum Cleaners and Cats
Another analogy to the rescue! Take out your favourite imaginary vacuum cleaner and imagine trying to clean:
- A huge shopping mall while blindfolded
- A small room while blindfolded In both scenarios, you don’t know how well you are doing. However in the first scenario, you probably won’t feel like you made any progress. With some experience, you can probably make real progress in the second one.
Let’s imagine a couple more. This time, 100 cats have been let loose in a submarine, and you need to catch all of them. Now imagine two versions:
- No bulkheads - the whole submarine is open
- Bulkheads every couple of meters that you can lock behind you The second scenario is easier because you don’t have to keep rechecking areas you already cleared. You lock the bulkhead, and that progress is permanent.
Finally, imagine clipping the nails of 5 cats versus 100. Both are hard, but 5 is at least straightforwardly doable in one go. 100 requires planning, batching, and some cats will have escaped by the time you get back to them.
Having gone through that exercise, I now present the 3 most important things when determining software quality - in my opinion.
Observability
This is pretty straightforward. These are like your senses - you use them to make decisions, avoid obstacles, identify potential objectives. Observable software tightens and improves the fidelity of the feedback loop.
This isn’t limited to runtime monitoring — anything that tells you whether a change worked, from your compiler to your test suite to your deployment pipeline, is observability. The faster and more accurate that signal, the tighter your loop.
Boundaries
Boundaries are contracts that, when upheld, help you lock in progress. They do this by limiting the cascade of changes and the blast radius of any individual change - just like the bulkheads.
But not all boundaries are created equal. Boundaries can be fuzzy - function signatures in dynamic languages do not provide enough information about how they should be used. Same thing for an HTTP endpoint described by an OpenAPI spec. It functions as a boundary, but you can break it in ways that aren’t documented. The industry calls this UB - Undefined Behaviour.
Good boundaries have little or no UB. They are foolproof, and more importantly, they convey exactly how they work. A good boundary is expressed at a level of fidelity high enough to describe the operation precisely - a typed function signature, a gRPC contract, a well-constrained command. The less UB, the more confidently you can treat a cleared area as actually cleared, and the tighter your feedback loop becomes.
Scale
This is the scale of what lives inside a boundary. It differs from person to person, but everyone has a size they can hold in their head and manipulate comfortably. Going bigger slows changes down and exponentially increases rework and backtracking. Keeping scale close to that comfortable size is the goal.
Closing the loop
So there it is. Observability helps you know how you are doing. Good boundaries and appropriate scale let you make small incremental changes with confidence. All three combined contribute to a tight, high fidelity feedback loop.
A final analogy - good software is like navigating a flat plain with a compass. You know where you are, and you only need to take normal human steps. Bad software is like navigating a ravine without a compass. You don’t know where you are most of the time, and some paths forward require you to jump off a cliff.