Technical debt is every programming team’s mortal enemy. It can slow development progress to a halt, leaving management wondering “what went wrong” as a formally productive engineering team suddenly hits the brakes and output seemingly grinds to a halt. For the non-techies, the term “technical debt” refers to the build up of messy, complex, poorly document, or fragile code inside a project or company. Most companies are aware of the primary source of technical debt: compressed schedules, and programmer laziness oversight. Good engineering teams employ multiple strategies to keep incidental technical debt under control. They have code reviews, encourage testing, documentation, and coding standards. These measures give team leads warm fuzzy feelings that we are doing our part to keep the technical debt under control. Sometimes we might even push for allocating time to clean up particularly egregious pieces of legacy code.

So it’s simple right? Follow the best practices, put good strong standards in place, get buy in from your peers and management, and you’ve eliminated the problem altogether right? Done and Done?

If only it were so simple. Beneath the surface of even the most gold-plated code base lurks a more insidious form of technical debt. The accumulation of good decisions! Engineers couldn’t be happier than being heads down, in the trenches cranking on technical problems. Minor missteps, not bad enough to trigger any of the standards slowly add up to be a big problem.

It’s so easy to fall into the “doing too many good things” trap. A quick win, I’ll just write a utility here that’ll handle this edge case. This new library doesn’t quite cover all the cases solution did, so we’ll run them side by side for now. The code is documented well, and up to standards, but little by little the complexity of the code base crawls upward in spite of engineering’s best efforts. The number of projects and folders explodes. There are utilities controlling which utilities can run. Frequent comments of “oh, yeah, let me dig up the link to the documentation, I set that up a month ago”. Before you know it, all these well intentioned, even well executed solutions have grown into a many headed hydra that is beyond the best engineer’s ability to grasp it all.

I’m here to suggest that you take a moment with some regular frequency, step back and look at the big picture.┬áSurprisingly I find it’s can be bringing the team members up to speed that can illuminate this kind of description. Is the environment setup getting long? How many times do you have to explain “this is a little awkward but…”. How much trouble is it for new engineers to ramp up on the oldest parts of the code base? These kind of cues are flashing warning signs for the attuned developer.

Take the time to step back with some frequency (I’d suggest monthly, but this will vary upon the rate of change of your codebase). Pause your task list, pause your email and take a morning spent reviewing your system as a whole and get a feel for the various components. Be ruthless in looking for pieces to prune, combine, or refactor. Could we move this utility into our primary services api? Can we rip out this old library and change the code to use the newer solution we use in our second product? It’s so easy to get attached to our solutions, especially once they’re in code. They represent hours of our time and effort. Do your best to embrace the experimental nature of your job and prune the dead branches out of the tree before it becomes an impenetrable thicket.

Don’t let your desire to move fast and break things turn your good decisions drag you inevitably down the path of going full nuclear and re-writing.