Tuesday, February 10, 2015

Don’t waste time tracking technical debt

For the last couple of years we’ve been tracking technical debt in our development backlog. Adding debt payments to the backlog, making the cost and risk of technical debt visible to the team and to the Product Owner, prioritizing payments with other work, is supposed to ensure that debt gets paid down.

But I am not convinced that it is worth it. Here’s why:

Debt that’s not worth tracking because it’s not worth paying off

Some debt isn’t worth worrying about.

A little (but not too much) copy-and-paste. Fussy coding-style issues picked up by some static analysis tools (does it really matter where the brackets are?). Poor method and variable naming. Methods which are too big. Code that doesn’t properly follow coding standards or patterns. Other inconsistencies. Hard coding. Magic numbers. Trivial bugs.

This is irritating, but it’s not the kind of debt that you need to track on the backlog. It can be taken care of in day-to-day opportunistic refactoring. The next time you’re in the code, clean it up. If you’re not going to change the code, then who cares? It’s not costing you anything. If you close your eyes and pretend that it’s not there, nothing really bad will happen.

Somebody else’s debt

Out of date Open Source or third party software. The kind of things that Sonatype CLM or OWASP’s Dependency Check will tell you about.

Some of this is bad – seriously bad. Exploitable security vulnerabilities. Think Heartbleed. This shouldn’t even make it to the backlog. It should be fixed right away. Make sure that you know that you can build and roll out a patched library quickly and with confidence (as part of your continuous build/integration/delivery pipeline).

Everything else is low priority. If there’s a newer version with some bug fixes, but the code works the way you want it to, does it really matter? Upgrading for the sake of upgrading is a waste of time, and there’s a chance that you could introduce new problems, break something that you depend on now, with little or no return. Remember, you have the source code – if you really need to fix something or add something, you can always do it yourself.

Debt you don’t know that you have

Some of the scariest debt is the debt that you don’t know you have. Debt that you took on unconsciously because you didn’t know any better… and you still don’t. You made some bad design decisions. You didn’t know how to use your application framework properly. You didn't know about the OWASP Top 10 and how to protect against common security attacks.

This debt can’t be on your backlog. If something changes – a new person with more experience joins the team, or you get audited, or you get hacked – this debt might get exposed suddenly. Otherwise it keeps adding up, silently, behind the scenes.

Debt that is too big to deal with

There’s other debt that’s too big to effectively deal with. Like the US National Debt. Debt that you took on early by making the wrong assumptions or the wrong decisions. Maybe you didn’t know you were wrong then, but now you do. You – or somebody before you – picked the wrong architecture. Or the wrong language, or the wrong framework. Or the wrong technology stack. The system doesn’t scale. Or it is unreliable under load. Or it is full of security holes. Or it’s brittle and difficult to change.

You can’t refactor your way out of this. You either have to put up with it as best as possible, or start all over again. Tracking it on your backlog seems pointless:

As a developer, I want to rewrite the system, so that everything doesn’t suck….

Fix it now, or it won’t get fixed at all

Technical debt that you can do something about is debt that you took on consciously and deliberately – sometimes responsibly, sometimes not. h

You took short cuts in order to get the code out for fast feedback (A/B testing, prototyping). There’s a good chance that you’ll have to rewrite it or even throw it out, so why worry about getting the code right the first time? This is strategic debt – debt that you can afford to take it on, at least for a while.

Or you were under pressure and couldn’t afford to do it right, right then. You had to get it done fast, and the results aren’t pretty.

The code works, but it is a hack job. You copied and pasted too much. You didn’t follow conventions. You didn’t get the code reviewed. You didn’t write tests, or at least not enough of them. You left in some debugging code. It’s going to be a pain to maintain.

If you don’t get to this soon, if you don’t clean it up or rewrite it in a few weeks or a couple of months, then there is a good chance that this debt will never get paid down. The longer it stays, the harder it is to justify doing anything about it. After all, it’s working fine, and everyone has other things to do.

The priority of doing something about it will continue to fall, until it’s like silt, settling to the bottom. Eventually you’ll forget that it’s there. When you see it, it will make you a little sad, but you’ll get over it. Like the shoppers in New York City, looking up at the US National Debt Clock, on their way to the store to buy a new TV on credit.

And hey, if you’re lucky, this debt might get paid down without you knowing about it. Somebody refactors some code while making a change, or maybe even deletes it because the feature isn’t used any more, and the debt is gone from the code base. Even though it is still on your books.

Don’t track technical debt. Deal with it instead

Tracking technical debt sounds like the responsible thing to do. If you don’t track it, you can’t understand the scope of it. But whatever you record in your backlog will never be an accurate or complete record of how much debt you actually have – because of the hidden debt that you’ve taken on unintentionally, the debt that you don’t understand or haven’t found yet.

More importantly, tracking work that you’re not going to do is a waste of everyone’s time. Only track debt that everyone (the team, the Product Owner) agrees is important enough to pay off. Then make sure to pay it off as quickly as possible. Within 1 or 2 or maybe 3 sprints. Otherwise, you can ignore it. Spend your time refactoring instead of junking up the backlog. This isn’t being irresponsible. It’s being practical.


jagercode said...

Quite think so.
Budget is always short and explicit technical debt is first to drop from the list.
Therefore, make sure to hide any technical dept that really must be payed in the must-do's on the backlog.

Anonymous said...

