Lecture on Technical Debt 2025-02-05
Presented at a lecture in a course at the University of Gothenburg.
What is Technical Debt
Technical Debt (TD) is a term used in software development to describe non-optimal solutions, often as a shortcut to meet a limited time frame, which can lead to increased maintenance in the future. Cunningham (1992) first introduced the concept of TD to describe coding practice where developers employ quick-and-dirty solutions to address pressing problems. Left unattended, these solutions would cause problems in the future. He leveraged the metaphor of financial debt to help financial sector managers understand the consequences of certain software development practices: “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.” (Cunningham, 1992).
TD is a crucial topic in software development, and the importance of managing it effectively has been recognized . As software development continues to evolve and grow, so does the need to address TD and as per today TD has been devided into multiple sub-areas.
Sub-categories of Technical Debt
By today, the main sub-categories of Technical Debt are:
Code Debt - bugs and stuff
Design Debt - example: not object oriented
Architectural Debt - example: scalability issues as the number of users grows
Test Debt - example: missing test-cases leads to missed bugs
Documentation Debt - missing, inconsistent, outdated, or incomplete documentation
Defect Debt - known defects not fixed
Infrastructure Debt - example: postponed infrastructure upgrades leads to downtime
Requirements Debt - mismatched system functionality and real-world needs
People Debt - the wrong people in the wrong place
Build Debt - building and running unnecessary code and tests
Process Debt - example: what the process was designed to handle may be changed
Automation Test Debt - poorly maintained test scripts, outdated test cases, lack of proper test coverage
Usability Debt - example: poor interface design and complex navigation
Service Debt - crappy web-services
Versioning Debt - example: unnecessary code forks
Security Debt - solutions that compromises security of systems
Real world examples
Examples from my own experiance.
Code Debt - a My Pages solution for a large telecom operator was not thread safe and when a customer logged in the data owned by other customers was shown - was stopped after 15 minutes, but was recognized by the press and gave a lot of bad-will for the company.
Design Debt - a web-solution where the same integration-code was copied to all pages needing the integration - very costly to update and ended up in a complete re-write of the application.
Architectural Debt - was part of a team building a channel integration platform in the mid 2000. The platform was built as a monolith and in the end got impossible to extend since everything was entangled. The replacement of the platform with a microservices based solution started 2010-isch
Test Debt - the Code Debt example above relates also to Test Debt - had the solution been tested with simultaniously logged in users the bug would have been found
Documentation Debt - have seen this a lot, espeacially in non-documented web-services which makes integration a trail-and-error game
Defect Debt - maintained a system that chrashed every night - did not have so big impact so rather than to fix the defect making it chrash we built a script restarting the system every morning
Infrastructure Debt - Copied an entire web-application once since no one dared to touch the server it run on (it had not been patched for years) and the application had to be re-written due to changes in integrations
Requirements Debt - Really common - have a fresh example where a solution has been re-written 3 times depending on incorrect requirements
People Debt - Unfortnetly also quite common. The most dangerous people are the ones that actually belive they have competence in a field, but do not, they can produce a lot of damage
Build Debt - The platform described in Architectural Debt required all dependencies to be built on every commit, making the build process very slow
Process Debt - Seen a really critic Process Debt that led to that citizens was not treated according to law (finincial support that was not approved) - the debt was found and fixed, luckily before the press got to know about it
Automation Test Debt - a very popular e-service solution in open source has only one vendor, since other vendors do not dare to contribute due to no test automation - it gets impossible to know if anything breaks on a commit
Usability Debt - Very common, especially in large systems originated from the 1990s, like Siebel
Service Debt - Common in older systems - overly complex web-services designed inside-out instead of API first
Versioning Debt - Have a recent example of a team that delays updating third-party libraries and dependencies due to tight deadlines. Due to the increased security risks the development is now halted so the debt can be fixed
Security Debt - TietoEvry recently got hacked, affecting multiple customers negatively, because they had not changed standard passwords on their servers
Strategies to Avoid and Mitigate Technical Debt
Code Debt
How to Avoid:
Follow clean coding principles (SOLID, DRY, KISS).
Use automated code reviews & linting tools (e.g., SonarQube).
Conduct peer code reviews to catch potential bugs early.
How to Mitigate:
Refactor legacy code incrementally instead of rewriting everything at once.
Prioritize fixing critical code smells that cause maintainability issues.
Design Debt
How to Avoid:
Enforce object-oriented principles (OOP) and design patterns (Factory, Singleton).
Use domain-driven design (DDD) for a scalable architecture.
How to Mitigate:
Refactor high-maintenance code to follow modular design principles.
Apply automated refactoring tools (e.g., IntelliJ’s refactoring features).
Architectural Debt
How to Avoid:
Plan for future scalability by adopting microservices, cloud-based solutions, or containerization.
Use load testing tools (e.g., JMeter, Gatling) to identify architectural bottlenecks.
How to Mitigate:
Introduce load balancers, caching mechanisms, and horizontal scaling solutions.
Gradually migrate monolithic systems to more scalable architectures.
Test Debt
How to Avoid:
Follow test-driven development (TDD).
Automate unit, integration, and regression testing.
How to Mitigate:
Implement a test coverage monitoring tool (e.g., JaCoCo, Coveralls).
Incrementally write missing test cases for critical functionality.
Documentation Debt
How to Avoid:
Maintain living documentation using tools like MkDocs, Swagger, or Confluence.
Require documentation updates as part of code changes (e.g., pull requests must include updated docs).
How to Mitigate:
Conduct documentation audits and set up automated documentation generation.
Use AI-driven documentation tools (e.g., ChatGPT for code explanations).
Defect Debt
How to Avoid:
Implement continuous bug tracking and prioritization (JIRA, Bugzilla).
Adopt zero-bug policies for major releases.
How to Mitigate:
Schedule bug-fixing sprints.
Maintain a technical debt register to track high-priority defects.
Infrastructure Debt
How to Avoid:
Use cloud-based infrastructure that scales automatically.
Maintain infrastructure as code (IaC) with Terraform or Ansible.
How to Mitigate:
Gradually migrate legacy infrastructure in small, controlled phases.
Perform capacity planning & disaster recovery tests.
Requirements Debt
How to Avoid:
Use agile requirement gathering (e.g., user stories & acceptance criteria).
Conduct regular user feedback sessions.
How to Mitigate:
Prioritize fixing critical feature gaps in upcoming releases.
Implement feature toggles to test new functionalities with users before full rollout.
People Debt
How to Avoid:
Ensure balanced skill sets in development teams.
Encourage continuous training & knowledge sharing.
How to Mitigate:
Reallocate underutilized employees to better-fit roles.
Improve onboarding & mentorship programs.
Build Debt
How to Avoid:
Optimize build pipelines (e.g., CI/CD with parallel execution).
Automate dependency management (e.g., Gradle, Maven).
How to Mitigate:
Implement incremental builds to avoid recompiling unchanged code.
Remove unused dependencies & redundant code blocks.
Process Debt
How to Avoid:
Regularly review and adapt processes based on team feedback.
Maintain a lean development process with clear KPIs.
How to Mitigate:
Introduce agile retrospectives to identify inefficiencies.
Implement process automation tools to eliminate bottlenecks.
Automation Test Debt
How to Avoid:
Regularly review and update test automation suites.
Ensure automated tests are stable and reliable.
How to Mitigate:
Implement self-healing test automation frameworks (e.g., Testim, AI-driven testing).
Track test flakiness and remove unreliable tests.
Usability Debt
How to Avoid:
Follow user-centered design principles.
Conduct usability testing before deployment.
How to Mitigate:
Prioritize UX improvements in future iterations.
Implement A/B testing to validate UI changes.
Service Debt
How to Avoid:
Enforce API standards (e.g., REST, GraphQL best practices).
Monitor API performance and reliability.
How to Mitigate:
Refactor low-performance APIs.
Use API gateways and caching mechanisms for efficiency.
Versioning Debt
How to Avoid:
Follow semantic versioning and maintain branching strategies (e.g., GitFlow).
Regularly update dependencies with tools like Dependabot.
How to Mitigate:
Merge stale branches and remove unnecessary forks.
Maintain a versioning roadmap to prevent outdated dependencies.
Security Debt
How to Avoid:
Enforce secure coding practices (e.g., OWASP Top 10).
Use automated security scanning tools (e.g., Snyk, Checkmarx).
How to Mitigate:
Conduct regular security audits.
Implement continuous penetration testing.
Final Takeaways
Preventing TD requires proactive planning, automation, and best practices.
Mitigating TD involves prioritizing fixes, continuous improvement, and strategic refactoring.
Tracking TD in a technical debt register ensures visibility and long-term control.