When I’m called in to do a strategic assessment of a codebase, it’s never the result of everything being awesome. That is, no one calls me up and says, “we’re ahead of schedule, under budget, and knocking it out of the park, so can you come in and tell us what you think of our code?” Rather, I get calls when something isn’t going according to plan and the business people involved want to get some insight into what underlying causes there are in the code and in the team’s approach.
When the business gets involved this way, there is invariably a fiscal operational concern, either overtly or lurking just beneath the surface. I’ll roll this up to the general consideration of “total cost of ownership” for the codebase. The business is thus asking, “why are things proving to be more expensive than we thought?”
Typically, I come in and size up the situation, quantify it objectively, and then use analogies and examples to make clear what’s happening. After I do this, pretty much without exception, the decision-makers to whom I’m speaking want to know what small things they can do, internally, to course correct. This makes sense when you think about it. If your doctor told you that your health outlook wasn’t great, you’d cross your fingers and say, “but I can fix it by changing my diet and exercise a little, right?” You wouldn’t throw yourself on the table and say, “cut me open and make sure whatever you do is expensive!”
I am thus frequently asked, by both developers and by management, “what are the little things we can do to improve and maintain code quality?” As such, this seems like excellent fodder for a blog post. Here are my tips, based on years of observation of what correlates with healthy codebases and what correlates with distressed ones.
Write (Useful) Unit Tests
You had to know this was coming, much like a trip to the dentist yields sober talk of the important of flossing. And there’s a reason you knew it was coming – it’s true. Without exception, I’ve never been called in to assess a distressed codebase and seen a robust, valuable unit test suite. The tests are either non-existent or clearly the product of a team figuring out “what is this unit testing thing about, anyway?”
Unit testing has a learning curve, but that curve doesn’t need to be onerous. To put my money where my mouth is, I’ve literally written a book on this very subject. If you develop this skill and then practice it as a habit, you will see improvement in the codebase’s fortunes. Unit tests don’t just guard against regression bugs – they force you to write code that is less tightly coupled, more sensibly factored, and just generally better designed.
Keep Coupling to a Minimum
Speaking of writing code that’s less tightly coupled, let’s just go ahead and make that habit number 2. Some coupling in your code is inevitable, and creating too much indirection for its own sake is problematic, but I’ve yet to see a distressed codebase that didn’t have dependency snarl.
Snarl comes in many forms. It can be dependency cycles between modules or namespaces, or it can be failure to encapsulate within types. Violations of the Law of Demeter are subtle coupling issues, while global variables and patterns that hide them, such as Singletons and Service Locators, represent more overt problems.
It’s a broad concern, to be sure. But the common, unifying theme is that you’re forcing things to depend on one another when they don’t need to. The habit you should acquire is the one of constantly asking yourself, “does X really need to know about Y?”
Be Mindful of the Principle of Least Astonishment
This is a fairly subtle habit to develop, but an important one for code quality. I say that it’s subtle because in this case, your actions do not directly affect code quality; they affect it indirectly, through others.
The Principle of Least Astonishment holds that you should design components of your system (in this case code and the APIs/interfaces used by your fellow developers) in a way that squares with how people using them expect them to behave. As a practical example (because I’ve seen this in the wild), don’t offer two overloads of a method and have a comment in one of them explaining that it doesn’t work, and to use the other one.
Running afoul of the Principle of Least Astonishment in your code means people using your code are disproportionately likely to introduce bugs, since you are confusing them. And because this is entirely predictable to you, as you’re writing the code, that’s on you and not them.
Minimize Cyclomatic Complexity
Cyclomatic complexity is the number of independent paths through a given piece of code. So if you had a method that did nothing but dump output to the console, it’s cyclomatic complexity would be 1. If you introduced an if condition to qualify whether or not to dump to console, the method’s cyclomatic complexity would now be two, since there are two different ways to traverse the method.
As the cyclomatic complexity in a unit of code grows, the difficulty of reasoning about that code explodes. Try looking at a method with conditionals, loops, case statements nested five deep, and, without a pen and paper for note-taking, explaining the circumstances under which a given line of code is reached gets really hard.
And if it’s hard to reason about your code, it’s going to be hard to maintain, hard to change, and likely to be riddled with defects. Making it a habit to keep cyclomatic complexity as low as you can helps you avoid this state of affairs and maintain code quality.
Get Names Right
After reading the rest of these, you might find yourself reading “Get Names Right” with some skepticism. If so, help yourself to some more skepticism for the moment because I am going to argue that this is the most important habit that I’ve mentioned so far.
If you doubt the power of a bad name to create confusion, ask yourself how many millions of hours have been wasted on phone interviews over the last 15 years because recruiters think “Java” and “Javascript” are more or less the same thing. Or to bring it home on a smaller scale, imagine encountering a Boolean variable named “invoiceTotal.”
Bad names bewilder, making it take longer to understand and correct issues. But they don’t just do that once – they do it every single time someone encounters them. Confusion results. Bugs are left in. Meetings are called. Arguments ensue. From a total cost of ownership, bad naming is a disaster.
To preserve code quality, get in the habit of spending time thinking of a good, clear, unambiguous, and accurate name. Failing that and if time is a limiting factor, at least get in the habit of making a suboptimal name easy to change, and then changing later when you or your group think of something better.
Forming Code Quality Habits Isn’t Easy, But It’s Worth It
Forming a habit is generally not an easy thing to do, and it’s probably not going to go well if you try to form a whole lot of them in one fell swoop. I’ve seen it said that habits take, on average, 66 days to become habits. So pick one of these, practice it conspicuously for two months, and then move onto the next. Within a year, you will be writing substantially better code.
“Get Names Right” reminds me of a famous quote 😉
“There are only two hard things in Computer Science: cache invalidation and naming things.” – Phil Karlton