Technical debt is toxic debt, much like a payday loan you take on technical debt often in a state of panic when that feature just has to be out there right now. Further just like a payday loan if not paid off quickly it can rapidly become a millstone around your neck where you find yourself forever in a paralysing technical debt trap.
So what can be done to avoid the pain of Technical Debt in the first place?
#1 Acceptance Test Driven Development
Code that has no tests cannot be refactored safely, code that cannot be refactored is code that will rot, the second you write code without a regression test you have taken on some debt.
For me ATDD is even more important than TDD in this respect, once you have worked on a system with close to 100% acceptance test coverage you will wonder how you ever made changes safely without it. I have had to go back to working on systems that lack good acceptance test coverage having worked on some with close to 100% coverage and it is paralysing. More than this it is shocking to see the different attitude of developers to refactoring in such environments, without full regression suites (and if you don’t use ATDD you probably haven’t got a full regression suite) the attitude becomes “If it ain’t broke don’t fix it” rather than constant improvement. By using ATDD and treating the test as the source of truth you enable the freedom to change how code does stuff without changing what it does.
#2 Don’t feature branch – work on trunk
Feature branches discourage refactoring. I hadn’t really appreciated this benefit of trunk based development until recently when a colleague of mine rasied it. He had previously always worked with teams who used feature branching, when he moved to our team which was using trunk based development he was blown away by the freedom it gave him. He enthusiastically declared
“It’s fantastic I can do fundamental refactoring’s knowing I won’t have people breathing down my neck at some point in the future because they have merge hell”.
Sound familiar? With trunk based development the worse case scenario is maybe ½ a days worth of code to merge in.
I find it ironic that feature branching is often championed as allowing developers to work independently on features when the truth is the opposite.
#3 Test Driven Development
TDD encourages good design by nudging us in the direction of loose coupling, strong cohesion and clean encapsulation. It doesn’t of course guarantee any of these things by my experience is that coders who have and do practice TDD tend to write cleaner more maintainable code. Code that is well written is easier change our efforts upfront reduce the debt to the future.
#4 Refactor all the time
Good citizen’s pick litter up and don’t add to it carelessly so always leave code a little better than you found it. Personally I find a little bit of refactoring is a great way to understand how the code does what it does. When faced with a method or a class that does more than one thing if we start breaking it up into smaller methods teasing apart layers and introducing abstractions its purpose becomes clearer. This has benefits both for us and for future pairs who work on the code.
#5 Limit your work in progress
Control your work in progress, working at a sustainable pace is key to maintaining velocity. If you are constantly firefighting technical debt will keep piling up. Techniques such as swarming can help you here.
Remember that ultimately it usually costs a lot more to pay back than what was saved by doing it well in the first place. Left to fester you end up taking on another piece of debt to cover the debt you already took out, then another and another until the debt becomes crippling. It’s much better to save up and invest in quality upfront than borrow from your future productivity.