Software Quality (8 Part Series)
Most of us have heard the expression “Less is more”, but have you thought about what that means for software quality?
If you’re not familiar, less is more is defined as follows:
Used to express the view that a minimalist approach to artistic or aesthetic matters is more effective.
Obviously, this phrase could be used on things like user interface and user experience. Having a minimal and clean user interface allows the user to focus on what really matters.
What’s interesting to me personally is applying this truism to code and software quality.
Earlier this week I sent in a merge request that had the following delta signature:
That’s saying that I modified or added 138 lines and deleted a whopping 12,618 in the single merge request.
My reviewer was appropriately mortified, but thankfully the vast majority of the changes were the wholesale removal of classes and methods and both the review and the work item went off without a hitch.
Additionally, even before the week was out, we started reaping the benefits of “less is more”. Code was easier to find and duplications were greatly reduced, allowing us to make changes more quickly by modifying fewer places.
Without going into details, the particular commit in question was ripping out large chunks of code that are no longer relevant in their current context.
You might think that code that isn’t called isn’t going to be changed, isn’t going to break, and shouldn’t matter.
You would be wrong.
Code that exists in a half-dead, zombie-like state still actively sucks energy out of the development team. Besides bloating the size of the code and any build artifacts, dead code slows progress.
To illustrate how this is possible, let’s say I’m looking at changing the signature of a method. I’ll either use built-in tools in my editor or do a find in files operation to see what other code calls that method, and then make judgment calls as to how each caller should be handled.
If code that is no longer relevant and could never be called calls out to my method, it will still show up in the search results and I’ll still need to account for it. In drastic cases, invokes from code that is dead but not realized to be dead can change the entire strategy of implementing a change, adding days or more to the development time.
Additionally, dead code makes it harder to scan files and find the relevant information. It adds noise to the code that doesn’t need to be there, and noise makes it easy for bugs to hide.
So, if you have large chunks of code that is provably dead, treat this code as a defect and remove whenever you find it.
So, how do we find dead code?
Searching for the name of the method or class you’re working with and look for any code without matches.
Bear in mind that inheritance and interfaces can make it more obscure as to which methods are not invoked, so keep an eye out for false positives.
Looking at unit test code coverage to find large spots missing from any unit tests. Note that this only works if your unit tests cover the entire application as used today, so this is usually less reliable.
The use of tools is overall my favorite way to find code that appears to be uncalled.
Static Analysis tools such as SonarQube or NDepend can tell you more about what parts of your code are used and are not. This is very similar to development tool based approaches, except these tools are typically special-case analysis tools that are either infrequently run on dev machines or are built into the build process.
Incidentally, these types of tools are also helpful in communicating software quality to others.
In my sample commit from earlier, I leaned on ReSharper to help identify methods to prune as I went through the codebase and then relied on NDepend after the fact to look for large classes that don’t show up on any code coverage results.
The results looked similar to the following screenshot of a past version:
Looking at a view like this, it’s easy to spot the areas of your codebase that aren’t being tested at all as they appear a lot more solid and uniform. It’s essentially like taking an X-Ray of your code.
Accessibility Note: For those suffering from color blindness, NDepend offers color customization
Product Disclosure: While I was previously a paid customer of NDepend, my current copy was given to me for free as an evaluation copy
I am fond of saying this, but it’s worth repeating myself: Code that isn’t there can’t break.
The code you’re ripping out could have blatant bugs in it and generally not function. It could have subtle edge cases waiting to be found. It could also be free of bugs entirely. Whatever the case, removing it will solve any existing defects in the code as well as prevent new defects from entering that block of code.
When you rip out dead code, you take a weight off of all future developers who either would have touched the file in question or searched the codebase and found matches on the code you removed.
Less noise in the form of dead code means a greater degree of focus is possible during debugging sessions, leading to more bugs being found earlier on in the process.
To return to our premise: less is more. Less code results in more quality.
I’d wager that it’s not a linear relationship either – there’s definitely going to be a sweet spot where if you get on the wrong side of that, quality issues grow at an alarming rate.
Ultimately, my recommendation to you is to embrace the death of code whenever possible and to look for ways to not only remove old code, but to find more concise (but still readable) ways of representing the code you have to keep.