In the rapidly evolving software industry, achieving tasks quickly often becomes the priority. Repeatedly taking this approach to anything in life comes with a cost attached.
In software, this is referred to as technical debt. Understanding technical debt and legacy code is crucial for founders to maintain sustainable growth and software quality.
Thinking about debt
Technical debt is a term introduced by Warren Cunningham, a programmer and author in the 1990s. Cunningham compared technical debt to financial debt. In the financial domain, borrowing money can provide the necessary capital for investment in growth; however, it comes with associated costs such as interest payments. If interest payments are neglected, debt accumulates over time, which is counterproductive to growth.
What is technical debt?
Technical debt arises when businesses prioritize speed over quality during software development. This often involves rapidly releasing software to outpace competitors, resulting in shortcuts that accumulate as technical debt.
Similar to financial debt, technical debt can grow quickly if not managed properly. Common causes include unforeseen technical issues, poor-quality implementations, and deferred maintenance.
The key to mitigating technical debt lies in its effective management. Just like in financial scenarios, excessive technical debt can hinder growth and productivity, making it essential for businesses to address it promptly and strategically.
What is legacy code?
In software, we can break things down further: We have technical debt and legacy code. Both of these can lead to outages, performance, and security problems, bugs, and lowered productivity. A company's failure to invest in maintenance and refactoring work can accelerate both. However, it is important to be aware that technical debt and legacy code differ due to their origins.
Whilst technical debt is often the fallout of a rapid release, according to the Cambridge Dictionary The term legacy describes “Something that is part of your history, or that remains from an earlier time". In software, legacy code is used to describe code that may be delivering value to a business but is hard to maintain due to becoming outdated. Often, it is poorly documented, or the documents are out of date. This can escalate the struggle which may occur to understand it. It is not unusual to find development teams fearful of changing legacy code. Often, the code features components or modules that highly depend on each other. In software, this is called tightly coupled.*
The problems with tech debt and legacy code
The overall problem with tech debt and legacy code is reduced productivity. In the long term, the code is less performant, the maintenance issues can become draining, and the process of releasing new features can become more challenging.
The primary goal of a software developer is to create good code that is readable, testable, and extensible. Code created in this manner is also inherently maintainable. Whilst problems can start as minor ones, they can rapidly mount up, and with them, so does the cost of rectifying the issue, placing the revenue stream and, consequently, the business at risk.
While temporary fixes can be applied, this is akin to using a plaster for a short-term solution, and as complications grow, more resources are spent. This expenditure can be in the form of customer support time, developers fixing bugs, or simply the longer development cycle to release. Over time, it can become harder to recruit new developers as they may not have the skillset to work with the legacy code or may not wish to spend vast amounts of time performing maintenance tasks. Another factor to consider is that onboarding new members to the team can become more lengthy than necessary. Added together, this is a costly hit to a company and the overall morale of the development team can become eroded.
Mitigation strategies
While most companies will inevitably accumulate some amount of tech debt or legacy code, it is what they do with it that matters. Breaking the problems down and applying a defined strategy is important. If this is achieved during a company's early stages, it becomes ingrained within the culture, leading to collaboration, not resistance.
Assessment
To create the strategy, it is necessary to assess the problem, and there are several approaches you can take. These will likely entail all or some of a deep code review and the state of the test overage to highlight the most problematic areas within the code. A review of the project requirements, along with data collected from code analysis, code coverage, and code smell tools such as SonarQube can assist here. As with anything, it is the realization that there is a problem that will lead to a resolution.
Solving the problem
As work moves to address the issues, a great starting point is to focus on increasing code test coverage, particularly through end-to-end tests, and improving the continuous integration and continuous delivery processes. Focusing on these processes will enable the development team to make small and safe changes.
Automating the continuous integration and continuous delivery processes is valuable for any company to invest their time in. Automation will save time, increase stability, and reduce errors. It will also grow trust and create a faster feedback loop. If you don’t already have these processes, they will enable the team to make minor changes with greater confidence. Using tests will not only decrease risk from changes implemented during the refactoring work, but they can also assist in providing metrics for measuring the quality of the code and the ongoing improvements.
Refactoring code means improving it without changing its user-facing behavior. In real-life terms, this can be compared to taking a car in for its annual service. Refactoring can improve the software's overall performance. If the refactoring work is too widespread too quickly, it can risk errors being introduced within the code. To avoid this, it is important to make gradual, incremental amendments, perhaps even breaking the system down into smaller modules. These should be tested thoroughly.
Documentation
The importance of documentation cannot be understated. One of the major factors as to why legacy code becomes unmanageable is the lack of communication around the code itself. Many companies struggle with retaining developers and as they join and leave the company the knowledge around the codebase can become inconsistent. As a company matures the fact that no developer was present when the system was created becomes a possibility. Consequently, documentation not just at the birth of the project but continuously updated documentation becomes vital.
documentation is seldom the focus point of a startup, and understandably so. However, as a company begins to scale, investing in creating and maintaining consolidated documentation becomes critical. Good documentation should explain the logic and behaviors of the code. It should also cover installation instructions, domain concepts, architectural diagrams, and deployment information. Within the code, brief comments can be used to explain complex parts of the code.
Roadmaps
The team should work towards a roadmap where issues are based on outcomes or problems. Overall, the roadmap is vital to communicate with both the engineering team and the stakeholders.
Ensuring that within the roadmap, time is allocated to tackle the technical debt or update the legacy code will ensure that ongoing improvements are made and that the most at-risk areas get prioritized correctly. If the problems are more critical, a company can allocate specific sprints, perhaps occurring at set intervals, focusing on refactoring and decreasing the technical debt.
Another approach is factoring in additional time when a feature is worked on to decrease the technical debt around that code. The company should find the right balance that works for them here, considering the scale of their issues.
Reading tip: Five documents every startup should have
Continuous learning
Fostering a team culture where the team feels confident in making changes without fear is critical. One angle to this is promoting continuous learning. If a company views technical debt and legacy code as an opportunity, not a burden, it will far more likely overcome the issues. A growth mindset where experimentation is welcomed will ensure the code delivered is high quality and stakeholders are satisfied. Knowledge-sharing techniques are another approach that can assist with this. Programmers can use techniques such as pair programming or mobbing. These techniques, where developers work together, enable collaboration and can also be a tool to use during the onboarding of new members to the team. Additionally, code reviews, a process where developers review each other's code can be used as an opportunity to create discussion around architectural improvements. Overall, for the whole company, it can be beneficial to gain support from mentors and experts. Of course, books and courses can be purchased to assist the team in staying up to date with modern and best practices.
Codebases of every size and age will have flaws to some degree, and the code's age can increase issues. It is important to state that age alone should not be the reason to rewrite the entire codebase. While the team must stay on top of the modern techniques, they should avoid the trap of being drawn to using the popular new framework that could be trending at that moment. While there are times when entire rewrites may be appropriate, doing so can create extra systems requiring maintenance, which can consume resources and may not be the best solution for the business.
Prevention
There are many clues, such as a rise in bug reports, delays, and costs, that may mean technical debt can no longer be ignored. Customers may be reporting glitches, which can be red flags for bigger issues; customer feedback, the timeframe for onboarding new developers, and outages can all provide clues to problems arising and how to manage them.
Pay attention to your development team. They are the ones interacting with the code frequently. Additionally, ensuring custom support problems are reported to leadership will provide another communication channel.
Conclusion
In conclusion, addressing technical debt requires a holistic approach. While there is no single solution, establishing a culture of open communication and psychological safety is vital to solving these issues. Without fostering such an environment technical debt can build up without the awareness of leadership. Just like with a credit card, this can become a problem that can spiral rapidly out of control. Integrating both the business and technical strategies and ensuring resources are allocated adequately will safeguard against further accumulation and will assist with the long-term success of a company.
*From the Cambridge Advanced Learner's Dictionary & Thesaurus © Cambridge University Press) https://dictionary.cambridge.org/
Member discussion