Chapter 4. Improve Code by Removing It
We ascribe beauty to that which is simple; which has no superfluous parts; which exactly answers its end...
Less is more. It’s a trite maxim, but sometimes it really is true.
Some of the most exciting improvements I remember making to code involved
removing vast chunks of it. Let me tell you, it’s a good feeling.
You can improve a system by adding new code. You can also improve a system by removing code.
So why did all that unnecessary code get written? Why did one programmer feel
the need to write extra code, and how did it get past review or the pairing
It was almost certainly the programmers’ indulging their own personal
vices. Something like:
It was a fun bit of extra code, and the programmer wanted to write
it. (Hint: Write code because it adds value, not because it amuses
you, or you’d enjoy trying to write it.)
Someone thought it was a feature that would be needed in the future, so decided
to code it now, whilst they thought about it. (Hint: That isn’t YAGNI. If you don’t need it right now, don’t write it right now.)
But it was only a small thing; not a massive “extra” feature. It was easier
to just implement it now, rather than go back to the customer to see whether
it was really required.
(Hint: It always takes longer to write and to maintain extra code. And the
customer is actually quite approachable. A small extra bit of code
snowballs over time to a large piece of work that needs maintenance.)
The programmer invented extra requirements that were not documented in
the story that justified the extra feature. The requirement was actually
(Hint: Programmers do not set system requirements; the customer does.)
Now, we had a well-understood lean development process, very good
developers, and procedural checks in place to avoid this kind of thing.
And unnecessary extra code still snuck in.
That’s quite a surprise, isn’t it?
It’s Not Bad, It’s Inevitable
Even if you can avoid adding unnecessary new features, dead pieces of
code will still spring up naturally during your software development.
Don’t be embarrassed about it! They come from a number of unavoidable
accidental sources, including:
Features are removed from an application’s user interface, but the
backend support code is left in. It’s never called again. Instant code
necrosis. Often it’s not removed “because we might need it in the
future, and leaving it there isn’t going to hurt anyone.”
Data types or classes that are no longer being used tend to stay put
in the project. It’s not easy to tell that you’re removing the last reference
to a class when working in a separate part of the project. You can also
render parts of a class obsolete: for example, reworking methods so a member
variable is no longer needed.
Legacy product features are rarely removed. Even if your users no longer want them and will never use them again, removing product features never looks good. It would put a dent in the awesome list of tick-box features. So we incur perpetual product testing overhead for features that will never be used again.
The maintenance of code over its lifetime causes sections of a
function to not be executable. Loops may never iterate because code
added above them negates an invariant, or conditional code blocks are
never entered. The older a codebase gets, the more of this we see.
C helpfully provides the preprocessor as a rich mechanism for writing
Wizard-generated UI code inserts hooks that are frequently never
used. If a developer accidentally double-clicks on a control, the wizard
adds backend code, but the programmer never goes anywhere near the
implementation. It’s more work to remove these kinds of autogenerated
code blocks than to simply ignore them and pretend that they don’t
Many function return values are never used. We all know that it’s
morally reprehensible to ignore a function’s error code, and we would
never do that, would we? But many functions are written to do
something and return a result that someone might find useful. Or
might not. It’s not an error code, just a small factoid. Why go through
extra effort to calculate the return value, and write tests for it, if
no one ever uses it?
Much “debug” code is necrotic. A lot of support code is not needed
once the initial implementation has been completed. It is unsightly scaffolding
that hides the beautiful architecture underneath. It’s not unusual to see reams
of inactive diagnostic printouts and invariant checks, testing hook points, and
the like, that will never be used again. They clutter up the code and make
Does this really matter? Surely we should just accept that dead code is
inevitable, and not worry about it too much if the project still works.
What’s the cost of unnecessary code?
It is undeniable that unnecessary code, like any other code,
requires maintenance over time. It costs time and money.
Extra code also makes it harder to learn the project, and requires extra
understanding and navigating.
Classes with one million methods that may, or may not, be used are
impenetrable and only encourage sloppy use rather than careful
Even if you buy the fastest machine money can buy, and the best compiler
toolchain, dead code will slow down your builds, making you less productive.
It is harder to refactor, simplify, or optimise your program when it is bogged down by zombie code.
Dead code won’t kill you, but it will make your life harder than it
needs to be.
Remove dead code wherever possible. It gets in the way and slows you down.
How can you find dead code?
The best approach is to pay attention whilst working in the codebase. Be responsible for your actions, and ensure that you always clean up after your work. Regular code reviews do help to highlight dead code.
If you’re serious about rooting out unused code sections, there are some great code coverage tools that will show you exactly where the problems are. Good IDEs, especially when used with statically typed languages, can automatically highlight unused code. For public APIs, many IDEs have a “find references” feature that can show whether a function is ever called.
To identify dead features, you can instrument your product and gather metrics on what customers actually use. This is useful for making all sorts of business decisions, rather than just identifying unused code.
There is no harm in removing dead code. Amputate it. It’s not like you’re throwing it away. Whenever you realise that you need an old feature again, it can easily be fetched from your version control system.
It is safe to remove code that you might need in the future. You can always get it back from version control.
There is a counter argument to that simple (and true) view, though: how
will a new recruit know that the removed code is available in version
control if they don’t know that it existed in the first place? What’s
going to stop them writing their own (buggy or incomplete) version
instead? This is a valid concern. But similarly, what would stop them
rewriting their own version if they simply didn’t notice the code
fragment was already located elsewhere?
As in previous chapters, remember to remove dead code as a single step; do not conflate it in a version control check-in that also adds functionality. Always separate your “spring cleaning” work from other development tasks. This makes the version history clearer, and also makes revivifying removed code a breeze.
Code cleanup should always be made in separate commits to functional changes.
Dead code happens in even the best codebases. The larger the project,
the more dead code you’ll have. It’s not a sign of failure. But not
doing something about it when you find dead code is a sign of failure.
When you discover code that is not being used, or find a code path that
cannot be executed, remove that unnecessary code.
When writing a new piece of code, don’t creep the specification. Don’t
add “minor” features that you think are interesting, but no one has
asked for. They’ll be easy enough to add later, if they are required.
Even if it seems like a good idea. Don’t do it.
How can you identify “dead code” that is not run in your program?
If you temporarily remove code that is not currently required (but may be needed in the future) should you leave it commented out (so it is still visible) in the source tree, or just delete it completely (as it will be stored in the revision history)? Why?
Is the removal of legacy (unused) features always the right thing to do? Is there any inherent risk in removing sections of code? How can you determine the right time to remove unused features?
What percentage of your current project’s codebase do you think is unnecessary? Does your team have a culture of adding things they like or that they think will be useful?
Write Less Code! Talks about removing duplication at the micro level: whittling away unncessary lines of code.
Wallowing in Filth How to navigate a route into problematic code so you can spot what needs to be removed.
Coping with Complexity Removing dead code reduces complexity in your software.
Effective Version Control Removing dead code does not mean it’s lost forever. You can retrieve it from version control if you make a mistake.
Try this…. Look for dead and unnecessary code in the files you are working in. Remove it!