Technical debt isn’t an arbitrary thing; it’s a metaphor to try to explain doing maintenance to non technical folk.
The explanation goes like this:
If you have no debt, you can borrow money.
If you have a bit of debt, you can borrow money, but you have to make regular repayments of maintenance to service that debt.
...but when you have a lot of debt, lenders are cautious about lending you money. Will you pay it back? Why didn’t you pay back any of your other debts? Can you afford the repayments?
The metaphor isn’t perfect, but the point being made is that maintenance is the repayments on technical debt; if ignore it, at some point you are going to end up spending all your developer hours on maintenance, and your throughput for features will grind to a halt.
You “repay” the technical debt by fixing stuff that causes problems, not by randomly rewriting it because you can’t be bothered reading the existing source code.
It’s not maintenance.
That’s a different thing; that’s making changes.
Technical debt is more like a metric of how prone to failure /what feature velocity your software is / has.
Sure, the metaphor falls over; you can just keep slapping broken features and other crap on top of either other indefinitely if you dont care if your existing features work or not... but, that’s a different issue.
The article ignores that there are many ways to initially write code, and some are better than others. “Creating technical debt” is about not spending the time to find a good way to initially write the code, leaving it to a later point in time.
This should be a cautious decision (and I’ve seen it happen many times, at individual/team/even org level). This is “tech debt”, and it does exist.
> is about not spending the time to find a good way to initially write the code
in an ideal world, yeah, that would be the cause, but in reality there are multiple other constraints:
- time constraint: you can not spend as much time as you want to make the perfect code (and that is ok, you want to ship and add value to the business so you continue to get your salary - see the extreme case of Duke Nukem Forever)
- dependency constraint: sometimes other teams requirement will affect your code quality
- cost constraint: again, you don’t want to go bankrupt
- competency constraint: there are juniors and there are people who don’t have the knowledge to perfectly cover your requirements
Sure, and that's actually 100% normal in business -- it's called the "J-curve". Lots of businesses big and small start their lives, or even start some new project or expansion within an existing business, by borrowing money and then paying it back. The key thing is 1) borrowing money "strategically", and not just willy-nilly and 2) paying it back when you can afford to, rather than continually paying interest on it.
It totally makes sense to take on technical debt in order to get your MVP out the door quickly; or to be able to fix an urgent customer issue; or to be able to make a release. And in some cases, it may make sense to keep technical debt around indefinitely.
What doesn't make sense is to pile on technical debt for no real benefit, and then not take any time to pay it down. That's like always buying things on your credit card just because you can and only making the minimum payments.
> “Creating technical debt” is about not spending the time to find a good way to initially write the code, leaving it to a later point in time.
This is the wrong way to describe and explain technical debt. It fails primarily to sell the wrong idea that technical debt results from incompetence or failure to do a good job.
The truth if the matter is that in software development the absolute best solution to a problem is to provide an implementation that meets current requirements and is implemented as fast as possible. Overgeneralizing is bad code. Abstracting implementation details is a code smell. Proposing an implementation that considers additional design requirements and constraints is goldplating a solution, thus the sign of a mix of incompetence and wasted effort.
But then what if requirements change? Ah, the delta between the fast and good solution for the initial problem and adapting it with quick fixes to address the new requirements is precisely where technical debt pops up.
I think you’re being downvoted because your comment seems, especially in the first two paragraphs, to imply that:
- competent programmers who do a good job write the best code in the shortest amount of time
- more time always leads to worse code (unnecessary abstractions, handling non-existent requirements)
If you do know of programmers who are competent and generally do a good job, you can disprove this claim easily: Have them write a solution to some problem in a given amount of time, then have them spend more time on it.
Often you can see that they actually achieve a “cleaner” solution, including removing unnecessary abstractions.
I do agree with you though that some tech debt is caused by a lack of skills/experience. I guess we could further divide tech debt between intentional (the one I wrote about) and unintentional (the one you seemed to focus on in your comment).
> - competent programmers who do a good job write the best code in the shortest amount of
As much as it pains me, this is an undisputed fact. If you get two developers to implement the same ticket and both hit the definition of done, but while one takes an afternoon the other takes two weeks, who performed better? That's the reality of today's corporate software dev culture. The best code possible is that which meets the definition of done and is delivered in the shortest amount of time and preferably requiring the minimum amount of changes possible.
> - more time always leads to worse code (unnecessary abstractions, handling non-existent requirements)
No, that's not a function of time. The problem I stated is that in today's corporate software dev culture goldplating and over-engineering are code smells. Thus the pressure is placed on adding code that is less robust and adaptable to change. Consequently, technical debt piles up whenever new requirements emerge because the current culture puts a pressure on "good enough, delivered as fast as possible". Refactoring and redesigning and cleaning up are deemed unproductive tasks that deliver no improvements or new features, and whoever delivers more features is deemed a more productive developer. Hence, today's software development practices favours accretion which results in continuously growing technical debt.
Debt may not be an overwhelming problem in itself until misfortune creates issues which would not be overwhelming otherwise, usually in financial terms.
A small technical upset or failure can aggravate larger technical debt, not unlike a small financial upset or failure can amplify the threat of outstanding obligations.
Only the latter is usually visible and numerically measurable in any way.
In its ultimate form, technical debt is the liability that can cause bankruptcy of a technical company without ever showing up on the bottom line.
To put things on a level playing field and really bring it home, I agree with comparing your own solution after a rushed 2 days versus a full two-week completion of optimized code addressing the same issue.
>today's corporate software dev culture
Doesn't sound like it's good enough for NASA, but then again NASA doesn't have to worry about bankruptcy.
The shortest time possible to reach the moon was not accomplished using software taking the shortest amount of time to write & test to no end.
Google's tops in search now because of code they started on 20 years ago without ever giving up.
Not on what they accomplished last year, and especially not a 2-day sprint whether it has been deployed or not.
Technical debt does not have to reach its ultimate bankrupt conclusion for it to seriously dim your overall prospects.
all things being equal, shipping a feature with a 2 weeks delay is certainly bad, but often all things are not equal: the slower dev does something more and the does something less (I'm ignoring the case where the slower dev just slacks off).
The faster dev may have skipped important things, things that can potentially sink the company, like security aspects.
The faster dev may have been aware of that and still decided it was worth to complete the task even if the system as a whole wouldn't be ready to production. For example so that the feature can be seen in action in an environment where for example security is not perceived as to be a severe risk factor.
By doing so, the developer plays with the definition of done, in a way that can be either advantageous or disastrous, depending on what happens down the line. Will product management understand that the system indeed still needs more work before shipping it to a wide audience?
This is where the debt metaphor has its teeth. It allows to negotiate these tradeoffs, also across the engineering team boundaries.
You made a good argument that generalizing is an investment that often doesn't pay off.
That's a separate issue from technical debt because whether code is generalized is only one aspect of many that determines whether the code is a dream or a nightmare to work with.
For example, if all your variable and function names are vague and misleading because you didn't take the time to choose names that make any sense, that saved you time now and will cost you later, but it has nothing to do with generalization.
> That's a separate issue from technical debt because whether code is generalized is only one aspect of many that determines whether the code is a dream or a nightmare to work with.
That's one of the problems attributed to goldplating and over-engineering solutions. One man's future-proof implementation is another man's unnecessary indirection that's a problem to maintain. Thus we end up creating an environment where rigid implementations that are prone to colossal amounts of technical debt are preferred over adaptable solutions that minimize or eliminate technical debt.
> ...but when you have a lot of debt, lenders are cautious about lending you money
That doesn't strike me as the main point of the metaphor. The more debt you have, the more borrowing becomes expensive because you have to pay interest on the total amount; it grows exponentially (in the literal meaning) if you don't repay it. As many find out the hard way, it gets to a point where adding new features or fixing bugs takes insane amounts of time because you can't make changes without breaking everything.
This is what technical debt means to me. Decisions/shortcuts made early in the project that lead to lots of lost time when adding something new later (the lost time is the interest payment).
Good examples of this are premature microservices, no good separation between modules, bad database schema design, etc...
I would add that technical debt by itself is not a bad thing. Just as real life debt is not a bad thing. It fact, it can be essential if you want to do anything at all.
It is all about proper balance. Too little debt is inefficient, too much and you are getting overwhelmed.
The metaphor for piling crap on crap would be a Ponzi scheme. You pay your debts by making bigger debts. It works as long as you find suckers, it will break down eventually.
Technical debt by itself is a bad thing. It's just that, much as with actual debt, it's often worthwhile to accept a little bit of bad if you get a lot of good in exchange.
I was going to write that there's no reason you'd want financial debt qua financial debt, and that's sort of true but I realized that credit ratings complicate things. Sometimes an important part of what you're getting in exchange for the debt (and its repayment) is capacity for borrowing more when it first-order matters in the future, by way of establishing a reputation for being able to handle debt correctly.
Mapping this back to software seems interesting. I can't think of a parallel for credit reporting agencies, but it's certainly the case that a team might be more (or less) willing to take on technical debt to meet the schedule desired by a manager or a PM if there is a history of allowing them room to pay down the debt later (or failing to).
That certainly makes it more attractive to make deals that involve taking on debt. But note that it's a deduction, so the amount you pay to service the debt will always be more than the taxes you save. If you could simply not have the debt anymore, you would be better off. You still don't want the debt itself.
You may want to take on debt, but it's the money you get in the short term that you want (or the stuff you spend it on, really). The debt itself, you'd prefer not to have, if everything else could be equal.
He redefines what technical debt means and then claims the new definition doesn't happen, so what other people define as technical debt must not happen either.
This depends on the situtation. Especially when writing something that doesn't have a good existing pattern (think research code). After writing something it might become clear that a everything would be better off one way or another, and adding new research features might be impossible given the current structure. This is why most packages for doing machine learning research have just turned into autograd tools. Picking any fixed structure for research code is effectively blocking off some avenue of research.
I need some software to solve a business problem I have now, in 5 years time my problem might not even exist, there may be a better solution on the market, my company might not even exist - perhaps because my outgoings were all on expensive developers refactoring into the latest and greatest framework.
Software is not the end goal for most businesses, it's a tool to get
I’m not sure if this is #1 a geographical-cultural thing, #2 a generational thing or #3 a natural evolution thing but I’ve been in the game from mid 1999 onwards including the Dot-com bubble (yeah get off my lawn, ok boomer etc etc).
In all* the places I ‘ve done work for if there were any uncaught bugs during peer review we would be mortified (‘we’ being whichever person wrote the code) and endure a day or two of gentle ribbing from the rest of the team (sounds a bit like bullying but wasn’t).
If anything we (the team) shipped had bugs in it that were discovered during client demos or even worse at the end-user stage it would be like there had been a death in the family in the department for the rest of the day. And this was at all of the places I’ve chiseled code on to clay slates in cuneiform at (ok slight exaggeration there).
The point I am trying to (badly) make is - When did the attitude of ‘code fast, push it out the door and fix in v2’ become the norm?
I mean, sure in my days(), there was always a massive push by sales to get stuff out the door, screaming pre-sales consultants* etc but the Lead Dev would always be the one having sufficient authority (and guts?) to say - ‘it’s ready when it’s ready and not till then’. Diplomatically of course.
Perhaps the difference was in the way that project time-scales and milestones were set. We (the devs) would always double any time estimate and ‘fight hard’ to get bargained down to say 9 days down from the original ten (eg). Usually if it was ready by the 5 days it should have taken we would release it. All time budgets always included a full day allocated for the entire dev team to kick the tires on it prior to releasing anything.
Consultants and Sales guys loved us (dev team) – They get their ‘toy’ early so had a few days to play around with it before the customer got it (hint, makes for a much slicker sales pitch) but also because they knew we (any team I’ve worked with or lead) took any bugs very personally. If you have never been in sales, Trust me – the inner cringe a sales guy feels as the demo app crashes is a tough thing to recover from when pitching to clients.
Perhaps I just got lucky and never came across a company that moved fast and broke things. I’ve certainly politely never returned on day 2 at a few gigs based on team moral, coding style, basecode etc so who knows? Certainly I know that being able to mock competitors that released buggy code helped ease the pain of explaining why any stuff we did was 3x – 5x more expensive than the median of the ‘get three tenders’ quote price range.
Ranging from government departments, an international consultancy firm, an international high net worth private bank, small (10+ employees) companies, various start-ups (mostly B2B integrators, B2C arbitrage etc), NGOs etc.etc. After that moved to RTOS running on custom chip-sets and then some on to some other fun stuff. Mention not as a boast (hence the throw-away account) but to illustrate that the teams and companies I have worked for all had very, very different cultures and mindsets. If I had no experience of a start-up environment or experience of bureaucratic orgs etc then I really couldn’t / shouldn’t / wouldn’t comment.
* = when still junior enough for it to be an issue.
* (what are these days called Forward Deployed Software engineers I think)
One reason for such change I can think off is application of Goodharts law to estimates, when some managers base the programmers potential pay raise on how much faster than estimates the programmer was.
All the smart things I ever read about technical debt basically describe debt and add the word "technical" - because that's what it is. It's not maintenance, it's not refactoring, it's just pure and simple debt.
It's not debt because unlike (financial) debt, there is no way to not take it or to repay it.
Technical debt in software is unavoidable. Every variable and function and design decision can invariably be rebranded as technical debt soon enough.
The implication of "technical debt" is that there is a perfect state to reach with no debt, but there isn't. It also implies that debt could be avoided but it can't be, it's inherent to development.
I'm writing this as a middle-class person, who has to go into debt if she wants to buy a house. It's not a bad thing - I want the house now but saving up would take too long. It's just a choice.
"Repaying" technical debt is implementing the missing features, removing the temporary hacks etc.
Not taking it can be an option, if you have full funding upfront for example. It's just not always the best option. Just like borrowing money, getting into some technical debt can be worthwhile.
Branding every possible decision as "technical debt" is intellectually dishonest. Each decision you make has consequences. Technical debt is the result of choosing an expedient path over one that supports your wider goals. It's true that there's no perfect state, but only because this is engineering, where we don't care about "perfection", only tolerances.
But there's a difference between maintenance that fixes problems expediently and maintenance that fixes problems more permanently. The technical debt metaphor is an attempt to model the cons of doing the former too often (otherwise faster always seems better).
Maintentance is not technical debt, and confusing both terms is weird. While there are multiple definitions, I'd say maintentance is the work of making adjustments to keep the software in a working state, say supporting new operating systems, fixing bugs, updating to minor versions of libraries...
Technical debt, on the other hand, is quite literally a form of debt. You borrow time from the future by developing a solution that requires less work now, but that will need to be changed later on because it won't be enough future requisites, and that will create additional work (interest) in the long term while it's present. And every programmer does this, consciously or unconsciously. Say hard-coding some parameters in functions instead of creating user-defined options because it's easier, or writing a specific class instead of an abstract one that can be later extended, or writing a hack instead of refactoring so that new changes fit properly.
Of course, it's difficult to predict the future and some technical debt is never repaid. But it's important to make a distinction and to have in mind the technical debt in your project and when it's worth it to remove it.
A typical point where you find you have to repay your dept is precisely when you need to perform maintenance.
As a concrete example, a previous programmer at work when tasked with adding yet another file format to export would copy the entire code of one of the existing ones, make the necessary changes and ship it.
Instead he could have written it from scratch, recognized that "hey, I need to calculate the same sum as in this other exporter, let me refactor that into a separate function and call that" and so on. Then we wouldn't have two dozen pieces of code to check when doing maintenance.
Him not spending time to refactor created an additional burden that we now have to repay when performing maintenance, that's the technical dept.
I'd add that there is also a principle of not trying to predict the future and make design over-general and over-flexible 'just in case', but rather to have the simplest design that fits current requirements (though of course it should be clean and follow good practice).
The may lead to technical debt in the future but it may not. The idea being not to spend until you have to.
Now, a fudge now to save time should only be done under tight schedule with a plan to fix it later... In an ideal world. I think this is the case that really fits the analogy of borrowing/debt.
Overall, technical debt is unavoidable in a ever changing, unpredictable environment
I agree that further work is unavoidable in an ever changing environment. Software is built over time with the environment constantly shifting under it, it's a cycle to maintain and extend (constant changes if you will).
That's the crux of why technical debt doesn't exist, or is not a receivable term. If it were debt there should be a way not to take it, but there isn't. Every variable and function and design decision will invariably be rebranded as technical debt by the next developer soon enough. That's not reasonable.
One solution to not have debt may be to not write anything, that's not receivable because then there would no product and no company.
The other implication is that there may be a perfect state to reach with no debt, but that's not the case, realistically it's not possible to start from code that's perfect and bug free and supports every (future) feature in the universe.
> I agree that further work is unavoidable in an ever changing environment.
But can definitely be reduced. If my company plans on delivering version 1.0 with feature X, and version 2.0 with feature Y, I can either do an ad-hoc solution for X that will require less work but will need to be changed to allow Y in the future, or I can do some extra work now to reduce the total amount of work I need to do.
Those kind of decisions are made quite frequently while programming. Being conscious of them is important in order to understand better the project and the costs of maintenance and new features.
> Every variable and function and design decision will invariably be rebranded as technical debt by the next developer soon enough. That's not reasonable.
This is not a problem regarding the concept, it's a problem of the specific developer. Just because some people misuse a concept doesn't mean it's useless.
But you talk about technical debt as if it's necessarily bad. It's not bad. Some technical debt never needs to be repaid because the product is removed, or because that part is never touched and works well enough. Some technical debt generates so little extra work that it's worth it. But it's important to not scrub that concept, because, as I said, it's important to know the technical debt in a product so that we can better understand the costs of making changes and doing maintenance.
Indeed I've never seen debt in a sense that wasn't considered bad. That's what developers invoke to say everything done by their predecessor was shit. That's what developers claim to rewrite or to jump ship endlessly. All while management is clueless to it because it's all made up in the engineers mind.
How does one go about promoting (fixing) technical debt, or ask for time to fix technical debt, or plan for technical debt in a roadmap or an end of year performance review. That doesn't work.
I do think companies and developers should drop the idea of debt entirely. Consider a more positive model where software development is a cycle to maintain and to extend. Maybe maintenance can put more emphasis on keeping things working and in good conditions.
I've worked for many years on many things from startup to finance to aerospace (this may include planes you fly in and the bank you use). Software is eating the world a bit more every day. The only consistent experience is how companies and developers have zero vision and incentive to keep software running smoothly. The closest thing is a recurrent mention of debt, more often than not as an excuse to refactor or throw away anything.
> All while management is clueless to it because it's all made up in the engineers mind.
Saying that technical debt is made up is not a very productive approach. It's very real and programmers make the trade-off between less work now and less work in the future every day, sometimes consciously, sometimes unconsciously, sometimes because of lack of knowledge. It's useful to have a name for that and to understand it.
I've been programming for a few years already and being conscious of the concept of technical debt and when I incur on it when creating software helps me a lot by improving the decisions I make, by leaving better documentation on the technical debt used and possible fixes, and by being able to communicate better the risks and costs of certain approaches when deadlines are tight.
> How does one go about promoting (fixing) technical debt, or ask for time to fix technical debt, or plan for technical debt in a roadmap or an end of year performance review. That doesn't work.
I've done all these things.
- I've said multiple times to my superiors that implementing a certain feature in a short amount of time would add yet more debt to a certain part of a project, and that it might be better to invest more time to refactor code and add that feature with better code. That led to discussions on whether it was worth it or not depending on the possibilities of similar features being added. Sometimes we did the refactor, sometimes we did the hack. But we were conscious of the decision and the consequences.
- I've taken advantage of periods of low stress to ask for refactors instead of new features, arguing that those refactors would reduce future maintenance and development costs, or reduce bugs in certain fragile parts.
- When discussing roadmaps, I have explained that we can shorten certain stages with technical debt, at the cost of lengthening others to fix that debt, and we have discussed the trade-offs of both approaches.
In all of those situations, being conscious of technical debt allowed me to communicate better the costs associated with maintenance and development and enabled better discussions with management on what to do.
> Consider a more positive model where software development is a cycle to maintain and to extend. Maybe maintenance can put more emphasis on keeping things working and in good conditions.
I agree. That's why I think that the concept of technical debt, correctly used, leads to better understanding of the costs of maintenance and extension and enables better decisions.
> The only consistent experience is how companies and developers have zero vision and incentive to keep software running smoothly. The closest thing is a recurrent mention of debt, more often than not as an excuse to refactor or throw away anything.
But this is a management and culture problem, not a problem of concepts. I don't think that removing useful concepts from our toolbox fixes that. I think that the "not-invented-here" or "not-invented-by-me" syndromes predate the widespread understanding of technical debt.
Doing the best one can with the information available at that moment may not be 'technical debt'.
But I think the analogy fits well when people either cut corners in order to save time now, especially, or, still, when people design something less generic now because there is no need to over-engineer things 'in case'.
The latter, and deciding on it, is a form of risk management. The former really is "borrow now/pay later".
Claiming that technical debt does not exist fails to try to understand the analogy, imho.
> Technical debt, on the other hand, is quite literally a form of debt. You borrow time from the future by developing a solution that requires less work now, but that will need to be changed later on because it won't be enough future requisites, and that will create additional work (interest) in the long term while it's present
It is all just a words game. If there is an issue now, and I do a hotfix and hardcode some stuff that surely will need work later on to keep things working, then:
A) I created technical debt. I took the quick route now, but will have to spend more time in the future if I want to update this code.
B) I did maintenance. A thing was not working and I made it work. This is a simple, small bugfix, a.k.a. maintenance. In the future we might have to spend some time to keep things working in slightly different conditions. We also call that maintenance.
Both ways of phrasing it can help to communicate a message. But there is no use in getting hung up on what to call it. Your job as a developer is to be smart about it and choose the best possible trade-off. You need to do that in both versions equally.
Maintenance and technical debt are intertwined, but I don't think it's just a words game. Things without a name are less visible, and maintenance and technical debt are different.
There's maintenance unrelated to technical debt, and technical debt unrelated to maintenance. Taking into account the technical debt you have helps you when estimating the cost of maintenance and new features, or to understand the possible problems when new environments come into play.
IMHO, part of being smart about it is having the words to communicate the concepts clearly, it's not a moot discussion.
Technical debt it's literally dollars to donuts in time spent getting yourself out of the hole you are in right now.
Maintenance is the cost of keeping the lights on.
I run a two year old system which is now riddled with technical debt. How we got here has very little to do with the technology decision, stack, code, and a lot to do with management outsourcing and cost cutting.
Technical debt is never paid back through maintenance. It's just conceptionally wrong to tie them together. To phase better I can't maintenance my self out of this problem, but I can keep this system running forever with little to no cost.
Your B) example is not maintenance, it’s bug fixing. Maintenance is doing things like running backups, patching the OS, or updating code to work with a newer version of some library you’re using.
Technical debt is not just limited to programming, either.
Consider the cabling in a data center: It's very easy to create a new connection by dragging a cable down the aisle from one device to the other. But do this over and over and you'll be left with a tangled mess that's impossible to maintain, understand, and document.
Short quick fixes can be appropriate under the right circumstances, but it's important to go back and clean them up at the earliest opportunity.
Having done building work in various guises over the years, I've seen is interesting patterns analogous to software development.
Every builder comes in and says the previous guy didn't do it properly, used the wrong materials and overcharged.
Every software project I've been on, someone always comes in and says there's tons of technical debt, we're using the wrong language and the other developers are poor.
There's probably some truth in both statements but it takes experience and knowledge to take things with a pinch of salt.
I completely get where this person is coming from. The distinction between maintenance and producing new features on one side and paying down technical is so strange. It does not make very much sense to start working on a piece of the code that is hardly every touched and that has been working for years to improve it. What one should be doing instead is to make sure that the work one is doing for the purpose of maintenance and creating new features leaves the code base in a better state than it was found in. If one does that it becomes mostly unnecessary to talk about technical debt.
I do think technical debt does exist, though. Sometimes the changes that one wants to do are a bit too big to do right now and one should make a plan to get them done eventually and in small pieces wherever possible. This is called having architectural goals. One should try to be in this situation as little as possible, though.
>Software do not have a technical debt problem, software simply requires maintenance.
[...] There is no such thing as technical debt. There is work to do, that we can agree on, but it’s not debt payment.
The "technical debt" doesn't have a 100% universal agreement on what it means and apparently the author has only been exposed to one variation of it and that's what he's rebutting.
Another type of "debt" is caused by programmers writing bad/clunky code (often deliberately) to quickly solve a problem. The wikipedia article mostly talks about this definition of "debt"[1]. The programmers know the bad code will create "debt" that makes future maintenance harder. Example of this tension would be 2 different approaches to adding a feature or bug fix:
- (1) study the code base carefully -- maybe for hours or days -- and then write the properly architected code that makes future changes/extensions easy. E.g. expend the extra brainpower to see if the multiple edge cases or logic spread across files can be unified or simplified.
... or ...
- (2) quickly find the points in source code that seem "obvious" to modify just change it -- sometimes copy-pasting code and duplicating logic if if/then/else or switch/case statements in multiple files. Yes, now the immediate issue is "fixed" but you've simultaneously created "debt" down the road.
first sentence: Technical debt (also known as design debt[1] or code debt, but can be also related to other technical endeavors) is a concept in software development that reflects the implied cost of additional rework caused by choosing an easy (limited) solution now instead of using a better approach that would take longer.[2]
I've always understood tech debt to be the shortcuts we take along the way to ship fast(er). A global variable here, a hardcoded constant there, that copy-pasted-slightly-modified function instead of adding a parameter.
Fixing those things seems qualitatively different from the maintenance examples.
Where I work, "technical debt" is a term used by the project management folks whenever they are looking for justification for forcing developers to do something that everyone knows is a bad idea.
No worries, we'll come back to it when we have more time (we never have more time).
I agree with this. 'Technical debt' always exists in the heads of the engineers: the rest of the organisation can't see it. It's an invisible problem, imagined by the engineers, and not always in touch with the practical long-term reality.
Software is a tool. Tools that are used often require maintenance, which in itself is not a novel concept.
Whereas saying 'technical debt' is like delivering a dramatically vague House MD diagnosis.
If you must, call it what it is – Maintenance – and let's move on.
> Software is a tool. Tools that are used often require maintenance, which in itself is not a novel concept.
Yes software is a tool, but unlike physical tools, it does not wear out. Running the same code many times won't cause it to slowly degrade.
What does happen with software is that as you adapt it to particular roles, if you're not careful you can make it very hard to re-adapt to new roles. A "new role" doesn't necessarily mean user-facing features. It can be as simple as running on Ubuntu instead of running on RHEL. Or it can involve using a newer (and incompatible) version of some library. It might even be as daunting as porting the software to a new language (eg: from JavaScript to TypeScript).
The common analogy used to illustrate this is that of a car. If you have a typical passenger car and you need to compete in a demolition derby, you can modify that car to be a good demolition derby car. But if you're then asked to modify that car to win a track race, you'll have an easier time starting with a new unmodified passenger car than modifying your demolition derby car.
> Software is a tool. Tools that are used often require maintenance, which in itself is not a novel concept.
In that context, maintenance is fixing your screwdriver. Technical debt is having a fixed-head screwdriver instead of another with swappable heads: it might be ok for you if you mainly use that one, but if you're constantly needing to buy new screwdrivers and carry them, it's extra money and work.
And the same as technical debt, you might decide to buy first a fixed -head screwdriver because you're not certain about your future needs and it's just cheaper and easier, and then switch to a swappable head when you are certain you need it.
If it exists only in the heads of the engineers, then it's not technical debt.
Don't get me wrong, there are problems that mainly exist "in the heads of the engineers". Many of them want to refactor code or migrate to a newer framework or re-write for the latest buzzwords. But that's not technical debt.
The distinction between maintenance and Tech Debt comes down to whether the choice for an imperfect or expedient way of solving the problem was made consciously. You incur Tech Debt consciously to deliver value in the short term at the cost of not being able to sustainably deliver that value as parameters change.
Maintenance on the other hand is stewardship of a system as both the system(entropic decay) and it's environment constantly change.
I found Martin Fowler's "Technical Debt Quadrant" helpful on this - https://martinfowler.com/bliki/TechnicalDebtQuadrant.html . He argues you can have deliberate and inadvertent debt, both of which can be wise or unwise - the debt metaphor still works.
It's a terrible term because the technical people think they are talking to the business people in their terms, but when the business side of the operation hears the term technical debt, they ask some questions and what they hear is that they are issuing non-recourse debt.
What the technical side of the operation wants to say is "We're making short term decision that are going to hurt us in the long term", and what the business side of the operation hears is "This is free money".
Technical debt is not maintenance. Except for a very radical perspective: "So you used python2 in 2008 to start your project? Well you obviously knew the language would not last forever, you should have chosen ANSI C99 instead." This argument can be made for any dependency and it contains a grain of truth, but generally it is just too extreme.
I sympathize with the author's take that our industry is way too trigger-happy and willing to throw away perfectly working code, but none of this:
> Python 3 changed all the string handling, have to make adjustments. The build is too slow, should make it faster. Security vulnerability found in one library, time to update libraries.
is what (most?) people mean by technical debt.
If the build is just slow, but can be improved, that's not technical debt. Technical debt is when the build system is actually a bash script that was written ten years ago because none of the developers wanted to waste time on a proper makefile, and none of the interns knew make (or cmake, or ant, or whatever), and now just adding a new source file takes three hours.
(Edit: oh, or my favourite example, how could I forget this?
I've worked on a very large (MLoC) codebase that used autotools as a build system. Unfortunately, at some point, someone didn't want to learn m4 -- totally understandable -- and committed a manually-mangled configure script and a bunch of generated makefiles...
...and from that point, autotools were eschewed completely, and the configure script, and the generated makefiles, had mangled by hand each time something was required. Adding a new module to that program took 2-3 days of grepping and hunting through a bunch of files, just to make sure you didn't miss anything. That's more than enough time to learn the basics of how to use autotools. Multiply it by several hundred modules and weep.)
If a security vulnerability is found in a library, and that's solved with a library update, that's not technical debt. Technical debt is when message signing is done via MD5 hashes produced by a slightly buggy in-house implementation. You got to ship on time, but not by shipping a functional product -- that's a bug, and it still has to be fixed at some point.
If some stuff is single-threaded and slow, but easy enough to parallelize so that it's faster, that's not technical debt. Technical debt is when there's a single, global object containing absolutely all application state, and splitting an extra thread to do some computation in the background requires four weeks of extra manual testing just to be sure you didn't introduce another race condition.
(Edit: unfortunately, nope, none of the examples above were invented. I had the pleasure of working on a codebase where adding a new thread literally required written approval from the product manager because of the extra testing it required. Calling that "maintenance" is much like calling a bridge collapse a "vertical position readjustment" -- it gleefully ignores the fact that it was readjusted downwards, through gravity, possibly involving mass casualties, and had to be rebuilt).
Some maintenance work is legitimate and is just the result of normal software progress. But some maintenance work is entirely self-inflicted. Ignoring that is just an excuse for the kind of "first we do git push, then we think about it" crunch mentality that lots of companies encourage, for reasons that have absolutely nothing to do with either productivity or technical excellence.
Joking aside, they should have "maintained" they build system before it took 3 hours to do a build.
Things don't go from 1 minute to 3 hours overnight. The company and the developers had to work with it daily and didn't bother to take care of it at any point over 10 years. It's not a technical problem as much as it was an organizational and human problem. Inertia does add though so that's certainly difficult technically to replace now and getting more difficult by the day.
Yes, the author seems to ignore that there is a difference between good code and bad code. There is an enormous amount of subjectivity in that difference, but sometimes things _are just worse_. Maybe they're worse for the product (e.g. buggy code) or maybe they're worse for the developers (e.g. your tedious build system and global state). Either way, it takes time to do something about.
Yup. Technical debt is a liability you take on now in the hope that it enables later being better than if you hadn't taken out on. But it's still a liability that may impede forward progress.
Thank you - I think you're framing the discussion in a more nuanced way than the author, by talking about different kinds of issues or technical decision which lead to qualitatively and quantitatively different footprints of future maintenance effort.
We could go a bit further and start trying to categorise decisions by the different footprints of future maintenance effort that they cause. Roughly, we could differentiate between decisions that: (a) cause no additional effort in future, (b) cause a once-off impulse of additional maintenance effort in future, (c) cause an ongoing pattern of periodic maintenance effort in future, separately from any other change, (d) cause the effort required to make any other change in future to be amplified linearly, and (e) cause the effort required to make any other change in future to be amplified superlinearly. So now we've invented five somewhat arbitrary, non-exhaustive categories of maintenance effort to talk about [+].
Technical decisions that cause future maintenance effort of the form (a) are (relatively) fantastic, and (b) are often fine as well. (c) can become problematic depending on how often and how much effort regular maintenance has to be performed, but maybe can be dealt with by hiring an extra pair of hands to perform the activity. (d) and (e) will act as productivity dampeners and bog down any other development efforts, if they get out of control may completely prevent progress.
Technical debt issues of the form (e) might be: the system is built in a hand-rolled programming language that is not well defined, doesn't have specified behaviour or test cases, the developers are terrified of changing it but need to change it in order to make functional changes or fix defects in the system, but changing it tends to introduce more regressions that are discovered a few months later. Or maybe the code is arranged in an exponentially growing number of long-terms forks that need to exchange patches to support different special features sold to the latest key client, without good tooling support or test coverage. Or generally any system that's ended up in the state where the least-energy solution to fix any local problem makes the overall system globally worse, and for whatever reason no-one ever overcomes the energy barrier to do anything other than the locally easy globally corroding fix.
[+] We can define a lot more than five categories -- for simplicity let's assume we live in a world where it is not possible to make a technical decision that can ever _reduce_ the effort to make changes in future ("what if we migrated from using global variables, if expressions and goto to prefer using locals and structured programming constructs wherever practical") and act as a global productivity amplifier for all future development. Let's assume for simplicity that maintenance burdens are never probabilistic or conditional ("approach X will never require additional maintenance effort in future unless the client ambushes us with unlikely requirement R, which will invalidate the entire theoretical and practical foundations of X at the core of the product. it's a wager, most likely the debt won't ever be called in")
edit: i guess this could be characterised asymptotically with big-oh notation: "what's the asymptotic complexity of future maintenance effort introduced by this decision? as a function of t, time, or c, the cumulative number of changesets? O(0)? O(1)? O(t)? O(c)? O(c log c)?"
Although it is slightly wacky focusing on asymptotics of maintenance burden as the time or number of changeset goes to infinity, since in relatively finite timescales all the clients will defect or go bust, the product will fall out of use, the company will go bankrupt, the developers will quit or retire. hopefully someone remembers to ceremoniously burn all remaining copies of the source repository.
Technical debt is the additional maintenance for which you create a need when, presented with multiple options to implement a thing, you take the quickest and dirtiest one. It compounds the more you make decisions like that, just as compounding interest and capitalization create ever higher amounts of monetary debt.
Also, I found their self-referenced article on how Windows required more work than the pyramids, using man hours as a metric, simply absurd. This strikes me as a mythical man month type fallacy but can’t quite put my finger on it.
> To make another analogy, when you bring your car to the mechanic to replace breaks after 80 000 kilometers, he doesn't proceed to warn that the previous mechanics has left debt'ed break pads all over the place and to question the person's qualifications.
Good mechanic will refuse to touch your car. If breaks would fail
after her fix, she might be liable.
Same apply to coders. If your code is bad, it will be very difficult to find good developer to fix it.
It feels to me as though this argument misses the point.
-- Technical debt is time that you have borrowed from the future. --
That makes perfectly good sense if you need a simple, working system, quickly.
But if you plan on extending it, then the workarounds and shortcuts that you took will need to be replaced, slowing you down in future. Concrete classes where abstractions will be needed in future, hard-coded values, all that stuff. The debt will have to be repaid.
> This is meant literally. Reading existing code is difficult -more difficult than writing code- thus it is a genuine take from the developer that throwing everything away and starting over from scratch is “easier” than continuing on the existing. [Ergo this is flawed for non-small projects because the new development will never catch up with the existing during the tenure of the developer(s).]
I tend to work alone, so the poor schlub that needs to go in and maintain the code is usually Yours Truly.
That’s a big reason that I am pretty obsessed with heavily-documented code. I eat my own dog food.
It is quite painful to review undocumented codebases; even (especially) really well-written code. I write in Swift, and it’s entirely possible for an experienced Swift programmer to write highly efficient code that is almost completely inscrutable.
Not sure I’d necessarily call that “technical debt,” but it’s a real issue.
I’ve learned to use naive coding (in reality, highly efficient code often doesn’t buy squat), and a lot of “this is why I did this” commenting, along with long, descriptive entity names[0]; assuming that it’s my goal for my code to be maintained by others, and to not have them constantly pinging me with questions.
I will say that the (fairly ambitious) projects I’ve done that have grown into large community efforts, have resulted in very, very few questions about the code. It seems that understanding the code I write, isn’t an issue, for most people.
I write code for myself, and that seems to make others happy.
Pretty low-quality flaming post. Basically "battle of the word definitions".
Refactoring is observably the best way to get to know someone's code. Refactoring doesn't have to actually change behaviour; in fact, the word itself means "improving the code without changing its behaviour".
Not seeing any value in this post outside of "I disagree with some words, let's use others".
This is what I've caught myself doing; instead of reading and trying to understand the code, I scan it and start "fixing" it to make it my own.
Writing and changing code is a lot less boring than just reading it. Focusing on details (formatting, function naming, ordering, files) seems to be a lot more "gratifying" than trying to grasp the bigger picture.
I guess this is why I'll never become a senior developer / architect. Mind you, atm I don't have anyone else working on the same codebase either so it's all me.
What's a senior developer? I had zero problems grasping a lot of bigger pictures but since I didn't care much about political and office drama I never was dubbed "senior" while working in offices.
At one point I decided for myself that I am senior by the virtue of being able to quickly understand what's happening in a codebase and where the real value-add lies, plus the ability to integrate it into the bigger ecosystem of deployments that the company practices. And having a good general culture.
So don't you care about "senior". :)
As for the topic -- harmless refactoring is indeed the way to get to know code. This has been said by people like Martin Fowler and Kent Beck so it's not exactly baseless and isn't easily refutable by a flaming post like the OP.
Whilst I am sure Debt exists in classic software it is endemic in SaaS. If the truck is travelling at highway speed and you cannot afford to pull over; changing the break pads can indeed cost considerably more then building a new truck.
This is all just words and redefinitions, how you choose to slice and label things. Here's the useless reframing:
> Software do not have a technical debt problem, software simply requires maintenance.
Despite the words used, tech debt does exist. This article doesn't do much to make the task of maintaining software any better. It lumps it all together.
Tech debt, created accidentally or purposefully can be reduced with specific forms of maintenance which makes further changes easier and reducing future maintenance efforts.
The principle force of the analogy is the compound interest equivalent cost of delayed update. Jumping over versions can acquire more bugs than you can handle and the cost of remediation en masse under time duress is not the same amortised time as the per increment bugfixing across the versions
I have experienced this and I also work with people who deny it. It's complicated.
Technical debt != maintenance.. Maintenance is just maintenance. Technical debt is having to jump through arbitrary hoops to satisfy a code base or piece of technology instead of being able to directly address the problem at hand. With that said, I do agree that all code has some degree of technical debt but that can be limited by good design.
I think the point might be the term? I read somewhere a suggested alternative: product debt.
The technical side is the one that loaned the time in terms if shortcut. Paying it back has always a steeper cost.
To me (from an Ops perspective), technical debt is when people who have come before you implemented a setup that is broken at a deep level. A setup which can only be fixed by a complete re-implementation. No amount of maintenance will suffice.
I like the debt metaphor, especially because of 1) interest, which costs more the more debt there is and the longer you carry it, and 2) the fact that sometimes it's smarter to borrow than to be debt free.
If someone is knowingly building something that wont scale is that not an accumulation of future work? I think the key point about technical debt is that units of work are not all equal in long term outcomes.
Personally I (a developer) use it most as a metaphor for business people explaining why we need to schedule time in-sprint for work that isn't explicitly tied to a new feature.
We have debt, we need to service it. Repayments come in the form of developer time. If our debt is well-serviced it enables us to keep up a nice quick pace of feature development.
If a feature is politically important and our tech-debt is under control we can fast-track its implementation knowing that we'll have time to polish it up later and it isn't likely to break too much.
If our debt is too high we can't borrow anymore. Sorry, that super important feature that the board wants by end of sprint simply can't be done. We're at our credit limit and if we try and borrow more to get that done we'll go bankrupt (the product crashes and burns).
If the code was initially a net positive (i.e. provided a feature users wanted/needed, etc.), then in a way, yes. It might be bad in the abstract in the way patching something with bubblegum is, but if it works at the time--it works.
I think the only way to argue it was wholesale bad is that it immediately and objectively sank future progress (like a fatal wound of sorts). If it made things a hassle going forward, you'd have to argue that it was so much of a hassle that it actually receded things back into a negative. Most companies rarely experience the debt that stops forward progress cold or can prove they'd be making X times more progress if wasn't for such and such debt.
That said, if you could do things in such a way that yields the desired result and causes less (there is always some, and never little) maintenance, that's ideal. The thing is, everyone is trying to code from that place. The only ones who aren't are those who can't (they really are incompetent) or those who have given into utter malaise or malice.
Taking a useful analogy, or conventional piece of wisdom and contriving it into something meaningless is an excellent strategy for premium thoughtleader blog writing. Even the people who think your ideas are dumb will still click.
The explanation goes like this:
If you have no debt, you can borrow money.
If you have a bit of debt, you can borrow money, but you have to make regular repayments of maintenance to service that debt.
...but when you have a lot of debt, lenders are cautious about lending you money. Will you pay it back? Why didn’t you pay back any of your other debts? Can you afford the repayments?
The metaphor isn’t perfect, but the point being made is that maintenance is the repayments on technical debt; if ignore it, at some point you are going to end up spending all your developer hours on maintenance, and your throughput for features will grind to a halt.
You “repay” the technical debt by fixing stuff that causes problems, not by randomly rewriting it because you can’t be bothered reading the existing source code.
It’s not maintenance.
That’s a different thing; that’s making changes.
Technical debt is more like a metric of how prone to failure /what feature velocity your software is / has.
Sure, the metaphor falls over; you can just keep slapping broken features and other crap on top of either other indefinitely if you dont care if your existing features work or not... but, that’s a different issue.