Don’t buy into the Technical Debt Illusion
Originally, the metaphor of technical debt was introduced to help us explain the need for refactoring software systems to upper management.
But is it working? Is this metaphor helpful to explain the need for refactoring? Do we effectively convey the message? Do we reach our goal to get time to refactor by talking about technical debt? And if not, why not?
Well, to look into all those questions we have to start from the beginning.
The term was first introduced by Ward Cunningham, who compared shipping code faster by doing things we know aren’t quite right, with taking on debt. He used this concept to explain to his boss – who worked in the finance sector – the need for software refactoring.
He said: “I thought rushing software out the door to get some experience with it was a good idea. But that of course, as you learn things about that software, you would repay that loan by refactoring the program to reflect your experience as you acquired it”
Similar to normal debt, the idea is that technical debt incurs interest. So, it is important that we pay it back fast.
Since then the term has been diluted. Almost everything from smelly code to missing documentation, or even the process of hiring people without the right skill set has been attributed to technical debt.
This overuse of the metaphor isn’t helpful, as it makes it harder and harder to grasp. It almost seems like it slips us through the finger when we want to define it. Nearly everything and anything could be technical debt. So, how should we explain it?
Deliberate or inadvertent technical debt
One of the most important distinctions from my point of view is the distinction between deliberate and inadvertent technical debt, that Martin Fowler described in his Technical Debt Quadrant.
Deliberate technical debt is debt that we take on while knowing better. For example, we know we should write more automated tests, but we deliberately won’t write them to ship faster.
Inadvertent technical debt is debt that we incur despite our best efforts. This type of technical debt has a lot to do with our partial understanding of the world, and software systems in particular. So despite our best efforts, we incur technical debt. It makes us realize that even the best-designed system should have been designed slightly differently, once we have a better understanding.
It’s something that happens to us without us directly contributing to it.
Technical Inflation due to External Factors
But our changing understanding of the system and the evolution of the system aren’t the only factors why we have to go back and rework and refactor.
There are also external factors such a rapidly changing technologies and changing markets that cause even the best designed and implemented systems to incur technical debt. And for exactly this kind of decay, I believe a different term might better convey the message of what is happening. So, I’d like to introduce inflation as a metaphor for external evolutionary circumstances that lead to the decay of a software system.
Similar to financial inflation, you have to make investments in order to keep the same value. For our systems, this would mean that we have to invest in refactoring on an ongoing basis to fight inflation. This way we ensure to keep the system up to date with technologies, the market, and changing requirements.
Inflation isn’t something we decide for or against. It is something that happens inevitably. External factors that we have little to no influence on but that we have to deal with.
Similarly, our changing understanding of the system and the world also can be attributed to inflation. We can do our best to understand the requirements, the market, the system, but at the end of the day, learning and understanding is an ongoing process.
If we look at for example lean development approaches the line between deliberate and inadvertent becomes even more blurry.
Be careful with deliberately taking on technical debt, as you'll have enough technical inflation to deal with anyway. Click To TweetReleasing as early as possible builds up debt
While I am in favor of releasing software early and frequently, it surely aggravates the problem of only partially understanding software systems.
It’s great to get early user feedback to make sure you are building the right product, but the need for refactoring activities is a direct outcome of this approach.
The earlier the product and the rougher the idea and your understanding, the higher the chances you have to go back to the drawing board.
You release a proof of concept to validate the idea. And then, you go back and implement it from scratch again the way it is supposed to be.
Such a decision is a deliberate act of taking on technical debt. We know, we will have to come back and improve.
Without following through by acting upon our newly acquired wisdom, we are just releasing software with a lot of technical debt.
And while many people will agree that prototypes and MVP need a lot of rework, it gets trickier for more stable and mature systems.
Why would you rework a feature you just released? Why should we spend time reworking older parts of the system that seem stable? Why should we change parts of the architecture?
Well, I think the better question is why not? Why wouldn’t we act upon our desire to improve the system? Why wouldn’t we refactor if we know the current design will haunt us in the long run?
Rushing software out the door to get an understanding
As Cunningham said, we are rushing software out the door, to get an understanding, but also to come back and adjust the system accordingly.
That sentence reflects perfectly a large part of our current software world.
But, if you stop for a moment, and soak up that sentence. Doesn’t something seem off?
For me, it is the word rush.
Rush seems to point us in the direction of the underlying problem of technical debt. Or better, why we often can’t or won’t pay it back.
Rush stands for pressure, tight schedules, and the need to release software at all costs.
Well, that can be expensive, and also tragic, as the story of Boeings 737 fatal crash shows.
But sometimes we are in a rush. And, isn’t the premise of technical debt that we can take it on to have some short-time advantage?
A technical debt fairy tale
Let’s imagine you are the CTO of a start-up that develops innovative tax software for cryptocurrencies. You and your team have been working on this software for several months now.
Unfortunately, as so often with software projects, development took longer as expected. The pressure is high as the tax season will start soon. The more you sit over the feature roadmap, and the more you see the actual progress, the more you realize you can’t finish the product in time as designed.
So, what do you do?
Together with your team, you cut some features, and you decide to implement other features in a less than optimal way.
That is because your number one priority is to ship the software before the end of this tax season. Otherwise, nobody wants your software anymore. You will have to wait for the next tax season to start in a year. You know, that if that happens the company will run out of money and die in the middle of the so-called “death valley” of startups.
In this scenario, taking on technical debt can actually save your company.
The time to market pressure
In this scenario, the software development team takes on debt in order to reduce the time to market. The assumption behind this is that because the time to market was (drastically) reduced, it allows the company to be successful and justifies the take on of technical debt.
But even in such a high-pressure situation, taking on the technical debt could be the wrong decision. As we can imagine, sacrificing code quality might have devastating effects.
But, I don’t want to argue about this. I want to look at something much more profound. The reasons for the schedule pressure.
The real reasons behind schedule pressure
Studies have shown that pressure to deliver on schedule is the number one reason for accumulating technical debt. But what are the real reasons for the pressure? Are the reasons for the pressure as profound as the one we just imagined?
Often, the root cause of the pressure to deliver on schedule might be due to unhealthy ambition, unhealthy expectations, and badly managed software development efforts in the first place.
Sometimes it is just because someone, somewhere wants the software to be delivered at a certain time. And that’s a receipt for disaster.
Sometimes we sacrifice code quality just because someone, somewhere wants the software to be delivered at a certain time. And that’s a receipt for disaster. Click To TweetCan we work without hard deadlines?
But do teams need the pressure to perform (well)? In my experience, many successful teams work without hard deadlines. For them, schedules are loose concepts that help them to make better estimates.
Schedules help to understand early on if plans and reality match. If not, it is the team’s responsibility to adjust the expectations and the plans, but they do not sacrifice the code quality. Only in very rare cases, like an important external event, or a concrete time window to enter a market, or a contract that binds the team to a specific date, should the team think about which short-cuts one could make to ship faster.
So it’s important to really understand where the pressure is coming from. In quite a few companies, pressure is used as a means to increase productivity. But productivity is a complex concept.
Indeed, putting a lot of pressure on development teams might get them to be more productive. Productive in a way of producing more in less time. But, your goal should not be to have more code.
Simple is harder than complex
Famously, simple is harder than complex. So, it is important to look right through the wish for productivity and strive for effectiveness instead. What are the long-term and short-term effects that you want to achieve?
And more often than not, putting unnecessary pressure will not help your team achieve the desired effects.
So, let’s strive for high-quality code. Because technical debt and technical inflation happen without you actively taking it on – if you want it or not.
Looking at long-term performance, I agree with Martin Fowler that internal quality and costs aren’t a tradeoff in software engineering.
And, the real problem of technical debt that you take on due to tight schedules is just about to begin.
The ripple effects of technical debt
The real danger of technical debt lies in its compounding effects. Just like real debt. At first, the debt does not look that high. But, over time, as it accumulates, it becomes this overwhelming amount that cannot be paid back anymore.
[Technical debt] just gets gradually worse and worse; it’s only gradually worse than it was the previous day. So, it’s always easy to say, well, I’m going to put this off until tomorrow or until the next release or whatever because you can always do that. But, at some point, you get overwhelmed by it.
Participant of a study on technical debt*
*here is the study of Lim et. al.
A simple example is a badly named method. Every time you have to use that method you first have to scratch your head and dig a bit around to understand what this method does. If that’s the case, you just identified a tiny bit of technical debt in your codebase.
Now, consider how often you use that method and how often you scratch your head. That’s the interest you pay. And as days and weeks or month go by, this time (which was at first few minutes) builds-up to become a whole hour.
And now, imagine all the other places in your code.
The deprecated library that you still use. The workaround that needs quite some manual preparation of the data from your end. And actually, you also know that some of your architecture has to be redesigned, as the system evolved differently as first anticipated. All of this is technical debt, and if you can feel the effects of it on a daily basis you should start paying it back.
The deal breaker just like with financial debt is whether or not you are able to pay off the interest of the technical debt. Click To TweetBut how do we pay it back?
The best way to reduce technical debt is to not deliberately take it on in the first place. But that’s easier said than done. Many development teams struggle with it. Be it because technical debt slowly creeps in, or because pressure from above is so high that teams cannot perform well.
So, if we already accumulated technical debt, what are the best ways to pay it back? Do we have to refactor the whole codebase? How do we get management on board to pay back the technical debt?
Building awareness to tackle technical debt
Building awareness about technical debt is the first important step to start reducing the debt in your codebase. Developers that know how a quality codebase looks like, that know best practices and design patterns are normally able to spot and prevent technical debt. A wonderful practice to help the whole team to improve code quality and to learn is to use code reviews.
In some teams, it helps to add the high-level task of “reducing technical debt” as a deliverable. Therefore, occurrences of technical debt are spotted in the codebase and sorted by priority. And then, similar to feature requests or bug reports, the act of reducing those technical debt occurrences is added to the work items.
But just adding them to the backlog isn’t gonna cut it. Because those items will most likely continue to be pushed down to future sprints. So, you have to have a routine in place that makes sure reducing technical debt is actually done.
The rule of not allowing code quality to deteriorate can help teams to keep technical debt at bay. Even better is the rule of always checking-in code that is better than before.
Still, even when your development team has a good understanding of code quality and technical debt other stakeholders might not.
Making technical debt approachable outside of engineering
Understanding technical debt is harder for stakeholders outside of engineering. To help them, it is important to convey the business value of a high-quality codebase. Otherwise, people outside will continue to increase the pressure that leads to technical debt in the first place. And in addition, the same pressure causes tight schedules that do not allow the team to pay off technical debt.
A devil’s circle.
This means we have to show management the tangible effects of a high-quality codebase as a motivator. If we can’t, it becomes (understandably) hard to justify why we should reduce technical debt.
But is technical debt a problem?
Maybe it isn’t important that we have nicely written code. Maybe some developers are just obsessing about the architecture? Maybe, developers want to produce this perfect piece of code that isn’t necessary.
Indeed, coming up with the plan of running a static analysis over a codebase to then stop development for three weeks in a row to change all problematic areas of the code, isn’t that great of an idea. Luckily, only very few people are so eager to clean up the code.
What helps when addressing technical debt is to think about whether it affects your present and your future work. The more the technical debt is affecting your daily work, the faster you should act upon it.
The main problem of technical debt lies in the time you have to spend longer as a developer on fixing a bug, adding a feature, or the time it takes you longer to comprehend a certain part of the system.
The costs of technical debt
Well, so what are the costs of technical debt? The most common ones are associated with the fact that the codebase gets less understandable, and brittle. It gets harder to add features or to change parts. So, reduced code quality results in reduced code velocity.
Further, increased complexity in the system also leads to poorer performance and fragile code.
If the system has a poor performance and isn’t stable this causes a poor customer perception. Leaving you with unhappy and angry customers. Well, if a happy customer isn’t a business value, what is?
Technical debt: a two-sided sword
If we manage to convey the message of technical debt and to make a connection to actual business needs and value, using the technical debt metaphor can be powerful. It can help all stakeholders (developers and management) to make the right decision together.
In rare occasions, it can be that incurring technical debt makes sense. Often, the right decision can be to extend deadlines to avoid technical debt or even to give the development team time and space to pay some of the accumulated technical debt back.
If we try to explain technical debt as a tradeoff between cost, time and quality, we aren’t doing ourselves a favor. The falsity behind this reasoning is that in software there often isn’t a direct linear tradeoff relation between cost and quality. If we use this to describe it to management, no wonder, they go for the cost factor.
Another problem I see is that nowadays debt isn’t something people are afraid of. With the low-interest rates, everybody wants to incur it instead. Isn’t that how businesses are built right now?
You take in some debt and only pay as little as possible back while taking on more debt, to buy more. Unfortunately, that strategy does not work in the tech world.
A high-quality codebase is a developer’s responsibility
Finally, I want to close with an exercise that I read on another blog*. We often hear that management is the reason why we incurring technical debt. That, if only management would allow and support us, the codebase would be in a much better state.
But now, let’s take a step back.
Why did they hire you as a developer in the first place? Did they hire you because they trust in your expertise? Because they believe you know what you are doing? Yes?
Well, then, they should also trust you that you know about the long-term effects of certain decisions. They should trust you that you do a good great job.
Now you say, well, but I haven’t been given authority to do that.
Well, you probably have the authority to add, delete and edit the code. To add files, edit and delete files. They trust you can make decisions on how to implement a certain feature. Why shouldn’t that extend to the trust that you are the right person to add tests to the code? It seems that it also includes that you are the right person, with the right expertise and authority, to decide to refactor a method.
Again, I am not talking about starting a complete re-write of the codebase. But, surely, you have the knowledge, expertise, and authority to ensure your company writes reliable, scalable and maintainable code.
Together as a team wouldn’t you be able to even convey the importance of major architectural changes?
I’m not asking you to misbehave. This was a mental exercise. A confidence boost. So, trust in yourself, your competences and expertise, and help others understand the value of a high-quality codebase.
Lead the way to a sustainable solution and set the company up for long-term success. And don’t buy into the current debt illusion!
*I’ve been searching for hours for the original blog post, but unfortunately, I can’t find the source anymore. If you know it, please leave a comment and I’ll add the link.
If you are interested in how other developers deal with technical debt, or if they have automated tests, or do code reviews, check out my Software Engineering Unlocked Podcast. Get yourself subscribed today, and make me happy! ❤
Another treat I have for you is my code review e-book. Check it out here.
And don’t forget to connect with me on Twitter. I have many interesting discussions going on there. Come and join me!
Great article! I see the same pattern in my current gig. The problem I face is I’m working in a feature factory and battling a product owner that does not feel the pain of working within the codebase. Also the product direction does not come within the team it comes top down from management so these external drivers again cause huge friction as we have zero say in steering the product roadmap.
Yeah, what you describe are common underlying problems developer teams face when doing code reviews. If policies and processes are designed by stakeholders that do not completely understand the benefits and pain points of code reviews, everybody suffers.