The term “technical debt” was coined over a decade ago to help highlight a common problem in software development. Generally you can think of technical debt like this:
The cost of additional rework created by choosing an easy, shortcut solution now instead of the better approach that would take longer.
Delivering software to meet deadlines or functional goals that are wrought with technical debt is like buying items before you can afford them. Both practices can drastically affect your ability to reach your future goals, both long term and short term.
This article is a gentle reminder to recognize, fix, and avoid technical debt in your software projects.
Software is often released into production after a great deal of focus on what it can do. Unfortunately, not as much attention is spent on how it was built. Software can pass all functional, integration, and system tests but still contain technical debt.
So if the release happens on time with expected usability and functionality, what’s the problem? Similar to buying something really cool and shiny on credit that you really can’t afford, the day that the problem occurs isn’t typically shopping day.
Usually, the first indication there is a problem is when you find a bug in production. That said, most non-trivial software will have some problem when it is actually used, so there is nothing uncommon about finding a bug. The problem with technical debt is that bugs can be harder to diagnose and fix.
Often, the time needed to work on the next release is depleted because of the amount of time that must be diverted to repair existing problems. The problem becomes compounded when band-aids are applied to stop the bleeding in the near term, but simply add to difficulty of making changes.
Technical debt also has a detrimental effect on adding new features. Maybe the first release had a few trade-offs in functionality, but made it to production in record time. But the next release containing incremental changes took longer, even without as many capabilities as the original release.
This conditional can occur for many different reasons. For example, class responsibilities could be misplaced; business logic could be mixed in with the user interface code; code may be copied and pasted instead of being abstracted and reused. These bad practices and others make extension of existing functionality difficult and can easily cause new bugs to be produced.
Getting a software release to production is important, but it has to be available and run fast enough under load that users will want to use it.
A common symptom of technical debt is that while units of code work really well, when you put it all together and graduate to using real-life data, problems happen. You let all the users who never seem to follow what were considered to be the normal code paths loose on the system and the house of cards can come tumbling down. Many of the practices and quality initiatives that take time during initial development are there to counter these specific problems.
If your application is hard to maintain, hard to extend, and has a hard time staying up in production, you likely have a problem with technical debt.
So, you find yourself with a production release that is full of technical debt. No one can just start over, but something has to be done to free up development resources from constantly fixing bugs as well making the code ready for new features. There is no silver bullet, but these techniques can help.
Use Code Analysis Tools
Code analysis tools can be used to automatically detect common coding problems (SonarQube, for example). This type of tool will analyze existing code and give hints and suggestions about what can be done. Security vulnerabilities, unlogged exceptions, and convoluted implementations are just some of the types of problems that can be identified. This can be a great start for identifying existing debt.
Start Automating Tests
If you didn’t start your project off taking the time to automate unit, integration, or user interface tests, get started now. Even if you don’t have the time to go back and write an acceptable amount of tests (SonarQube can also show code coverage for tests), get started by writing a test for every bug you find. Write the test and make sure it fails when the bug is there. Then fix the bug and verify that the test now passes.
This technique will allow your developers to start learning good automated testing methods as well as helping prevent one bug fix from re-breaking a prior fix.
Be Willing to Refactor Existing Code
While you may never have the time to fully rewrite a poorly written implementation, identity the most problematic areas and take the time to refactor that code. It is very important that refactoring is seen as a normal part of the software development life cycle. Treating refactoring as a signal that someone did not do their job when the first wrote the code will lead to hiding of problems. Many time refactoring is more a result of evolving requirements than it is an indication that someone didn’t know what they were doing.
Be sure, though, that before refactoring code you have identified any bad practices so that you don’t end up with just better written technical debt.
Develop Strong Leaders
The more prominent reason for technical debt is not that your developers are incompetent or negligent. More commonly it is more a matter of a lack of technical leadership. Time pressures to get code working at the expense of good programming techniques occur more often when there are not good leaders. This is because good leaders push back and convince business stakeholders that all functionality not only has to be thoroughly tested but also built and organized in a way that lends itself to good maintainability, extensibility, and reliability.
Apply Best Practices
Most of the problems that developers encounter have been solved before and the techniques and practices that work best are documented. Team members need to understand proper patterns and methods for producing code as they do not come naturally to most.
Experienced leaders can help teach less experienced developers and using up-to-date frameworks and subsystems accelerate adoption of best practices.
Reviews, Reviews, Reviews
No matter how experienced your team is, reviews are important. Design reviews, code reviews, test scenario reviews are important; even on small, agile teams as they encourage multiple sets of eyes on all the software artifacts that are produced. They also help with giving less experienced team members to see more code other than their own. Most of us learn better by reviewing mistakes and how to fix them. The review process can be tuned to team and project size and does not have to slow and laborious.
For instance, a common practice for pre-committing code is the peer review that requires just one other team member to review the code before everyone else is affected by it. Not everyone gets a chance to comment on it, but having the one additional set of eyes on the code can catch a lot of problems early.
Be Agile, Not Zealous
Too often, it seems, that zealousness is confused with being agile. A too-common misconception is that the Agile Methodology means don’t bother documenting what you do and just get code to production fast. A truly agile team is one who is really good at getting high quality code to production fast. But how much functionality can actually move to production each sprint depends on a lot of factors.
However, no matter what your definition of done is for a sprint, it must include quality code that can be enhanced and incrementally improved every sprint.
While the discussion about code quality is an old one, we still find ourselves with imperfect teams of people repeating age-old problems.
Reducing technical debt is an exercise that must be performed on a daily basis by teams that are ever-vigilant toward maintainable, extensible, and reliable software.