Why dead code always ends up causing problems

Removing code is rarely a priority. When a feature is no longer used, we often tend to think that we can remove the code later. You never know, it might still be useful someday. After all, if it doesn't do anything anymore, it shouldn't cause any problems.
That's exactly where things get interesting. Unused code isn't necessarily code without a cost. Even when it is never executed, it can make maintenance more complicated, slow down certain changes, and above all make the application much harder to understand. Let's take a look.
What exactly is dead code?
Dead code is simply code that is no longer used. It can be a function that isn't called anywhere, an old class, a route that no longer exists, a condition that can no longer be true, or even an entire feature that has been abandoned.
The problem is that it's not always obvious whether a piece of code is actually dead. You can't always tell at first glance.
A function may seem unused in the project but be called dynamically. A route may still be used by an old version of a mobile application. A file may be loaded automatically by a framework.
So you need to be careful before deleting something. Tests can help detect some regressions, but obviously they don't cover every possible use. But once you know that a piece of code is no longer used, why keep it?
Because you never know if it's really useless
This is probably one of the main reasons why dead code accumulates. You find an old function and wonder what it's for. Nobody seems to use it, but nobody wants to take responsibility for deleting it either. So it stays.
A few months later, someone adds a new feature and comes across this function. They don't know either whether it's still needed. And since it already exists, they may be tempted to use it rather than create a new one.
We've just turned dead code into live code. And sometimes, that's when the problems begin.
Dead code makes code harder to read
When I work on an application I don't know, one of the first things I do is try to understand how the different parts communicate with each other.
The problem is that a large codebase often contains much more than what is actually being used. You find old functions, parameters that are no longer used, classes that don't seem to be used anywhere, and pieces of code kept “just in case”.
This increases the noise. The more code there is, the more time it takes to understand the code that actually matters. You start reading unused code, which can mislead you about an obsolete way the application used to work.
It's not necessarily a performance problem. It's mostly a problem for developers.
Dead code can still cost maintenance time
Even if it's never executed, old code can still have a cost. Imagine that a library used by the application is updated. The code that is actually being used still works, but an old class kept in the project uses an old API from that library.
You then have to check whether this part of the code actually causes a problem. Just two weeks ago, I worked on an index rework and spent more than an hour modifying some queries so that they would use a new index, before finding out with the project manager that this part of the application hadn't actually been used by the client for at least a year... A complete waste of time. Fortunately, this time, it only cost me an hour.
Dead code can generate warnings, static analysis errors, or simply force a developer to check something that nobody actually needs.
It's a bit like keeping an old car in your garage: even if you never drive it, you still have to figure out what to do with it when you move.
Commented-out code is not a backup
There is also a particularly common variant: commented-out code. I admit I did this a lot when I was starting out.
You remove a feature, but instead of actually deleting the code, you comment it out.
/*
function oldFunction($value) {
// old implementation
}
*/
The intention is usually good. You think you'll always be able to recover it later. But with Git, this code is already saved in the history.
Keeping it in the file therefore generally doesn't add much. On the contrary, someone reading the file now has to wonder why this code is commented out and whether they still need to take it into account. If a feature needs to come back in two years, it will still be possible to find its old implementation in the Git history.
Dead code can also hide bugs
There's another problem: dead code isn't necessarily completely isolated. An old function may call another function that is still being used elsewhere.
An old condition can also make it look like a behavior is no longer needed when it is still reached through another execution path. By gradually removing unused code, you can sometimes uncover unexpected dependencies.
And that's what's interesting: removing code can sometimes help you understand an application better than adding code does.
But removing code can be riskier than adding it
Obviously, you shouldn't go to the opposite extreme. Removing code simply because it seems unused can be dangerous.
Before removing a feature, you should check dynamic calls, external scripts, public APIs, scheduled tasks, and other applications that may use the code.
This is particularly important for projects that have existed for several years and don't have very extensive test coverage. Well, over the past 20 years, I've occasionally deleted a bit too much code. It didn't happen often, thankfully, but I still remember those times ._. Back then, not everything was covered by tests, so it was definitely riskier.
There may be things that are no longer used by the main application but are still used by an old script that runs once a month.
Deleting this code without checking can then turn a simple cleanup into a production bug :'(
Removing code is also a change
We often think of new features as development and deletion as simple cleanup. Yet removing a feature can require just as much thought as adding one.
You have to identify what can be removed, check the dependencies, gradually remove the calls, remove tests that are no longer needed, and then make sure that nothing still depends on that feature.
But once it's done, the result is often much nicer. Less code to read. Fewer things to understand. Fewer dependencies. Fewer chances of accidentally bringing back old behavior.
So why do we keep so much dead code?
Because deleting code generally doesn't give the end user anything visible. A new feature can be shown, tested, and announced.
Deleting 2,000 lines of old code doesn't exactly make for a great screenshot. And yet, it can be much more valuable for us developers who will have to maintain the application for the next few years. Code that we no longer need to maintain is probably the least expensive code.
Of course, you shouldn't go hunting for every single unused piece of code and spend weeks cleaning up a project. But when a feature has been definitively abandoned and you know it is no longer being used, keeping it “just in case” is generally not a good reason.
Dead code may not do anything. But it still takes up space in our developer minds. And over time, that space ends up costing a lot.


