Technical debt is a term frequently used in the software industry to signify the work that should be done to a project so that the project is situated in the best possible state.
Technical debt has been historically thought of as a problem that is created when software is rushed into development — where quick, inelegant solutions are chosen over more robust but time-consuming solutions. Like financial debt, technical debt accumulates interest and grows over time if not paid down.
Software developers frequently argue with management over the need to pay down technical debt, and it is often very difficult to justify the cost simply because the software as it is, currently works. This article will explain a little about eliminating and managing technical debt, assessing where it is worth the investment to pay off, and where it may not be when measuring technical debt.
While time pressure or a tight budget adds fragility to the software that is being developed, there are a host of other reasons that technical debt can be accrued. Here are a few technical debt examples.
Types of Technical Debt
1. Aging Code
Some technical debt materializes as newer development patterns and techniques are discovered over time.
No programmer likes the code that they have written after several years (many will joke that they are unhappy with the code the second after they have written it). Their skills are hopefully improving the longer they write code and new techniques for solving problems are learned over time.
While most algorithms in programming are unchanged since the 70s or 80s, newer solutions to problems are discovered all the time. Beware, however, as some programmers will obsess over rewriting code to be as clean as possible, this is generally not the best place to invest in technical debt.
Although frequently not considered strictly technical debt, if the codebase has good logging and error reporting bugs that were not discovered during testing can be uncovered by users doing things that were not originally expected.
Bugs may also occur due to race conditions that could only be replicated under the heavy load of real usage by large quantities of users.
These bugs and unexpected behaviors should be on the top of your technical debt list. If your codebase does not have good logging and error reporting, you should probably add logging and error reporting to the top of your list of technical debt.
If you don’t know why, or even if your app is crashing or misbehaving, it is impossible to fix. Logs and error reports mitigate this challenge.
3. Out of Date Libraries and Frameworks
In the past, libraries and languages changed at a relatively glacial pace. It did not require too much work to keep software projects up to date as the libraries would make significant changes only once every 4 or 5 years. For example, it was over 20 years between the release of Perl 5 (released in 1994) and Perl 6 (released in 2015).
Nowadays languages like Java, C# and NodeJs get a major version bump every year or two.
Mobile operating systems and frameworks get a yearly update from Apple and Google, and web development frameworks are even more frenetic in their pace of change.
Libraries also used to be relatively static, however, these days programmers are hesitant to use a library that has not been updated within the past year.
While the makers of these operating systems, frameworks, libraries and languages do try their best to make sure nothing breaks as they improve and update, they do not always succeed. Sometimes an update to any of these can cause things to go wrong. You should always test to make sure that things aren’t broken by updating to a new operating system or library.
If you ignore these updates for a long enough period of time you will get to a place where something will break and things will stop working.
At this point, the amount of effort and time required to get things working again can seem insurmountable, especially with the newly applied time pressure because your app or program has stopped working for customers.
4. Automated Tests
The final aspect of technical debt that is talked about by developers, but rarely as much by those who contract (i.e. pay for) the development of the software, is automated testing.
Adding automated tests is always a hard sell for those who don’t fully understand what it provides — proponents of testing say that you should spend at least as much time writing tests as you do writing code.
Tell that to someone who is paying the bill — “you want to double the cost of building this project to add something that adds no benefit to the user?”.
And unless you have someone overseeing production that truly understands the code, it cannot be verified if writing the tests was done correctly, or even at all. And yet testing greatly simplifies the ongoing maintenance of a project, errors can be detected and fixed in minutes.
Without automated tests it can take hours of tedious manual testing, at which point the tester has written up an error report that details how to duplicate the error, the developer has to fix it, and then the manual tests have to be run again to validate that error has been fixed.
The Costs of Incurring Technical Debt
Long ago a company I worked for developed some software for an industrial company that controlled an essential portion of what could be thought of as their assembly line. The program was written in an esoteric language that never had its runtime updated past Windows NT 3.5. After 20 odd years without any communication, they reached out asking for help.
For 15 years or so they had gotten away with buying old computers when the computer on the assembly line failed, then copying the old software over to the new computer. However, they were at a point where they could not find a computer that would run the software.
They wanted the company to recreate the software from scratch – FAST (their production line was literally shut down).
This illustrates an extreme example of what can happen when you don’t pay down technical debt.
Here are more costs you may incur if you don’t keep on top of technical debt:
1. Reduced Speed in Adding New Features
While situations like the above are rare, having technical debt can frequently mean that adding new features, fixing minor bugs, and improving the software becomes a much more time-consuming and expensive task.
You don’t want your project to get to the point where adding a new essential feature adds months of fixes before the work that you need actually can start.
If your project has been rushed and started with a hefty portion of technical debt, there will undoubtedly be bugs and erratic behavior that was not caught during the testing process.
While it is virtually impossible to create a bug-free program (Microsoft Windows 2000 famously shipped with 63,000 known defects) the more time pressure placed on a project, the more potential problems it will have.
There is a famous saying “fast, cheap or good, pick 2”, and a joke in software development saying that you can really only pick one, due to Brooks Law and the nature of the mythical man-month: “Adding manpower to a late software project makes it later“.
2. Bugs Plaguing Your Users
Technical debt is created the second your project was released because the increased use of the software by real users will lead to undiscovered bugs and erratic behavior.
This should be the first technical debt you pay down — building on top of buggy software will only create software that is even more buggy. Software that acts in ways that are not 100% predictable will cause additions to the software to become even more unpredictable.
3. Getting Hacked
Security is another rather new problem in the software industry (when I say new, I mean in the past 20 years).
Most operating systems were designed in the 70s (Unix), 80s (OSx, which in fact is also based on Unix but was initially for NeXT computers) and 90s (Windows NT) – the thought of a massively connected Internet with ubiquitous attacks from across the globe was the last thing on the OS architects’ minds.
Thus, security has been bolted onto these systems as an afterthought, and it shows.
Clever hackers are always discovering new ways to break into systems and take advantage of not only bugs but flawed architectures and design.
Therefore, keeping the custom code that you’ve developed secure (and potentially audited by security professionals), and also ensuring that you get the security updates for all the libraries your code uses, is an important part of software development maintenance.
4. Antiquated Look and Feel
Design and style standards change frequently, and this as true in programs and apps as it is in all fields of life. If you look at a mobile app from several years ago and compare it with today, you will see they look very different:
iOS 6 Settings Page
iOS 15 Settings Page
Android Gingerbread Settings
Android Pie Settings
While these differences are probably the least important reason to pay down technical debt, if your app doesn’t update visually after several years it looks dated and signals to your users that you are not investing in the application. If you aren’t willing to keep the look up-to-date, they might wonder what else you aren’t updating.
Managing Technical Debt
Obviously, resource allocation in paying down technical debt is a tricky subject.
It will always be in conflict with new development — where new development adds features for users and selling points for salespeople, paying down technical debt will generally not be noticed by the users.
Rarely will “paying down technical debt” be a bullet point on a slide touting a newer version of the app (how many people get excited reading “various bug fixes” on the release notes of an application).
So here are some guidelines:
1. Know What Problems You Have
Understand what technical debt you have, have a frank discussion with the developers. Get estimates about how long it is going to take to mitigate each problem, and get ranges.
The good thing about technical debt is you don’t have to pay it all down at once, and any work done on it is an investment in the codebase.
2. Be Judicious
Don’t try to fix everything at once, try to improve one area at a time.
In the waterfall development methodologies of the 90s and early 2000s, the belief was that about 60% of the cost of the software would be maintenance and 40% would be development.
Over the years the move to more iterative, agile methodologies has kind of made those statistics meaningless. A move to more frequent smaller fixes, rather than infrequent giant updates, means you should probably only target one area for improvement each fix.
3. Ignore Shiny Things
Do keep libraries up to date, and definitely consider switching out libraries that are depreciated or no longer maintained, but don’t just change to the newest shiny language or framework for the sake of being current.
Developers and CTO’s are frequently enamored with new libraries, frameworks, languages and patterns because they promise to solve problems in new and potentially exciting ways.
Frequently fixing things that aren’t broken by replacing older libraries (that are still supported), is a way to incur even more technical debt if those new libraries haven’t been battle-tested or optimized.
Does your company have an old or legacy software application that needs to be updated? Reach out to the team at Big Fish to learn about how our experienced team of software engineers can help.