Technical Debt: Recognising the rate of interest
So, I’ve had this idea to break up technical debt into two high-level categories — high interest and low interest. Somebody may have already expanded the concept to this level of detail, but I came up with this on my own, thus it doesn’t count as plagiarism :-D
A quick recap of technical debt
Technical debt was a term coined by technical people as a metaphor for helping to explain the accumulation of complexity in software systems that causes a slowdown in ongoing development — a time penalty on adding new features. Technical and non-technical people alike are familiar with financial debt and its impacts, and recycling the word ‘debt’ in a technical context appears to make for a semantically viable metaphor for all to comprehend.
On the whole, the metaphor works, although there are some obvious exceptions where debt in a financial sense does differ from its technical counterpart. Sam Newman recently — and succinctly — summed up one such important difference on Twitter:
In my experience, engineering teams often fail to discover what they could know about their business domain. Astute teams use Domain-Driven Design (DDD) to help discover, understand and decompose domain complexity, but DDD can’t help with predicting the future, only with understanding what’s known in the present. That’s why DDD practitioners will insist on the importance of constantly revisiting your modeling canvas when context changes.
DDD can’t help with predicting the future, only with understanding what’s known in the present
One of the inherent challenges in building software is that we don’t know what we don’t know about the domain we’re working in. As much as engineers love to believe in infinitely extendable designs, the reality is that you’re deluding yourself when believing that your designs/models can effortlessly accommodate future changes in business context and product direction. The fictitious universe where significant new features can be indefinitely accommodated at will, without any refactoring of existing code, is a universe reserved for those who like to talk a big game, but who’ve never actually built a real-world system. The delusion leads us blindly towards creating ugly, generic abstractions that manifest themselves in code that appears foreign to the problem its trying to solve.
When we don’t know what we don’t know, a design, model and/or code that we create in the present moment may not constitute technical debt. How many times have you only recognised that something represents technical debt in hindsight? This is one of the reasons why we shouldn’t necessarily beat ourselves up too hard when we look back and acknowledge the existence of technical debt in something we built in the past. It could well be that we did everything we could to discover the complexity that existed at that present point in time, and, ultimately, we made a good decision with the information we had available to us. In such cases, we didn’t consciously choose to create the debt, it just happened that future changes in context means that only now can we can consciously observe it as debt.
We must acknowledge that one of the inherent challenges in building software is that we don’t know what we don’t know about the domain we’re working in.
We must appreciate, though, that we do create a lot of conscious technical debt when we build systems. And that’s perfectly okay — it’s all part of the trade offs we make every day between evolving a product quickly in the short-term vs looking after long-term sustainability. Nobody (well, not me) is saying that all technical debt is bad, far from it. Just like in the financial world, there are many cases where taking on debt is a strategic win, where the advantages of accumulating the debt in the short-term outweigh the long-term disadvantages.
We must make sure that we do our due diligence and carry out an impact analysis when we consciously decide to accumulate debt — there’s an obvious danger that we hide behind the inescapable unconscious debt, blaming all debt on “things we didn’t know at the time”.
Introducing the ‘rate of interest’
The issue as I see it is that I’ve not come across a succinct way to categorise the technical debt we consciously create, such that we can distinguish between debt that is ok to accumulate versus debt that is far more damaging to accumulate. Without this formalised approach to categorisation, it makes it harder to be strict on ourselves when making our conscious trade offs. Ideally we need a method of categorisation that also works for non-technical folk, thus preserving the value of the overall debt metaphor.
So, what I propose is that engineers assess technical debt based on its rate of interest — low interest vs high interest. Let’s walk through what this means, how the concept of interest in finance can be applied in a technical context.
Interest is essentially what you pay a lender as commission for having advanced you a loan. The higher the interest rate, the more you pay. It’s pretty simple. Sometimes, and for certain financial products, the interest burden becomes so great that the borrower is capable only of meeting the interest payments, failing to make a dent in paying back the loan amount itself. The is where I’d like to draw the useful analogy for technical debt.
the interest burden becomes so great that the borrower is capable only of meeting the interest payments
Let’s consider that a piece of arbitrary technical debt adds an overhead of 10% on all new feature development. If we pay back half of the debt, that brings down the overhead to 5%, and we keep paying it back until we remove the overhead altogether. Now let’s consider that the difficulty in paying back the debt is measured in terms of its interest rate — i.e. the harder it will be to refactor our system to remove the debt, the higher the interest rate assigned to it.
The fine art of technical solvency
If we find ourselves burdened by an excess of high interest technical debt, we’ll end up like the financial borrower trapped in a debt spiral, only able to afford the interest payments, never able to pay back the underlying loan. In such cases, the rate of accumulation of high interest debt can grow exponentially — high interest debt has a tendency to drag you down a path towards even more high interest debt. And the interest rates on existing debt somehow keep on rising.
the rate of accumulation of high interest debt can grow exponentially
At some point you reach the point where the line between solvency and bankruptcy becomes almost imperceptible. There are a whole number of potential indicators of when you’re crossing the line into technical bankruptcy — here are just some examples:
- Your teammate who’s on call out-of-hours appears weary eyed at your desk in the morning. Every morning.
- “Simple” things that were meant to take days, somehow take weeks or months (or years!)
- Your teammates never seem to make it to stand up on time. And if they do make it at all, they’ll probably be sitting down.
- Your P3 bug backlog is so full, and growing daily, that nobody even cares about it any more.
- Nobody ever bothers to challenge decisions they don’t agree with
- When a test suite is failing, everyone just blames it on Jenkins instability.
- Somebody turned off the static code analyser to get it to shut the hell up. The rules were too strict, right?
- All anyone seems to do is moan. All bloody day.
- Your company network appears to be issuing a denial of service attack on Facebook
If, however, we do our best to remain solvent, to limit ourselves to low interest technical debt, then we give ourselves a better chance of being able to afford to pay back the underlying loan, and ultimately relieve ourselves of the ongoing development overhead.
Of course, we must accept that we’ll routinely end up with both low interest and high interest technical debt via the unconscious pathway discussed earlier in this post. But, surely, the fact that we know we’ll end up having to address this unconscious debt is every reason to avoid the conscious accumulation of the damaging, high interest variety?
If we do our best to limit ourselves to low interest technical debt, then we give ourselves a better chance of being able to afford to pay back the underlying loan
Higher or lower?
One of my favourite insights from Martin Fowler is his view that software architecture constitutes those decisions that are both important and hard to change. This was taken from a keynote at OSCON in 2015. If we assume this to be true (and it is because Martin Fowler said it), then it follows that we can reasonably define high interest debt as relating to the architectural aspects of software. For me, this is an extra incentive to take Eric Evans very seriously when he advises that we break up our domain into distinct Bounded Contexts.
we can reasonably define high interest debt as relating to the architectural aspects of software
In my experience, so much high interest (architectural) debt is rooted in failing to acknowledge the natural boundaries that exist within a complex domain. With this in mind, I will always make a point of explicitly modeling these boundaries — at least as far as they’ve been discovered in the present moment — from an early stage in any software product/project.
But, what about startups?
I’m not for one moment suggesting that a startup loses focus by carving their fledgling software into a whole bunch of fully distributed bounded contexts, manifested as autonomous microservices. The operational overhead will most likely turn into a very painful — and potentially fatal — distraction. Trying to scale a business too early is a bad idea. However, once you’ve arrived at something resembling product-market fit, I don’t believe it’s wise to be thoroughly complacent with architecture/design.
One of the leading writers on software architecture, Simon Brown, recently shared a very useful overview of the microservices landscape:
For me, it’s wise for a startup who’ve identified their early stage product-market fit to aim for something that looks like Simon’s modular monolith. This means you’re establishing — and maturing — clear domain boundaries within a monolithic deployable (to keep debt interest low), without taking on the operational complexity of a full-blown distributed microservices deployment. Admittedly, before you’ve established some form of product-market fit, it doesn’t really matter what the hell you do as long as it helps you discover as quickly as possible the product you should be building!
before you’ve established some form of product-market fit, it doesn’t really matter what the hell you do
Ultimately, a microservices architecture will succeed as a side effect of having identified the right domain boundaries. Conversely, it will likely fail if the boundaries are wrong — the dreaded distributed big ball of mud! Thus, we should recognise that the real value lies in establishing the domain boundaries early within a modular monolith, and delay the operational optimisations until later. We can start splitting up the modular monolith at that point in the future when we begin to ramp up autonomous teams and want to leverage the opportunities of scale. If we’ve managed to keep our technical debt interest low (avoiding the monolithic big ball of mud), then the job of carving up will be many orders of magnitude easier at that point.
a microservices architecture will succeed as a side effect of having identified the right domain boundaries
Don’t forget that it’s not always our intention to eradicate all conscious technical debt — it can be a competitive advantage to take it on — but there appears to me plenty of sense in aiming to keep the rates of interest low. Sometimes, it may be the case that adding just a couple of extra days development time up front is enough to significantly bring down the rate of interest in the long-term, albeit not avoid debt altogether.
I’m hoping to find some benefits in experimenting with this way of classifying technical debt, and I hope to share my findings in the future!
In the meantime, borrow sensibly, people.