top | item 20685950

Three Kinds of Good Tech Debt

426 points| memset | 6 years ago |engineering.squarespace.com

152 comments

order
[+] davnicwil|6 years ago|reply
The article links tech debt to financial debt early on, and this is exactly what the analogy is - it is best to be as literal as possible about it.

What is good debt? It's taking money from someone, promising to pay them back more money in the future, and using the original money to make even more money than that, so both you and the lender profit.

Good tech debt is just the same. It's taking shortcuts now to save time and get something shipped, knowing that it will cost you more time than you're saving now to fix it later. But by shipping now, you ensure there is a later, with a product that's out there and being used, a bigger team and therefore adequate time and then some to fix the debt and keep improving the product. Everyone wins.

That's the theory anyway. I think mostly where it goes awry in the wild is people forgetting the paying it off part. Part of this deal is you have to pay back your lender - they have to win too. It's just easily forgotten because you're the lender, so the incentives get a bit conflicted, and you'll keep giving yourself more time. But if the lender ultimitely doesn't get paid back and loses, you lose, and it's a bad debt.

[+] TheLastPass|6 years ago|reply
One important difference between tech debt and financial debt, is that if the project you accrued a bunch of tech debt in gets killed for unrelated reasons before it's completed, your debt is forgiven. You never pay it off, and you never have to.

When you're validating an idea, don't worry about making everything perfect. Worry about validating the idea. Once you're more confident that you're actually gonna do it long-term, then it starts being more beneficial to pay off the tech debt.

[+] tempestn|6 years ago|reply
> I think mostly where it goes awry in the wild is people forgetting the paying it off part.

Which also fits the financial debt analogy perfectly.

[+] matchagaucho|6 years ago|reply
Ward Cunningham, who coined the term "Technical Debt", would often use the analogy "If you borrowed $500K for a house that is now worth $1M..."
[+] tziki|6 years ago|reply
A big part of technical debt is that often... you just don't have to pay. This is what makes the comparison to financial debt a very poor one.

Whenever I'm moving from one framework to another I find loads of todos I can now delete. Or even if I have a well designed class, if I can find a nice third party library that does the exact same thing, I can now get rid of all that useless code and any debt around it.

Honestly I think calling it technical 'debt' makes programmers unreasonably averse to it, but unfortunately I haven't been able to come up with a better term.

[+] wellpast|6 years ago|reply
> The article links tech debt to financial debt early on, and this is exactly what the analogy is - it is best to be as literal as possible about it.

Financial debt has a clear quantitative measure.

Tech Debt is an incredibly fuzzy concept, even as a qualitative measure, to the point of being useless for anything other than covering one's ass. We can do better as engineers than resting on such fuzzy thinking.

[+] tyingq|6 years ago|reply
A little trickier maybe in that some tech debt is in the eye of the beholder. Some may consider an Angular UI tech debt, others might consider it an asset versus, say, React + whatever other parts it needs.
[+] julianozen|6 years ago|reply
Two points I really enjoyed:

> Not Fixing All the Edge Cases

We’ve all seen bugs that occur so rarely, and fixing requires destroying some part of the original design architecture. The ability to acknowledge these types of bugs as not worth fixing is liberating.

> Err on the side of building too little because you can always build more later. Build things to be easy to throw away and replace;

I find developers tend to really dislike throw away code or temporary solutions knowing they might break in the future. If the quick hack is well documented, written in a very encapsulated and removable way, who cares about removing it later.

[+] gmueckl|6 years ago|reply
Writing hacks and throw-away stopgap solutions is generally a bad idea in reality. The only time when it can actually be allowed is when it it concerns a feature that is a pure leaf in the system with nothing else built on top of it. If you violate this rule and build other features on top of shortcuts, two things will inevitably happen: you will have to replace the shortcut with a proper solution eventually (it is only a matter of time) and by the time this happens, the assumptions underlying the shortcut have implicitly been taken on by the code depending on it. This means that you will have to review and replace a lot of dependent code as well.

I have gone through that dance often enough. It ends up being more time consuming than doing it right in the first place each and every time.

[+] marcosdumay|6 years ago|reply
Building things to be easy to replace often does not mean quick hacks. Quick hacks are most likely hard to trow away, not easy.

Building things to throw away means componentizing your code, that is extra work overall.

[+] hoorayimhelping|6 years ago|reply
>If the quick hack is well documented, written in a very encapsulated and removable way, who cares about removing it later.

I do. I care very deeply about removing it later.

The problem is that encapsulation is often mistaken as a strong justification for existence, and code that is written tends to justify its existence just by existing.

