Technical Debt Prevention: Best Practices
What is Technical Debt
Technical debt is a metaphor describing the accumulation of development work on the project. It is quite aptly called a “debt” because it behaves like a real debt. The more you postpone, the more you’ll have to pay for this later. It continues to the point when the system becomes unmaintainable. Usually the development team knows that there is this certain amount of work to do, but it never happens due to a variety of reasons.
Nobody can completely avoid technical debt. As soon as you give a task to developers, it becomes a “debt”. While they work on it – they pay this debt down. However, little by little some pieces of work start accumulating. When they are too numerous to handle or can affect the overall project efficiency – it’s high time to worry.
What are the reasons for technical debt?
You can easily find the full list of common technical causes on Wikipedia. However, I would like to speak from my experience. The first and the most widespread reason is the wrong definition of “done”. The entire development team should be aware of the fact that the task is actually done when:
- functionality is completely implemented
- unit tests are performed
- software has been tested by QA
- it has merged into a stable branch
- automation and regression tests show zero bugs
- code analyzer results are clean
- all documentation is updated
- the code review is complete
As a measure of technical debt prevention,the product owner should build Continuous Integration into the workflow. The working process should leverage the best software development and QA practices. Prior to building anything new, stabilization and refactoring must always follow the initial development.
What are the simplest ways to prevent technical debt?
The problem project teams face too often is the developers’ unwillingness to follow the established procedure. They way too often underestimate its value and consider it to be the boring part. Our own solution was to build the development process in a way that it was hard to do without any single element. Log properly each single step. Here are a few helpful examples:
- set source control check-in hooks
- tasks must have a strict lifecycle covering all stages from “open” to “done”
- reflect this lifecycle in a task-management tool
- inform all team members about the vitality of this procedure and make them follow the task lifecycle
Another potential source of technical debt is the newcomers. You should teach all new people on the project the proper development process nuances. The project manager can create an illustrated guideline of the task lifecycle. In general, the dev team should keep in mind that their work is not over until they merge all changes into stable branch and properly test them.
All bugs found during the unit, automation or regression testing have the highest priority. You should immediately link them to the original task. Do not close the task until you fix all the bugs. In case of time pressure, mark all “shortcuts” in the code. You should at once create “refactoring” tasks in your task-tracking system so as not to forget about this later. As a result, the “debt” is visible and the project manager can prioritize and include these tasks into a stabilization sprint.
How does system design affect technical debt?
System complexity is one of the major technical debt accelerating factors. As a rule, Tech lead is responsible for fighting this complexity by:
- modularizing the system
- applying decoupling techniques,
- following the “separation of concerns” design principles, etc
All this can reduce system complexity from an f(n*n) problem to an f(n) problem. Here “n” is a number of components that depend on each other. These two functions are illustrated on the graph.
The graph makes it very clear that it doesn’t matter how perfect your development process is if your system is tightly-coupled.
Adding new functionality to such a system costs enormous effort. Moreover, every new addition makes it worse. In practice, a project manager cannot understand why last month the task took two hours to implement and test but now the identical task takes two weeks. To make it worse, a number of bugs go out of control after adding each new feature. Those are the symptoms of a technical debt in a tightly-coupled system.
Code quality aspect
My experience shows that code conventions and static code analysis can dramatically reduce the number of mistakes:
- Turn on all automatic tools tracking code conventions. They will check your code and send warnings into the source control.
- Collect code metrics such as “depth of inheritance”, “class coupling”, “cyclomatic complexity”, “lines of code per class”. They may act as your early-stage warnings that something is going wrong. However, they cannot guarantee the absence of technical debt in other areas.
To sum up, there is, unfortunately, no single metric showing the amount of technical debt. The reason is – the process involves the whole team: from product stakeholder to a junior developer. The clearest technical debt sign is a bug rate. If the number of open bugs grows from sprint to sprint while the amount of assigned development work remains stable – it is high time for stabilization, refactoring and/or some radical management decisions.