I don't classify 'not following coding guidelines' and 'magic numbers' (and other such things) as technical debt. That is more an indicator to target the programmer responsible for more training attention. If you don't have bugs there, you don't touch and it just is what is.

Technical debt is poor/no design that leads to bad things. I've seen poor design that works, but to extend it is nightmarish.. or very poorly designed/written code that is very buggy.. where fixing even the simplest bug can take days, not hours. That is technical debt you can't ignore and must fix at some point..

Bottom line, I think the author misses the mark on WHAT is technical debt, and what is not.

Jim Bird said...

There is no one-size-fits-all definition of technical debt. Many developers - and most tools that look at technical debt - do consider inconsistencies in code and hard coding and other minor cruft to be debt because it can make the code harder to understand and change. It takes time to understand that isn't consistent and to figure out magic numbers and so on. My point was that this is debt that can be paid off daily with a small refactoring tax, as long as this kind of code is the exception rather than the rule. If too many people on the team don't care about or don't know about or don't have time to write consistent clean code then you will end up with code that is harder to understand and more difficult to change as time goes on - which is what technical debt is about.

TiTi said...

Interesting article.

Let's talk about this particular sentence:
"If there’s a newer version with some bug fixes, but the code works the way you want it to, does it really matter? Upgrading for the sake of upgrading is a waste of time, and there’s a chance that you could introduce new problems, break something that you depend on now, with little or no return."

It's a typical question I caught myself having frequently. Probably not the only one.
This is linked to a lot of topics: accountability, responsability, care, quality, reliability, risk managment, ...

Sure, upgrading might breaks some existing things. I've seen it, multiple times. Maybe there isn't enough tests ? Or maybe this is tricky.
Or maybe you're too confident. And it happens: something broke :-/

But there is also a greater risk not to upgrade: get behind.

For instance, we had this dilemma with jQuery.
Code was working "fine" and they were no fonctionnal motivation to upgrade jQuery, however several devs wanted to upgrade it.
Upgrading seemed to be important in order not to get too much behind...
We were already several versions behind and jQuery release cycle wasn't going to stop. Furthermore, the application was heavily relying on jQuery.
We felt like migration path was easier when upgrading frequently, rather than after several months or years.

At that point, we had white card on the project. So we did upgrade jQuery several times.
Potential issues seemed hard to guess beforehand. State of mind was mostly "let's upgrade and fix things rapidly, if any".
Upgrade was not that painful, but it didn't go without incident.
One manager saw that as dangerous and useless work, so he went berzerk: "do not update jQuery anymore". "You don't need too", blah blah blah

However, bugs were present. And we were of course responsible to fix them.
But the game was to keep using the same jQuery version...
Therefore people were sometimes spending several hours to bypass a jQuery bug by working around in our code :-/
Crappy fix over here, whole rewrite over there, missing comments to explain the why/how, you get the idea...

At one point we even had to patch jQuery with a fix from the next version because upgrading was out of the question.
And it was becoming way too dangerous to simply upgrade jQuery...
So I did patch jQuery, generate a minified version and voilà, fix was in place.
But we ended up having a custom jQuery in production at that time... which felt like going off-road in a dangerous jungle.
Patching your bikes tires work once or twice, but at one point, you know this isn't viable. You've gotta change the whole tire.

As you said, "this is strategic debt - debt that you can afford to take it on, at least for a while".
And it's not that easy to know how long you can live like that without going into danger zone.

You mentionned this point in your comment: "you will end up with code that is harder to understand and more difficult to change as time goes on".

Maybe we should have used a more stable library and stick with a specific version.
But browsers evolve ; our usage of the library too.

I agree with your feeling: "debt that can be paid off daily with a small refactoring tax, as long as this kind of code is the exception rather than the rule".
We struggle with complex situations and we have to make choices.
One might also fear to bring up the subject because consensus might be painful to get.

The balance is hard to guess. And Nobody Ever Gets Credit for Fixing Problems that Never Happened.

Even if the house is not modern, make sure to do household and maintenance regularly.
Otherwise everything will fall appart, more or less slowly.

Jim Bird said...


Thank you for your thoughtful comments. Your jQuery example is something that we can all learn from. Upgrading, or putting off upgrading, a key part of your technology stack is a high stake game. There's no easy call to make.

If you upgrade, you have to recognize the costs involved, testing, whatever fixes or workarounds you need to make, the opportunity costs of not doing other work that the business wants, and the risks of regressions. Trade these off against the advantages that you get from upgrading.

If you put off upgrading your stack for too long, you could get boxed in, limiting your ability to offer new capabilities or forced to deal with compatibility problems. At least now you have a better business case for upgrading.

Anonymous said...

Some interesting points all round...

All I want to say is that while rule violations (e.g coding style, magic numbers, etc) may or may not be technical debt, what they certainly are are broken windows. That is, the type of thing that encourages less conscientious developers to write clean code.

@philn5d said...

I'm in the thick of this right now. Another dev upgraded the ORM framework in a custom app framework we have and you know what happened next!? Now we have another app based on our framework that's broken as a result. It's costing us time and taking away from adding value to mitigate. And for what? Shaving a few millis off a data load!? There's enough debt in this system to just start over anyways, why bother refactoring unless its needed at this point. We don't track it, but we do need to put in front of the managers so they understand. We have in some ways and perhaps we've made enough noise to get the right thing done.

Site Meter