Technical Debts have been in the center of software engineers debate for quite some time. Not only its analogy to financial terms has been discussed, but also the different contexts and aspects a debt contains.
For me, the most suitable definition is that technical debts describe the additional work to rewrite an already-existing feature correctly. Developers tend to distort this definition by including debts from any nature. In contrast, they should only consider features that explicitly have been under-designed to meet the project schedule or customer expectations.
When business evolves, it deprecates lots of technical decisions made in the past. Experienced developers can extract knowledge from those mistakes and be aware that it’s part of the development flow. It should be natural to make architectural, design, and code changes to meet the new business context.
There are other kinds of decision, though. As humans, we may make incorrect decisions that need to be fixed. I don’t see those decisions as technical debts, but merely as bad decisions. And, of course, it’s vital to fix them.
Sometimes, though, the team chooses to implement a suboptimal solution to meet external requirements, like time to market. It generates a debt which the team needs to pay in a short time.
How to differentiate desirable technical debts from undesirable ones? I listed some examples below to distinguish those scenarios.
Desirable technical debt examples
When financial loss is high
Some features may lead to immediate loss of money. For instance, a bug in an e-commerce store not allowing customers to checkout. In this case, given the urgency, it may require a rollback to the previous version or to ship a messy code as soon as possible to fix it.
When developers deploy quick fixes for a production bug while they work on the definitive repair, they loan imaginary credits. This peculiar kind of debt should be paid as soon as possible. Its permanency in the codebase can generate a cascading effect since another feature can rely on this piece of code or behavior. The more time you take to replace for an adequate code, the higher the risks.
When it avoids over-engineering
I’ve worked in projects in which developers would create lots of CRUDs to parameterize the behavior of features. Unexperienced Product Owners and Managers always want control over everything as possible.
However, it comes with a price. It adds extra layers of complexity to the code. Writing simple and clean code is hard, and requires a bit of experience. Debts that avoid complexity in the codebase allows faster code shipping and reduces complexity.
A good example is when the team decides to hard code some configurations settings or a list of options in a select box. A good rule for these decisions is to ask some questions:
- Are those settings or items going to change frequently?
- Are the settings related to the business (like pricing policies or discount rates), or just ordinary configurations (like a list of available currencies or countries, or the number of items shown per page)
- Will it be time-consuming to maintain those settings up to date?
Another example is to set up a robust architecture design before needing it. Very similar questions can lead to a minimal but efficient solution.
When there’s business uncertainty around the feature
There’s no reason to implement an experiment in the best way possible. It would consume too much time and effort from the team.
It doesn’t mean the team should deploy a messy code. Agility requires quick prototyping and testing, which can be achieved without a hurry and with a clean code. It’s crucial, though, that the solution remains isolated and, thus, easy to be removed or refactored. Working this way saves time and sanity of the team.
Undesirable technical debt examples
When feature toggles are misused
Feature toggles are often misused. Following the Continuous Deployment philosophy, developers would use feature toggles to prevent the execution of uncompleted and untested code in production. It happens because CD implies committing the changes straight to the master branch to avoid conflicts during rebasing and merging branches.
Toggle, however, must be removed someday from the codebase. It means that as soon as your code reaches the master branch, you already have a technical debt to tackle in the future.
The risks arise when developers place toggles inside another toggle, like a “toggleception,” as testing is painful. Developers usually don’t add a test scenario for every possible toggle combination.
To scary you a bit more, debugging gets much harder, because the software behavior may change depending on what’s on and off in the different number of environments.
The only plausible scenario is a feature rollout on a specific date. Therefore, there are business reasons to only make that feature available after some external event. Otherwise, I strongly discourage its use.
When it’s part of the culture
I’ve worked in projects that words like “refactoring,” “rewrite,” and “technical debt” were like invoking a curse. Stakeholders couldn’t hear the word without rolling their eyes and discrediting developers.
It may happen due to a lack of experience of the team, but it’s not always the case. Debts may come from neglected work when developers give in too much to the hurry imposed by the business and give up fundamental principles established by good practices and quality.
Sooner or later, the codebase becomes a mess, and implementing new features gets slower due to higher complexity. A need for rewrites, refactorings, technical debt payments emerges for every change, even for the small ones. After granting concessions for an extended period, it gets absorbed by the company’s culture, and changing it is puzzling.
When team neglects project gardening
Project gardening is the act of cutting the edges, improve the existing code in every iteration, updating dependencies, and rewriting small parts of the codebase to achieve a cleaner and healthier code.
The lack of gardening generates a broken window effect, which prevents essential maintenance work because of the “it’s already ugly, improving a little bit makes no difference” feeling. It also may impact other aspects, like neglecting small changes and improvements because they are boring. As developers postpone the gardening, they put the work in the technical debt pile.
To be clear, I don’t think it is technical debt. Lack of gardening in a project in just neglected work originated in an unsustainable culture.
For a technical debt to be desired, it should be strategic. The team must align with the decision and pay as soon as possible this debt. The longer a proper solution delays, the higher the risks of implementing new features upon this disposable code.
Most of the undesirable technical debts come from an unhealthy culture when hurry or slouch overcomes agility. Teams should not only avoid this kind of debt but also count it as a measure of code quality.