That's to say that once code is in production, it's scary to delete. It's much less scary to delete a couple of lines of inline code wrapped in a comment that says "this is a hacky solution," because code that isn't encapsulated probably probably isn't used elsewhere. Or if it is, it's copy-pasted.

The scary thing about removing encapsulated / abstracted things, is that oftentimes, other things refer to them, unbeknownst to the original author or their intent.

It's pretty common (especially on larger teams) - the first developer writes up a hacky solution, the second developer abstracts that hack into a module due to some sensibility (abstraction, aesthetics, organization, etc), so now the hack has legitimacy. A third (or often the first developer) sees that the hack is in a module, assumes that the person who put it in a module had more information than when the hack was written and that it's now a first class member of the system. And first class members of a system take thought and time to delete.

[+] stcredzero|6 years ago|reply
> Not Fixing All the Edge Cases

This can also be a part of tackling very complex tasks. To prevent getting overwhelmed when implementing something huge, I sometimes do what I call, "Implementing something stupid." Want to build an MMO? Build something that manages to send sync information to a multiple clients, but ignores auth, security, being cheat-proof, robustness, etc... Then once there's something running, start addressing those issues.

So instead of not fixing all the edge cases, it's not implementing all the edge cases all at once.

If the quick hack is well documented, written in a very encapsulated and removable way, who cares about removing it later.

When it starts causing "lava flow" then it's time to remove it.

http://antipatterns.com/lavaflow.htm

[+] stronglikedan|6 years ago|reply
> I find developers tend to really dislike throw away code or temporary solutions knowing they might break in the future.

IMHE, it's usually management that feels this way.

[+] BurningFrog|6 years ago|reply
> I find developers tend to really dislike throw away code or temporary solutions

This is good way to level up: Learn to love throwing your code away!

[+] human20190310|6 years ago|reply
It's also great that the author gave concrete examples of times where it made sense to eschew purity for pragmatism. Very good article all-around.
[+] Nursie|6 years ago|reply
I love throwing away code!

We found a better way to do this - boom, you're gone!

I do dislike coding solutions which I know to be temporary, sometimes it's necessary but it feels wrong if we already know something's not going to be adequate.

That said it's also very bad to future proof (by which a lot of people mean abstract) the code to be able to do everything you can possibly think of that might be needed in future, or even (and I've seen this attempted) things we might possibly want that we don't even know about and can't think of!

[+] twblalock|6 years ago|reply
> If the quick hack is well documented, written in a very encapsulated and removable way, who cares about removing it later.

That's fine in a system with a small number of quick hacks. In reality, the number of quick hacks grows over time and you end up with a brittle system that has a lot of unanticipated behavior that only a few people understand how to work with. You will eventually have quick hacks that exist to fix bugs in the other quick hacks.

[+] theptip|6 years ago|reply
I like the framing of tech debt as value-neutral; whether it's good or bad depends on how you use it (just like financial debt). Of course, all else being equal, you'd rather have zero debt.

One higher level consideration that's particularly relevant for early-stage startups, is that you often simply don't have time to build the ideal "tech-debt free" solution. Focusing on 80/20 solutions almost necessarily involves adding tech debt. However, if you're mindful about where you take on tech debt, and commit to tapering and then paying it down as your resources grow, then you're more likely to avoid the debt spiral where your development grinds to a halt, due to everything breaking all the time.

One other point I'd make is that a common form of "tech debt" for early-stage startups is simply doing things that don't scale; it's often beneficial to make your domain models flexible so that you can support one-off manual interactions, for example if a new customer appears that requires it. Then automate that thing and constrain the domain actions once you have collected enough use-cases to know what makes sense to forbid.

