There’s an old saying that goes something like this: “There is more than one way to crack an egg.” Software engineering is a perfect example of this concept, when engineering a software system there is almost always more than one way to accomplish any task. The challenge this versatility presents is finding the best way.
The problem
If you simply consider brute-force algorithms and their computational complexities (mostly intuitive, straightforward, easy, and obvious) in comparison to their more efficient and advanced counterparts (less intuitive and obvious) it should be easy to see the dilemma in building software. Oftentimes the most efficient way of building a software system isn’t the most intuitive or obvious choice.
Engineering teams are often required to deliver new products and features very quickly and so it is easy to choose a coding solution that is simple and quick. In many cases, though, there may be a more advanced and time-consuming option that would be better suited for the task, and save unnecessary maintenance costs down the road.
Short-sighted logic: Keep pushing and circle back later
“We just need to move fast at the moment” is a mantra reiterated amongst members of coding teams every day. But when building new software features, the moment for developing fast doesn’t ever seem to go away. Meaning that little inefficiencies accepted as temporary throughout the process become permanent because there is never time to go back and find a better solution. Oftentimes it seems the thinking will always default to:
We could be building some really cool new features right now.
Or:
We’ll come back to this later when we have time.
But through experience, many in the industry have learned that the concept of software entropy never allows this time to come. Software entropy increases due to the tendency of software to become difficult and expensive to maintain as it gets modified over time. As new features are developed, and consequently new code is added to the existing (and sometimes faulty) code, pending improvements can often be abandoned in favour of the perpetual motion. The early commitment to clean code at all times and adherence to standards and conventions are forgotten as the problem of inefficient software further compounds. There comes a point in every team when the truth can hit hard: It doesn’t get easier over time to maintain software. Maintenance tends to cost the same at best, or gets more difficult and costly, especially as the codebase expands.
By opting for the easier path and avoiding the better but more time-consuming route, many software development teams incur technical debt for their products. As with any debt, the technical debt must be paid in the future for the software to remain efficient, scalable and maintainable. But you may be asking yourself, what is technical debt, and how does it apply to software system design?
Technical debt 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 preserve the efficiency, maintainability, and sanity of the codebase.
One of the main concerns with deliberately accumulating technical debt is that though the process can seem slow and steady (and controlled) to begin, it can be very difficult to stop over time. Once bad code is implemented into the system, more bad code must be added to control the original problem’s negative effects and must again be continued as new features are built. This can be an ever-growing mess until it is so difficult to even read and understand the existing program that a total rewrite is essential. Little by little, technical debt combined with inevitable software entropy (which is increased by technical debt) can make the codebase completely irredeemable.
So far we have referenced technical debt that is incurred deliberately. Alternatively, it is possible to create technical debt without knowing it by using outdated technology. Because software standards continue to rise, outdated legacy software can become obsolete quickly and lead to attendant technical debt on dependencies.
Can technical debt be calculated?
Without a quantifiable variable for such an important concept, it is difficult for team members to make decisions about technical debt. By placing a number on the amount of debt for a given software, team members can make analytical comparisons, determine progress in eliminating debt, and plan for future steps in the coding process. In addition, by placing a distinct number on the technical debt of a system, non-technical team members can better understand the current quality of the software developed to run the business.
There are many metrics to consider when determining code quality. Some of these 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. Technical debt computation is important, but it can be confusing to know how much work is needed to eliminate with so many contributing factors. However, there is a simple solution. By expressing the problem correctly, an easy solution can be determined:
A simple ratio can show the technical debt as a relationship between the cost to fix the software system (Remediation Cost) and the cost of developing it (Development Cost). The term used to describe this equation is the Technical Debt Ratio (TDR):Technical Debt Ratio = (Remediation Cost / Development Cost) x 100%
Really simple, isn’t it?
As a general rule, teams prefer a Technical Debt Ratio (TDR) that is low and many favour a value less than or equal to 5%. A high TDR score means that the software is of very poor quality.
The TDR value is often equated to time, so the larger the TDR score, the greater amount of time (hours) necessary for a coding team to restore order to the codebase and achieve higher quality. Without question, a smaller TDR value is the goal when comparing remediation and development costs.
Code quality metrics can even incorporate remediation cost (RC), as a function in terms of time if the team feels it is relevant based on rules for resolving code issues. For example, if the team chooses to make RC (expressed in time) a function of cyclomatic complexity, or the time it takes to fix issues within a code function, it would then be easy to determine how long it would take to fix entire files. With that said, I’d like to note that we won’t be discussing the computation of cyclomatic complexities in this article, this is just an example of RC determination.
RC α Cyclomatic Complexity
RC = k(Cyclomatic Complexity)
k is a constant
Once k and Cyclomatic Complexity above are given, it would be simple to calculate RC.
When determining development cost (DC) you should consider the time it took to write a specific length of code. As an example, if a development file has 100 Lines of Code (LOC) and the average time it takes to write one line is 0.27 hours or 0.27 Cost Per Line (CPL), then it would be simple to determine the overall development cost (DC):0.27 hours/line x 100 lines = 27 hours
Therefore, it took 27 hours for the code in that file to be developed.
We have now discussed all the relevant variables of TDR, and here is a quick reminder of the associated abbreviations for each: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%
Where:DC = CPL x LOC
Let’s use the equation to calculate total TDR 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/line
Recall that DC = CPL x LOC
=> DC = 0.27 hours/line x 26,398 lines
DC = 7,125.83 hours
Therefore:
TDR = ( RC / DC ) x 100%
TDR = ( 365.8 hours / 7,125.83 hours ) x 100%
TDR = 5.1%
The technical debt ratio for this example codebase is 5.1%.
Conclusion
To recap, hopefully, you now have a better idea of what technical debt is, and how it can relate to your software design projects. Technical debt is the cost to the product of engineers who take shortcuts or choose the fastest method of coding in place of a more advanced solution which is also better for the final product. The overall entropy of the software increases over time as a result of technical debt. Because Technical Debt Ratio (TDR) is a ratio of the cost it takes to fix the system to the cost it took to build the software, it is relatively easy to quantify.
I hope you enjoyed reading and seeing areas you could check and investigate to determine just how much you owe in your software projects.