Addressing Technical Debt | Yusuf Aytas
A field needs to be added, a rule needs to change, or a customer bug needs a quick fix, and on paper the work looks small enough to finish in a day. Then someone points out that the old service is involved, the tests are unreliable, and the only person who still remembers why the workflow behaves that way is on holiday, which is usually when technical debt becomes visible.
Technical debt occurs when we solve a software problem with our limited understanding of the business at the time. We start building a solution to get feedback as early as possible. Instead of spending additional time on business requirements, we prefer to deliver software early. Therefore, we borrow some time from the future.
By the time we deliver the software, we might have accumulated some burden. As the software grows, our limited understanding, as well as additional factors, become hurdles. We might be okay with those limitations for a while. We can pay interest, where interest is the extra time required to make a change. We then address the debt to cut down the interest we pay. As we deliver new features, we accumulate new debt. The debt will never be zero. Therefore, we should keep the interest payments affordable.
If the debt becomes a huge pile, we might go into arrears. We would then lose the capability to deliver new changes. Typically, arrears happen when everyone is firefighting incidents and bugs instead of building.
Although technical debt does not necessarily suggest poor quality software, the industry has often used it as a metaphor for sloppiness. Thinking of technical debt as an excuse for subpar software is counterproductive. There are three fundamental reasons for technical debt: deliberate decisions, outdated design, and rotten components.
Addressing technical debt
Technical Debt Is Not Always Bad Engineering
Technical debt gets used as a nicer word for mess. No tests. Poor design. A shortcut that should have never left someone’s laptop. That kind of work exists, and calling it debt can make it sound more respectable than it deserves.
But real technical debt is not always careless. Sometimes we make a deliberate trade-off because the business needs feedback. Sometimes the design was reasonable when the system was smaller. Sometimes a component rots because everything around it changed. If every ugly part of the system gets called technical debt, the term stops being useful. You can no longer separate a reasonable trade-off from poor engineering discipline. Worse, teams start using the debt metaphor as cover for work that was simply not done well.
Where Technical Debt Comes From
Once we separate technical debt from plain bad engineering, the next question is where it actually comes from. I usually see three sources: deliberate decisions, outdated design, and rotten components.
Deliberate Decisions
One might argue that skipping unit tests is a deliberate decision. Hence, it is part of technical debt. I do not think that framing is useful. There is no justification for such a move when the team immediately pays a harsh penalty. That is not debt. That is just removing a safety net and hoping nobody falls.
The deliberate decisions that qualify as technical debt are usually around business use cases. We might lack clarity about the needs and requirements of a system. Part of the initial development is then about delivering software early, giving people something concrete to react to, and uncovering unknowns.
The developed software might not decently meet expectations. It might still be good enough for the time being. We incur debt. Many more decisions later, we accumulate much more of it.
Outdated Design
The second way to incur technical debt is through design that no longer fits. There is no way to get everything right in the first place. We do our best at the time with the information we have. We make some trade-offs. Nonetheless, those trade-offs might not work out well.
Every time we want to add a new feature, the design can cause friction. Refactoring accidentally poorly designed software might be challenging as well. Similarly, our architecture might not hold itself well against time. What helped us move faster earlier can later cause a loss of productivity. The design may have been reasonable once. The problem is that the system kept changing around it.
Rotten Components
Last but not least, rotten components and services contribute to technical debt. A software component might get more features and gradually gain more responsibility. The complexity comes naturally due to incremental changes to the system. Nevertheless, it can get to a point where the boundaries of the service become unclear.
Adding new features or clearing out bugs becomes time-consuming. People start avoiding the component. Reviews become slower because changes are harder to reason about. Testing becomes defensive because nobody fully trusts the behaviour anymore. Hence, it becomes a...