[+] WorldMaker|6 years ago|reply
I like to point out as well that it isn't tech "debt" if there's no "IOU". For instance, in the hard-coding example, it's conventional wisdom to avoid hard-coded magic constants for a few reasons including it sometimes is hard to figure out later why a magic constant was used, and in an if/else branch situation like that maybe tough to figure out when you can cleanly refactor those branches. If the case is well documented (it's the date of a given feature release) and the code reasonably factored already (the "off" branch isn't a ticking time bomb bug that needs to be removed after the date in question), then it may be a code smell, but it isn't necessarily an IOU to a future developer (such as yourself), so it isn't necessarily "tech debt", it was just a trade off made at the time. Of course, if a future developer adopts a hard linter rule against that sort of code smell, they may come to see it is tech debt retrospectively, but that's also where the analogy starts to get fuzzy.

(Pushing the analogy to a breaking point: Is adding a linter then, doing an audit and depreciating assets for "tech taxes"? Come to think of it "tech taxes" would be a fun alternative term to add to the "tech debt" family. "Hold on, just paying my tech taxes; npm install, npm audit, and npm run lint.")

[+] spc476|6 years ago|reply
I don't like the metaphor. With financial debt, you get $X up front for $Y (where Y >= X) and pay $Y amount over N months. It's known up front what it's costing you (Y - X) over what time span (N). If the difference between Y-X is low, congrats! You got a great interest rate. If it's large, well, good luck!

But in both those cases, the pain is known up front. The discussion here seems to be no one knows what X is, nor Y, but it's expected that Y > X by some large amount that you may or may not have to pay off. Crazy!

[+] halfmatthalfcat|6 years ago|reply
As I’ve gotten more senior in my career, I think one of the most enlightening things has been anticipating debt before it happens (in planning/architecting), acknowledging it when it does and then taking prudent measures to minimize it and log it for future refactoring.

It’s also been funny seeing managers try to rebrand tech debt with other business speak like “tech health”. Debt shouldn’t be a bad thing but one that’s anticipated and respected.

[+] GeneralMayhem|6 years ago|reply
"Health" of a system isn't synonymous with debt. It's what happens when debt isn't respected and becomes bad. Debt is a tool; health is a state. If you carry six months of salary as credit card debt,a financial advisor isn't going to talk about how you're using debt as a tool, they're going to talk about your "financial health".
[+] ebiester|6 years ago|reply
Managers rebrand because their bosses and non-technical people start ignoring a term once it's used long enough, so we have to market it in another way.

There's also a distinction between "we made intentional decisions that we need to fix up now" and "We (or people before us) made unintentional decisions and it is in such a bad state that it endangers the health of the business."

[+] ThePadawan|6 years ago|reply
The term "debt" is really helpful. It also explains that while you can pay off debts by taking on other debts, there is a good reason why this is illegal (if done with money) in the real world.

Technical debt in your solution is fine. Running a Ponzi scheme in your solution isn't.

[+] Jestar342|6 years ago|reply
I've also come to realise that technical debt is everything. Anything that is resistant to change is technical debt, which includes the code you are writing "now", whether you anticipate its need to change in the future or not. There's the stuff that you know is painful or messy, but then there's the stuff that is suitable for today's needs but those needs may change in the future. Nothing is free from debt.
[+] tams|6 years ago|reply
Technical debt is a wonderful ally when building prototypes.

Imagine you are building a prototype and you are clever enough to only focus on validating an idea. Having this focus you ignore all best practices and maintainability issues you might tend to out of habit.

What you are likely to end up with is a functioning application that resists change beyond its original goals.

And this is good. It lets you declare technical bankruptcy early enough to avoid painful rewrites on a software that was not meant to exist for long anyways.

[+] spc476|6 years ago|reply
As long as both sides (developer, management) are aware of this (he says, as his "proof-of-concept" was installed into production directly by his manager with no warning).
[+] jakozaur|6 years ago|reply
Tech debt is inevitable and is good as long as you keep the level of debt reasonable, so maintanance cost doesn’t sunk too much of your dev time.

More good examples:

1. Technology progress: Long time ago JQuery was the best for UI library. Today you would use React or friends.

2. Company stage. As a early startup you usually prioritize for speed and validating hyphothesis. As you got a significant user base quality matters more.

3. Size of development team. With 5 or less developers having a monolite is good enough. With 50 developers you likely take more advantage of microservice architecture so each team can be decoupled and move faster.

[+] purple_ducks|6 years ago|reply
> With 50 developers you likely take more advantage of microservice architecture so each team can be decoupled and move faster.

Microservices aren't magic. There still has to be a contract between the services. Microservices are best when they are something which need to be scaled independently of the rest of the system.

[+] jackcosgrove|6 years ago|reply
> 3

I think it's almost part of most companies' lifecycles that they start with a monolith, and anticipate rebuilding it as microservices if they get traction.

[+] stevetodd|6 years ago|reply
I prefer the definition of technical debt being an uncovered call option: at some point the debt will come due and you don’t have anything in place to mitigate the issues it will cause and may not know when it will come due. This debt might look like a manual deploy process, a lack of automated testing, insufficient alerting, no rate limiting on public endpoints, etc. These are things that are fine and then when they’re not, they interrupt the schedule and require urgent and immediate attention.

Bad architecture can cause urgent issues and it can also impact new feature development. The debt is a barrier that makes new features either take much longer than they should or make them just infeasible from a business standpoint. This can exist in both technical architecture or information architecture (i.e. you chose the wrong model and now it’s too engrained across everything to change).

[+] munk-a|6 years ago|reply
Tech Debt is never good, but it can be acceptable in exchange for a greater gain.

So, just like this article's opening:

> Financial debt isn’t universally reviled in the same way. Your friend takes out a mortgage to buy a house and what do you say? Congratulations!

Except it is universally reviled, no one wants debt - they just accept debt in reasonable quantities as a way to efficiently get things done.

I think, in particular, the success of not-quite-agile-but-similar business practices has confirmed this, plan carefully and evaluate business needs, then get it done in a way that's fast and leaves you relatively free of debt - don't plan for everything. So don't get stuck in analysis paralysis trying to build the perfect things - and don't just code with no thought of the future... it's a difficult road to follow.

[+] nicoburns|6 years ago|reply
I'm not sure if I'd even consider these things tech debt. I tend to distinguish between:

1. Unfinished/unpolished implementations 2. Implementations that are architectural flawed, such that fixing them requires throwing away the existing code.

Issues in (2) are a lot more problematic than issues in (1).

[+] mbostleman|6 years ago|reply
Yea, I was going to say the same thing. Work undone as part of prioritization is not debt. Debt to me is causing more work than it would take otherwise so that less work can be done now (or because the developer / architect just doesn't know any better). Using a switch statement instead of a factory. Using role based auth instead of attribute based. Things like that.

The problem is, the debt metaphor is not being applied correctly. Not doing future work now does not take on responsibility for the future work - so it's not like taking a loan. Doing work in a way that commits the team to extra work that didn't exist before - that's like taking on a loan. For instance, I don't want to do this 80 points of work now, so I'm going to ship the same requirement by doing 40 points of work on a solution that will take another 60 points to get to the original 80 point solution which is the way we know we have to go long term. I just borrowed 20 points on behalf of the team.

[+] onemoresoop|6 years ago|reply
I'm not sure how this classifies as tech debt, but in my experience the worst type of tech debt is found in parts of a system that is relied upon and whose business logic has become unmaintainable, is too complex/poorly understood and is not possible to throw away as too many things depend on it. Also usually whose initial creator is long gone, the documentation is conflicting with the code implementation. This kind of debt causes eternal confusion and constantly lowers the productivity of the team. In cases like these, workarounds are created and more tech debt accrues on top of it that is even harder to reason about. Nobody sane can fix a mess like this and the only hope is death of the system or complete overhaul.
[+] dsjoerg|6 years ago|reply
Another kind of good tech debt I was hoping to see here is when something is built somewhat poorly/quickly because it will give you information that you need to have in order to know whether it's worth building it any better.
[+] notabee|6 years ago|reply
Prototypes are very valuable as long as you know to throw them away and build the real thing. The unfortunate aspect of many business environments is that low knowledge outsiders see something "working" and demand that it be thrown into production for [insert business speak reasons]. Tech debt isn't always a decision made by the subject matter experts.
[+] wool_gather|6 years ago|reply
I thought that was exactly the kind mentioned first:

> We realized we could build something cheap that we didn’t mind throwing away later—scaffolding—to unblock getting user feedback sooner.

If not, can you say more about the difference? I'm interested.

[+] nerpderp82|6 years ago|reply
If you aren't spending tech debt, you don't have a sound monetary policy. Zero tech debt is not a healthy goal.
[+] wellpast|6 years ago|reply
Unlike financial debt, which has a clear quantitative measure, Tech Debt is an incredibly fuzzy concept to the point of being useless for anything other than covering one's ass. We can do better as engineers than resting on such fuzzy thinking.
[+] momokoko|6 years ago|reply
I've always believed that "debt" is not a great term for this. Because it does not really apply to financial debt.

Financial debt is often repaid simply by bringing in more revenue because of the benefits gained from bringing on the initial debt. The only risk involved in the debt is that there will not be enough money to pay it back later. It is a very predictable and obvious issue.

Technical debt is closer to educated gambling. You are taking a risk that the shortcuts you are taking will not pop up and hurt your company before you fix them. You are essentially saying, "We know this is a problem, but we are estimating that it won't hurt us for the foreseeable future".

The better analogy to technical debt is investing. You are getting a certain percentage increase in productivity(a dividend or other capital gain) for the (hopefully low risk) gamble that your investment will not have a problem and wipe it out or simply cause significant losses.

Developers typically do not like this because when the inevitable few cases of these "investments" turn bad, they tend to be the ones that take on the majority of the blame.

[+] k__|6 years ago|reply
To me they even sound the same after you explained them.

Not being able to pay back money sounds like the same as not being able to fix bugs.

[+] jboy55|6 years ago|reply
Tech Debt, to me, grows with every line of code you write that didn't replace a line of code. Consider how many projects start with the idea, 'I'll rewrite this and get rid of the tech debt'.

First, If the original code isn't deleted, you've added to the tech debt. Now every new engineer will need to learn two chunks of code, and every existing engineer will need to support both.

Second, unless you've had a major epiphany between the old code and the new code, you're more likely to run into the trap of the first example. Major epiphany's could be, 'wow, we really didn't need all of that extra stuff', or, 'we've duplicated so much code, we should re-organize this all'.

I've been at plenty of companies where you have to understand that the Perl code is really old and these components use that, the Mason rewrite was 80% done and is over there, the Next Gen code was in Ruby these components do that. But now, we're embarking on a new Node/React project to replace all of it, finally going to get rid of the tech debt! (Said with no sarcasm)

[+] qes|6 years ago|reply
And I would say that's an example of when it can be good to purposefully take on technical debt.

In my experience there have been plenty of significant refactorings that simply would not have been done if we didn't allow ourselves to get into this state where there is simultaneously an old and a new way of doing something.

Especially if it's an area that sees frequent addition - it's not helpful to have people constantly adding code in the old way while off to the side people are trying to create a new way and completely finish switching everything over to it before integrating.

Managing risk and balancing other active tasks in the pipeline also push towards integrating smaller pieces over time while multiple ways of doing the same thing exist.

But you need to have organizational continuity, which can be rare when everyone is trying to hop jobs for better salary every couple of years.

[+] vinay_ys|6 years ago|reply
Nicely done. Good to see an engineering blog acknowledging tech debt openly.

I'm with you all the way until the last paragraph which short-changed the conversation a bit. Quoting a few lines to respond below:

>> Build things to be easy to throw away and replace; it’ll make your code more modular.

One valid strategy to manage debt is to declare bankruptcy or amnesty on debt – that is throw away the code (because this code is no longer needed). In domains where this possible, definitely use it. But in most real-world touching software domains this is not likely possible.

>> Good tech debt has clear, well-known limitations. Document these in code comments, READMEs, FAQs, and conversations with the people who’d care.

Doing tech debt accounting in a more formal manner is required to intentionally take debt. In the locking example, it is a design debt (not just a code debt). If the debt isn't document well and then it can become a ticking time bomb to be tripped by a clueless developer later.

[+] HelloNurse|6 years ago|reply
The defining characteristic of technical debt is attempting to avoid paying it, taking shortcuts that create "interest" in the form of increased effort and cost; it's always bad, but depending on timing and costs it can range from a "correct" minimum cost choice (e.g. badly tested truly throwaway code) to a stupid mistake to an existential threat.

Technical debt is a close relative of externalization (I save time, you waste time) and of mistakes that result from incompetence rather than from haste and shortsightedness.

All three sections of the article, instead, describe healthy planned evolution from a decent provisional product to a better one, with solutions that might be rough but not necessarily bad. In other words, a clickbait title for sound (but maybe unrealistically mature) examples of properly pragmatic project and product planning.

[+] cjfd|6 years ago|reply
@1: One could also have automated tests instead of scaffolding.

@2: Sure, but this it not really technical debt. It is the wise policy of not immediately writing code for the most general case. If one writes several instances of this it would lead to code duplication that would need to be refactored immediately.

@3: Careful with this one. The thing is that the thing you call an edge case might have the more proper name of 'bug'. If you deliberately leave in bugs at the time of writing the code it can lead to very difficult to estimate 'stabilization time' at the point where the code is supposed to go into production but is not quite reliable enough. It becomes somewhat difficult to explain if that 'stabilization time' turns out to be months or even as much as a year.

[+] ChrisCinelli|6 years ago|reply
> Good Tech Debt Is Intentional

When it comes to accelerate development in my opinion this is most (80/20) of it.

[+] jkoudys|6 years ago|reply
Love love love this article. I'm so tired of hearing lazy, unskilled, disorganized, mismanaged, etc. companies handwave the shit they write as simply being "tech debt". Sticking 50ms waits throughout your code, because you're not good enough to figure out how to actually trigger your action from an event or await something completing isn't "tech debt". Maybe hiring people not good enough to figure it out could be considered that from a higher level..

If we are going to allow the tech debt metaphor to continue, too much of the debt I see is the equivalent of a pay-day loan.