An Introduction to Software Quality (Part 2): What Is Technical Debt?
Technical debt, a term coined by Ward Cunningham in the 1990s, refers to the trade-off between short-term gains and long-term consequences in the development process. In this article, we will explore what technical debt is, why it happens, why you should care about it, and discover some new tools to help you discover the technical debt in your Rails applications .
What is technical debt?
Technical debt refers to the compromise made by developers when choosing a quick and easy solution over a more time-consuming but sustainable alternative. Just as financial debt accumulates interest over time, technical debt accrues potential issues if not repaid. Technical debt can happen in many places during the software development life cycle, including during the design phase, at the code level, in test coverage, or in documentation.
Technical debt can have different meanings depending on who you ask. Stop Saying “Technical Debt” is a good overview of all the possible meanings when someone mentions “technical debt”.
Just as the definition of technical debt changes depending on who you ask, the same goes for how to categorize technical debt.
Martin Fowler, a well-known software developer and author, has categorized technical debt into four main types, depending on the motives of the team accruing the debt. These four main types together are called the Technical Debt Quadrant .
In 2023, several Google researchers published a paper based on five years of research at Google documenting the tech debt across different teams at the company. The researchers surveyed several subject matter experts to determine the main components of technical debt. They were able to narrow down 10 categories of technical debt. You can read more about these categories, and how Google used this research to manage their technical debt, in Defining, Measuring, and Managing Technical Debt at Google
It is important to recognize that technical debt is an inevitable part of the software development life cycle. Tight deadlines, resource constraints, and evolving project requirements often force developers to make trade-offs.
The Difference Between Tech Debt and “Bad Code”
It is also important to recognize that technical debt is not “bad code”, a term technical debt often is confused with. Technical debt refers to the consequences of prioritizing speedy delivery over perfect code. It’s often deliberate, with the understanding that the code will need to be improved later. Technical debt can be managed and strategically used to achieve short-term goals while planning for future refactoring.
Bad code, on the other hand, typically results from poor programming practices such as lack of understanding, inadequate design, or insufficient testing. It might not be intentional and can lead to increased maintenance costs, bugs, and difficulties in extending or scaling the application. Here are some examples of what might often be considered technical debt but are actually just bad coding practices:
-
Spaghetti Code: Unstructured code that is hard to read and maintain, usually lacking in modular design.
-
Magic Numbers: Hard-coded values with no explanation or constant definition, which can be confusing and error-prone.
-
Copy-Paste Programming: Repeated blocks of similar code that could be replaced with functions or loops, leading to bloat and maintenance headaches.
-
Lack of Documentation: Missing explanations for why code is written a certain way, which can make it difficult for others to understand or modify the code.
-
Ignoring Errors: Failing to handle possible error conditions properly, potentially leading to crashes or incorrect behavior under certain conditions.
In practice, the line between technical debt and bad code can blur, as poor coding practices can lead to technical debt if not addressed. However, recognizing the difference is crucial for effective code management and maintenance strategies.
Why should you care about technical debt?
Caring about technical debt is crucial for several reasons:
-
Cost: Technical debt accumulates when shortcuts or suboptimal solutions are implemented to meet deadlines quickly. Over time, the cost of maintaining and fixing issues related to these shortcuts can escalate, making it more expensive in the long run.
-
Productivity: As technical debt increases, developers spend more time dealing with issues, bug fixes, and workarounds. This can hinder productivity and slow down the development process, making it harder to deliver new features or improvements.
-
Quality: Technical debt often leads to code that is less maintainable, readable, and scalable. This can result in lower overall software quality, making it prone to errors, difficult to enhance, and challenging for new developers to understand.
-
Innovation: Accumulated technical debt can stifle innovation as resources are tied up in resolving existing issues rather than exploring new ideas or implementing improvements. This can hinder a company’s ability to stay competitive in the rapidly evolving tech landscape.
-
Customer Satisfaction: Poorly maintained software with a high level of technical debt can lead to a subpar user experience. This can result in customer dissatisfaction, reduced user engagement, and even loss of customers to competitors with more stable and feature-rich solutions.
-
Long-Term Viability: Neglecting technical debt can jeopardize the long-term viability of a software project or product. As the debt accrues, it becomes increasingly difficult to maintain and enhance the system, potentially leading to a point where a complete rewrite is necessary.
Measuring Technical Debt
Now that we know about what technical debt is and its’ causes, let’s talk about how we can measure it. Measuring technical debt is inherently a complex task - to be able to measure it, it first has to be defined. We usually recommend teams to have a conversation about what they perceive as technical debt. Once they arrive at a baseline definition of technical debt that makes sense to them, they can identify metrics and start tracking them.
Here are some of the tools that we have found useful at FastRuby.io to measure technical debt:
- Libyear or libyear-bundler : This tool revolves around the concept of libyears . A libyear measures “dependency drift”, or the measurement between your dependencies’ installed versions and the latest stable versions, in years.
Libyear-bundler
compares the outdatedness of dependencies using time as a metric.
Here’s what a sample libyear-bundler
report looks like. It shows a list of the current versions and the most recent versions of a gem.
% libyear-bundler Gemfile
addressable 2.8.5 2023-08-03 2.8.6 2023-12-09 0.4
bootsnap 1.16.0 2023-01-25 1.18.3 2024-01-31 1.0
capybara 3.36.0 2021-10-25 3.40.0 2024-01-27 2.3
childprocess 4.1.0 2021-06-09 5.0.0 2024-01-07 2.6
concurrent-ruby 1.2.2 2023-02-24 1.2.3 2024-01-16 0.9
date 3.3.3 2022-12-19 3.3.4 2023-11-07 0.9
ffi 1.15.5 2022-01-10 1.16.3 2023-10-04 1.7
globalid 1.1.0 2023-01-25 1.2.1 2023-09-05 0.6
i18n 1.14.1 2023-06-04 1.14.4 2024-03-06 0.8
jbuilder 2.11.5 2021-12-21 2.12.0 2024-04-29 2.4
listen 3.8.0 2023-01-09 3.9.0 2024-02-24 1.1
loofah 2.21.3 2023-05-15 2.22.0 2023-11-13 0.5
marcel 1.0.2 2021-09-20 1.0.4 2024-03-01 2.4
method_source 1.0.0 2020-03-19 1.1.0 2024-04-15 4.1
mini_portile2 2.8.4 2023-07-18 2.8.6 2024-04-14 0.7
minitest 5.19.0 2023-07-26 5.22.3 2024-03-13 0.6
net-imap 0.3.7 2023-07-26 0.4.10 2024-02-04 0.5
net-protocol 0.2.1 2022-12-08 0.2.2 2023-11-07 0.9
net-smtp 0.3.3 2022-10-29 0.5.0 2024-03-26 1.4
nio4r 2.5.9 2023-04-02 2.7.1 2024-03-20 1.0
nokogiri 1.13.10 2022-12-07 1.16.4 2024-04-10 1.3
public_suffix 5.0.3 2023-07-11 5.0.5 2024-04-02 0.7
puma 5.6.7 1980-01-01 6.4.2 2024-01-08 44.0
racc 1.7.1 2023-06-14 1.7.3 2023-11-04 0.4
rack 2.2.8 2023-07-31 3.0.10 2024-03-20 0.6
rack-proxy 0.7.6 2023-01-17 0.7.7 2023-09-01 0.6
rake 13.0.6 2021-07-09 13.2.1 2024-04-05 2.7
regexp_parser 2.8.1 2023-06-10 2.9.0 2024-01-07 0.6
spring 3.1.1 2021-11-25 4.2.1 2024-04-22 2.4
sprockets 4.2.0 2022-12-20 4.2.1 2023-09-05 0.7
sqlite3 1.6.4 2023-08-26 2.0.1 2024-04-20 0.7
thor 1.2.2 2023-05-11 1.3.1 2024-02-26 0.8
tilt 2.2.0 2023-06-05 2.3.0 2023-09-14 0.3
timeout 0.4.0 2023-06-23 0.4.1 2023-11-07 0.4
web-console 4.2.0 2021-11-17 4.2.1 2023-09-05 1.8
zeitwerk 2.6.11 2023-08-02 2.6.13 2024-02-06 0.5
ruby 2.6.0 3.3.1 0.0
System is 96.7 libyears behind
bundle_report outdated
: This is a tool in ournext_rails
gem that we created to help developers extract more useful information about their gem dependencies. This tool lists all the outdated gems and measures what percentage of your gems are out of date.
Together, libyear-bundler
and bundle_report outdated
can help you determine your application’s dependency freshness . This metric can help you understand if and how outdated your dependencies are.
- Ruby Critic : Running this tool will tell you the number of files that have high churn and high complexity, concepts that I explain in my first article An Introduction to Software Quality . It also include code coverage data. Understanding which files have high churn and high complexity will help you guide refactoring efforts.
Below is a sample homepage in Ruby Critic. It shows the overall score, a churn vs complexity graph for the project, and a list of the files, churns, and smells of a project:
- SimpleCov : This tool shows the percentage of total test coverage of an application and the percentage of test coverage for individual files. This metric will help you determine how much of your code is exercised by your test suite.
Below is a sample output for SimpleCov. It shows what percentage of the files that are covered by tests:
- Skunk : Skunk is a RubyCritic extension to calculate a SkunkScore for a file or project. SkunkScore is a measure of the relationship between code complexity and code coverage . This metric can help you determine which files should be refactored and/or need more tests.
Below is a sample output for Skunk. The files are organized by SkunkScore, from highest to lowest:
- upjs-plato : While the other tools above focus on measuring technical debt in Ruby code, we also like to measure the debt in JavaScript dependencies as well. Running this tool shows JavaScript files that have high churn and high complexity. Our article on Using ES6-Plato to investigate the Express code base gives an example of what the report looks like.
Conclusion
Technical debt is an inevitable reality in software development. Understanding its various types and causes is crucial. By understanding why technical debt happens and knowing what tools you can use to measure it in your codebase, you can begin to develop a strategy to effectively manage and reduce it in your project.
Interested in paying off 1% of your tech debt every month? Let’s talk!