There’s this saying that there’s more than one way to crack an egg. This concept applies to software engineering too. In engineering a software system, there’ll be more than one way to do it. This poses a challenge: finding the best way.
It would seem that the best and most efficient ways of building software systems aren’t usually the most obvious and intuitive. A look at the computational complexities of brute-force algorithms [mostly intuitive, straightforward, obvious, and easy] vs their advanced but efficient counterparts [not so obvious and intuitive] should show this.
It’s easier and feels quite natural to reason about very simple solutions than advanced ones. Add to this the need to deliver features fast, and an engineering team may find itself choosing a quick and easy way over a time-taking, advanced, but better approach that would save everyone unnecessary maintenance costs in the future.
Team Decides: Let’s Move On Guys, We’ll Revisit This Later
To decide to go the inefficient, fast and easy way, a team may reason that at some future point, work will be done to cater for this “little” inefficiency: “We just need to move fast at the moment” becomes the mantra on everyone’s lips. It’s soon discovered that the moment where everyone must move fast never goes away and that the only way to move at all times, is fast. Time to fix accumulated software issues will always appear to encroach on the time to build new features. The thinking will always be:
We could be building some really cool new features right now.
Another popular one is:
We’ll come back to this later when we have the time.
But as experience has taught some of us, the concept of Software Entropy will ensure this time never comes.
Software Entropy increases due to the tendency of software to become difficult and expensive to maintain as it gets modified over time.
As pressure mounts to release new features, the team further compounds the problem by branching new code off poorly written code. Slowly but surely, the team gradually abandons earlier commitments to write clean code at all times, to stick to standards and convention no matter what, and eventually, no one talks about the pending improvements anymore. The truth below hits the team hard as they keep working:
It doesn’t get easier over time to maintain software. Maintenance tends to cost the same at best, or gets more difficult and costlier, especially as the codebase expands.
By choosing the easy way over a better and advanced, but time-consuming way, the team has only incurred a debt, software-wise. This is the Technical Debt they must pay sometime in the future to get their software efficient, scalable, and maintainable again.
Technical Debt [deliberate] is the cost incurred when poor design and/or implementation decisions are taken for the sake of moving fast in the short-term instead of a better approach that would take longer but preserves the efficiency, maintainability, and sanity of the codebase.
The problem here though is that having begun the slow but steady accumulation of interests on technical debt, it gets really hard to stop, as you must write more bad code to contain the negative effects of an existing bad code while building new features, then some more bad code to maintain the ever-growing mess, until the team begins crying out for a total replacement of the current system as it’s become a pain to even read any part of the code and make any sense out of it. Bit by bit, accumulated technical debt and the reality of software entropy [which technical debt increases] render the codebase irredeemable.
The example used above applies to technical debt incurred deliberately. It’s possible to incur technical debt inadvertently. One very easy way an engineering team can suddenly find itself dealing with a technical debt problem is if their technology gets outdated. With software standards rising ever higher comes obsoleteness of legacy software and attendant technical debt on dependencies.
Numbers Please — How Do We Measure Technical Debt?
It doesn’t help decision-making if we can’t place a number on such an important concept. Once we can quantify it, we can make analytical comparisons, we can know if we’re making progress, we can plan with data. Non-technical team members can get an understanding of the quality of the software upon which the business runs.
But so many variables are involved in determining code quality, of which technical debt computation is an important factor. Some of these variables include complexities [cyclomatic and cognitive], lines of code, arity, maintainability index, Halstead Complexity Measures, depth of inheritance, afferent and efferent couplings, nesting depth, time to write n lines, etc. With so many metrics to consider, it appears difficult to know how much work we need to do to pay off technical debt.
It turns out, however, that there’s a simple solution to this problem — it lies in how we choose to express the problem:
Express technical debt computation as a ratio. A ratio of the cost to fix a software system [Remediation Cost] to the cost of developing it [Development Cost]. This ratio is called the Technical Debt Ratio [TDR]:
Technical Debt Ratio = (Remediation Cost / Development Cost) x 100%
Really simple isn’t it! Technical Debt Ratio [TDR] is simply the ratio of remediation cost to development cost.
Generally, no one wants a high Technical Debt Ratio [TDR], some teams favour values less than or equal to 5%. High TDR scores reflect software that’s in a really poor state of quality.
If both remediation and development costs are represented in time [hours], for example, TDR scores simply indicate how long it would take an engineering team to restore sanity to their codebase and achieve quality. So smaller TDR values are to be preferred at all times.
Prefer smaller TDR values at all times
Remediation Cost [RC] can be made a function of any code quality metric a team feels is relevant based on rules for resolving code issues within the team. RC can also be expressed in terms of time.
For example, if the team chooses to make RC [expressed in time] a function of cyclomatic complexity, in other words, the time it takes to fix issues within a code function — RC, is directly proportional to the cyclomatic complexity of that code function, then it becomes easy to determine how long it would take to fix entire files once we compute their cyclomatic complexities. This is just an example RC determination, we won’t be discussing the computation of cyclomatic complexity here.
Basically, for this line of reasoning:
RC α Cyclomatic Complexity
RC = k(Cyclomatic Complexity)
k is a constant
Cyclomatic Complexity above are given, RC can be easily computed.
Development cost [DC] can be imagined as the cost of writing some lines of code [the time it took to write that much line]. For example, if a file has 100 Lines Of Code [LOC], and it takes an average of 0.27 hours to write one line, that is, Cost Per Line — CPL, is 0.27 hours, the development cost [DC] would be:
0.27 hours/line x 100 lines = 27 hours.
This means it took 27 hours to develop the code in that file.
Now that we have all the relevant variables for TDR covered with their associated abbreviations, let’s remind ourselves of the abbreviations again:
Development Cost: DC [hours]
Lines Of Code: LOC [lines]
Cost Per Line: CPL [hours]
Remediation Cost: RC [hours]
Technical Debt Ratio: TDR
So, referring back to the definition of the TDR, we can write:
TDR = ( RC / DC ) x 100%
DC = CPL x LOC
Let’s treat an example codebase where the remediation time is 365.8 hours; lines of code is 26,398 lines; cost per line is 0.27 hours/line.
RC = 365.8 hours
LOC = 26,398 lines
CPL = 0.27 hours/lineRecall that DC = CPL x LOC
=> DC = 0.27 hours/line x 26,398 lines
DC = 7,125.83 hoursTherefore:
TDR = ( RC / DC ) x 100%
TDR = ( 365.8 hours / 7,125.83 hours ) x 100%
TDR = 5.1%
Technical Debt Ratio for this example codebase is 5.1%
For a quick recap, technical debt is the cost incurred when engineers try to cut corners by choosing a fast and easy way over an advanced and efficient but time-consuming way when building software. Technical debt accumulates interests over time and increases software entropy. To effectively measure technical debt, we need to express it as a ratio of the cost it takes to fix the software system to the cost it took to build the system. This quantity is called the Technical Debt Ratio [TDR].
I hope you enjoyed reading. Please let me know what you think, especially if I missed anything that could help further clarify this really important software metric.