Clean Code is Back And It's Time For an Update
Back in 2008, Robert C Martin (“Uncle Bob”) published a book called Clean Code in which he condensed his insights from years of experience helping teams writing or maintaining large software systems be more productive. For a while, that book was quite the rage. I read it, of course, as it came recommended by a colleague whom I saw as a very accomplished developer.
Casey Muratory, a developer spending a lot of time using or writing software with high performance requirements, recently dissed the “Clean Code” ideas in his article “Clean” Code, Horrible Performance. That article got a lot of attention in developer communities around the internet. Recently, the two of them sat down (virtually) to discuss the discussion. It’s an interesting read as well.
Clean, not clever
So since the topic is hot again, and since I’m back to programming myself and dealing with some of my own code from around that time, this seems like a good time to update my own thinking on the topic of what makes good code good.
I remember distinctly that the first time I questioned some of the principles, especially “Don’t Repeat Yourself” and the tendency to abstract stuff using inheritance, was with unit test code.
As soon as the test setup becomes a little more complex, or a test suite starts exhibiting some repetitiveness, it’s very tempting to start abstracting and generalising. After having introduced and fixed enough bugs in the test code (the code exercising the actual production code), I eventually became convinced that this was a bad idea. Test code should look and feel stupid. It’s perfectly OK to write 10 test cases (each as one function) that look very, very similar, if they test for different things.
Eventually (I never stopped programming, even when it was no longer my profession) I realised that the same could be said about any code. I was reflexively abstracting when something was done twice or thrice. Yet there is no real issue with doing something two or three times in a codebase. Either that code is covered by tests that would break if you forget one place, or they handle different features and are really independent, or they are easy to find because they use a common function or type that can be searched for using plain-text search. So nowadays, I only generalise when I’ve already needed the concept a handful of times.
The downside of overgeneralising, though, had become quite clear to me in the meantime. Overgeneralised code is hard to follow because it looks like there’s more to it than meets the eye, so one becomes slightly wary of change.
I’ve come to love plain code. Plain is plenty clean.
Clean, and lean
Casey makes the argument that the indirections and abstractions encouraged by the “Clean Code” people eventually lead to horrible performance. More generally, he seems to have a thing about modern software being so slow as to be unusable, despite it running on unbelievably fast computers.
But I’ve yet to see performance issues commig from calling virtual methods instead of plain functions, except in super-performance-critical code.
When I tap a button on my phone, and it takes seconds to react (my phone is old, but not that old), I know it’s not because of virtual methods. Either I’m hitting swapped-out memory (is that a thing on Android?), or the algorithms chosen are bad (Uncle Bob makes the arguments that poor algorithm choices make software orders of magnitude worse than virtual methods, in their conversation, he and Casey Muritory discuss the performance issues of GitHub’s editor to illustrate that point), or there are so many layers of software involved that harmless-looking actions end up lazy-loading megabyte-big subsystems. WordPress sites aren’t slow because of PHP, an interpreted language, they’re slow because every page ends up loading tons of scripts and libraries that actually provide very little value, but it’s all kind of hardcoded in or sideloaded by plugins that don’t care about each other.
So in addition to writing reasonably clean code (without going overboard or ignoring performance-criticality), I like the idea of writing lean code. Code that doesn’t require dozens of megabytes of additional libraries to run. Code that maybe doesn’t integrate quite as tightly with the platform du jour, but still provides 95% of the value to the user.
So, to recap, I believe:
- Clever code isn’t Clean Code
- Plain code is plenty Clean
- Clean Code is lean code