A Shared Language to talk about Technical Debt
Everyone has heard of financial crisis where the economy is in trouble and where people and companies are overwhelmed with financial debts. The consequences can be disastrous. Did you know that such crisis happen often in software development? Technical debt is a sneaky plague that can hurt projects, deliveries, people, and sometimes an entire company. This blog is the first in a series of blogs on technical debt on what it is, where it comes from, and how to deal with it. It will shed light on something we too often ignore, but can take control over by being proactive!
The intention of this blog is to define the term and to provide a shared language to talk about technical debt.
What is Technical Debt?
Ward Cunningham: “Although immature code may work fine and be completely acceptable to the customer, excess quantities will make a program unmasterable, leading to extreme specialization of programmers and finally an inflexible product. Shipping first-time code is like going into debt. A little debt speeds development so long as it is paid back promptly with a rewrite… The danger occurs when the debt is not repaid. Every minute spent on not-quite-right code counts as interest on that debt. Entire engineering organizations can be brought to a standstill under the debt load of an unconsolidated implementation.”
Technical debt, a term coined by Ward Cunningham, is a concept in software development that reflects the implied cost of additional rework caused by choosing a quick and easy solution now instead of using a better approach that would take longer. It’s a metaphor that equates software development to financial debt, illustrating the trade-off between short-term and long-term benefits. When a development team takes shortcuts, such as skipping parts of the coding process to meet deadlines or opting for a less optimal solution to save time, they incur technical debt. While these shortcuts might speed up the development process in the short term, they often lead to more work in the future as the team has to go back and fix the issues that arise from these shortcuts. This is similar to how taking on financial debt can provide immediate funds but requires repayment with interest over time. Technical debt can be categorized into two types: intentional and unintentional. Intentional technical debt is a strategic decision made with the understanding of the future cost. Unintentional technical debt, on the other hand, is accrued due to lack of knowledge or oversight, and is often discovered later when issues arise. It’s important to manage technical debt effectively. If left unchecked, it can accumulate ‘interest’ in the form of decreased productivity, increased complexity, and reduced code quality. This can lead to a slower pace of development, making it harder to implement new features or fix bugs. However, not all technical debt is bad. Sometimes, incurring technical debt can be a strategic decision. For instance, a startup might choose to incur some technical debt to speed up development and get their product to market faster. The key is to manage it effectively and pay it off regularly by refactoring code, improving documentation, and investing in automated testing. Technical debt is an inherent part of software development that needs to be managed strategically. By understanding its implications and making informed decisions, development teams can balance the need for speed with the importance of code quality and long-term maintainability.
Types of Technical Debt
There are various forms of technical debt. The following list is not exhaustive, but it covers the most common types of technical debt.
- Code Debt: A prevalent form of technical debt, code debt stems from shortcuts or subpar coding practices, resulting in inefficient or disorganized code, which is hard to read, maintain and test. This make all development and maintenance tasks slower and will degrade the “enjoyment” of writing code while demotivating engineers. More details on how to deal with code debt can be found in the blog post Clean Code: Writing maintainable, readable and testable code
- Design Debt: Design debt emerges when the system design is compromised, often due to time pressures or inadequate foresight during the design phase, resulting in a system that’s challenging to maintain and expand. Due to this kind of debt developers can’t get confidence quickly that their code will not break existing functionality and dependencies.
- Testing Debt: Testing debt arises from insufficient testing, including a lack of unit, integration, or system tests. This can lead to unnoticed bugs and software issues. Another important type of testing are flaky tests. More details on how to deal with testing debt tests can be found in the blog posts:
- Documentation Debt: Documentation debt occurs when system documentation is incomplete or outdated, hindering new team members’ understanding and slowing down system maintenance and expansion.
- Infrastructure Debt: Infrastructure debt arises when the supporting infrastructure, such as the operating system, database, development environment, build systems, and CI/CD pipelines, are outdated or inefficient.
- Architectural Debt: Architectural debt occurs when the system’s architecture fails to meet current or future functional and non-functional requirements, making the system difficult to maintain, adapt, or scale.
- Knowledge Debt: Team lacks necessary expertise or the knowledge is not spread among the team, leading to “knowledge silos” and bottlenecks that can slow development and increase risk. This may be due to staffing gaps and turnover or inherited orphaned code/projects.
- Dependency Debt: Dependency debt occurs when a system depends on outdated or unsupported third-party libraries or services. The Dependencies are unstable or rapidly changing. Teams are unable to take advantage of new improvements and remain vulnerable to security problems. This also can slow down the onboarding of new hires and cause frustration for current developers who are forced to work with older versions. More details on how to deal with dependency debt can be found in the blog post Keeping Dependencies Up To Date
- Migration Debt: Migration is needed or in progress: This may be motivated by the need to scale, license issues, costs, to reduce dependencies, or to avoid deprecated technology. It can also occur when a migration was poorly executed or abandoned: This may have resulted in maintaining two versions.
- Unused features / Dead Code Debt Dead and/or abandoned code: Code/features/projects were replaced or superseded but not removed. More features creates more conditions and more edge cases that developers have to design around, which erodes the delivery speed.
- Reliability or Performance Debt: These can affect the customer experience as well as the ability to scale the business.
- Tool Debt Inefficient tools (both proprietary and third-party) can introduce friction or overhead for developers, slowing delivery speed.
- Manual Process Debt When a part of the product delivery isn’t automated, it requires more manual time and effort.
- Automated deployments Debt Automated deployment workflows enable the ability to deliver features to customers continuously and at will.
- Coupling Debt Coupling between modules or services leads to teams potentially blocking each other, slowing down delivery speed. Domain Driven Design is a great methodology to deal with this kind of debt.
- Duplication Debt Duplication of code, services, or functionality can lead to wasted effort and increased maintenance costs. On the other hand should duplication be balanced with the need for autonomy and independence of teams. Reduction of duplication can cause dependencies between teams, which can slow down delivery speed. So it is important to consider the trade-offs.
In conclusion, the legacy codebase, while being a significant asset, also presents a formidable challenge due to the inherent technical debt. This debt, if not addressed promptly, can become a substantial obstacle in delivering high-quality solutions and adapting to the ever-evolving market needs. Therefore, it is important to implement measures to manage and reduce this technical debt effectively. In the forthcoming year, I will publish a series of blog posts, each focusing on different aspects of technical debt. These posts will not only shed light on the complexities of the issue but also propose potential strategies, tools, practices, behaviorus, constraints and other approaches to deal with it.