“If we’re going to tackle this now, let’s do it right this time, though.”
I heard this exact phrase recently from a team in a fast-growing tech company, expressed with a fair amount of frustration.
The team had ambitious goals, which they definitely wanted to achieve. The discussion revolved around the question of how to reach the goal. The team had to decide whether they should make extensive changes to the architecture, including the migration to a new framework, in order to be faster in the long term, or to stay with the current structure. The discussion was not easy. Everyone saw the high risk of missing the deadline with the rebuild and potentially not having anything to show in the end.
The team was in a dilemma: how to focus on quality and quickly deliver solutions for urgent requirements at the same time? On the one hand, it is important to implement things correctly and cleanly in order to solve user problems in the long term and create a good basis for future development. On the other hand, solutions for possibly very urgent requirements / user problems have to be addressed quickly in order to satisfy users and offer added value quickly.
Both are necessary to run a successful product in the long-term. The team needs to get things done right and clean as well as be fast.
How can this dilemma be resolved? There are a few assumptions underlying it that are worth taking a closer look at.
All technical debt is equal
This is not so: If a piece of code almost never needs to be touched (or understood), it’s okay to just leave it as is, no matter how messy it is. Instead, time can be better invested in places that will need to be changed, read and understood often.
Code is not an end in itself, it (ideally) always solves a user problem.
The code / architecture is restraining us in the future.
If it is clear what exactly is to be done ‘in the future’, it may be worth investing time here. If ‘in the future’ is more of a vague feeling, it is most likely not worth it. Too much can still change (Lean principle: “Decide as late as possible”).
Our users need a solution immediately.
User problems often arrive with a certain urgency in development. Nevertheless, one can ask the question of how and how quickly to react. Perhaps it is possible to tackle the problem in several steps.
Otherwise, we do not have the resources to implement the requirement.
Should the requirement then be implemented at all? Obviously there are requirements here that are higher in priority and have more claim on the company’s resources. Then - from my point of view - it does not make sense to dilute the available capacity with additional workload.
The developer knows what the correct / clean solution looks like
Unfortunately, this is very often not the case. When a major technical change is driven by an individual (or small group), there is a high probability that the result will be average or may even worsen the situation.
From the assumptions, we can derive principles that can help to reduce technical debt and at the same time drive development forward:
It must be clear to all developers what a good solution looks like. Blue prints (the team’s/company’s own design patterns) and a clear architecture vision help here. This way, everyone can understand where the deviations lie and what the ideal looks like.
Reduce technical debt only where it really hurts. Refactorings and rebuilds are most beneficial in places that need to be touched and understood often. This is where the Scout Rule comes into play. Whenever a piece of code needs to be understood or edited, it gets cleaned up (and only that piece of code) so that it gets closer to the architecture vision and the requirement can be easily implemented. It is important to ensure that the rebuild is in proportion to the requirement.
Accept bad code and messy parts. Code that never needs to be read or edited does not need to be cleaned up as long as it does what it is supposed to do.
Focus on the most important requirements. If there is not enough capacity (time and money) to implement a requirement in a way that leaves the system in a better state, the requirement should not be implemented at all. At the moment, it does not have the necessary priority in relation to other requirements. It is more worthwhile to put time and energy into the higher priority issues.
Avoid unnecessary complexity. Is this really the simplest implementation for the requirement? Is the requirement really the simplest solution to the user problem? Often user problems can be solved by very simple means once they are properly understood.
All this sounds simple and obvious at first. What is important is that the reduction of technical debt and the further development of the system for the user do not have to be contradictory. If we question the assumptions behind this contradiction and derive the right principles, there is a chance to get both.
In order to deliver features both quickly and in high quality, it is necessary to identify and question some of the key assumptions behind this conflict. This article is the first step towards doing so.
Quality and development speed are not contradictory: research by Forsgren, Humble and Kim 1 has shown on a broad basis that the companies that have their quality under control achieve a higher development speed.