How organizations can shed the technical debt of legacy code
Few people get excited about digging into their organization’s old code, afraid of what they might find. While there are many legitimate business reasons to delay dealing with legacy code, companies that postpone efforts will eventually incur heavy technical debt.
This technical debt will often create situations in which the application becomes unserviceable or poses a risk to conducting business if it fails. But how do you know when it's time, and where do you start?
This article will explore different approaches organizations can use to tackle technical debt before it's too late.
How does technical debt happen?
Ideally, applications and systems should be kept current as part of an annual health check process, which should include:
- Applying patches at a point-level release
- Accruing for major version changes every three years or less
Custom applications differ slightly, but the same type of investment and rules apply. The focus for custom applications are the coding language, database management system, and actual custom code base versions. Custom-built applications should upgrade to the latest development language, database management system, and run-time libraries or dependencies. This keeps applications secure and puts companies in an advantageous position to retain top talent and to continue to service the custom application.
Technical debt occurs when companies lag in any of these areas or fail to budget accordingly to avoid technical debt (we suggest 15 percent of the initial application cost annually).
How can organizations recover from technical debt?
Companies in technical debt have a few choices they can exercise. When the application stakeholders are assessing their options, they need to ask these questions:
- How much need is there to enhance functionality versus replicate as-is in a modern technology stack?
- How well can business managers, engineers, and users articulate their needs and requirements?
- Is the legacy code structured cleanly enough to facilitate automated transformation?
- Are there new business goals or regulatory/security needs to comply with, or are there threats to core business operations that can’t be built in the current code base?
- Will a complete rewrite provide a competitive advantage?
Based on industry standard and our experience, companies should weigh their options by using the previous questions and their answers as a guide. The following matrix we developed helps visualize the options:
While "leave as-is" is often a choice, it is not recommended. If this system is critical to your business it is only a matter of time before it will fail, and chances are that it will fail when you need it most and leave you in a lurch to find a quick solution. If you are considering "leave as-is" as an option, you are on borrowed time and should take action.
An industry rule of thumb is that if your organization has kept up on its application maintenance cycles, then you are typically no more than two versions (e.g., N-2) behind the current application version, development language, and dependent components of the system. If you’re lucky enough to be in this position, your best option is to perform an automated application migration to the newest version. This may be a point release (e.g., v1.1 to v1.2) or a major release (e.g., v1.1 to v2.0). In both cases, a well-architected application can be easily upgraded in place with alternatives to the components that have been deprecated.
The same applies for a custom application, as far as the development language and supporting components, such as database management systems, run-times, etc.
Migration upgrade projects are typically a "big-bang" approach, meaning you convert the application or code to the new version and cut over the end-users all in a single conversion. This is clean and works well with smaller applications, which are less complex and are not far removed from the current version of the software.
For example, most migration upgrade projects are undertaken on a small code base or on very simple applications, which can be upgraded quickly. The applications are typically stable and well-written, which means that migration automation tools run smoothly.
If you have a brittle application and/or a large or complex code base, a migration upgrade is probably not a good approach. There is simply too much risk. For example, it is highly likely that the project will never finish, or that you will end up in an extend-or-replace scenario due to migration blockers with no viable solution.
An "extend" approach is much like a migration upgrade, but rather than taking on the entire application in a "big-bang" approach, you carve out epics or major components of functionality within the current application to upgrade, refactor, or rewrite. Then modify the legacy code to launch to this new component or integrate the new code so that you can slowly adopt the innovative technology at a pace that is sustainable and reduces risk on your success. "Extend" is a less risky approach and works if your legacy application can remain in place for as long as it takes to extend every component of the application to a new application.
The "extend" approach is typically embraced when an application is either nearing or reached its end-of-life. Your organization can negotiate to pay a vendor for extended support for a reasonable period until you have migrated to the new version. This is suitable if the application is no more than N-2 or N-3 from the current (N) version. If you go beyond N-3 your team will likely run out of time between extended support expiry and full migration of the application to the current version.
A riskier approach to an aging application is to look at a rewrite, sometimes referred to as a refactoring, which sounds less daunting. A refactoring effort begins with existing requirements, use cases, and personas. In many cases, however, key business people have long since left the company along with their documentation around processes and technologies, which can make replacing difficult.
Relying on code alone will get you only so far, depending on the patterns and practices that follow the code. The code itself could become unwieldly with many dead limbs or wicked logic trees that make sense only to the person who wrote it.
Refactoring is the best option if a migration has failed. If you end up here, it's best to go through the full software development lifecycle, starting with a lean requirements workshop that includes personas, story maps, and defining your new Minimum Viable Product (MVP). You can then go right into design and engineering the next generations of the product, which takes the lessons learned from the existing application and applies innovative solutions to business goals as well.
Devbridge Group has taken on a number of legacy code projects that include projects gone awry or projects that are so convoluted that other vendors shy away from them. In these situations, we look at the best fit technology and most efficient options to solve these problems through a technical spike. A typical engineering approach would likely be to start a greenfield development project and avoid brownfield history of the existing application. However, in today's world there are very few of those opportunities. We embrace and solve these wicked problems to get companies back to smooth operations quickly.
A technical spike typically involves analyzing the product from a design and engineering perspective to:
- Obtain and leverage existing requirements, architecture documents, and testing scripts to learn what the application does, how it works, and what obstacles or challenges may lie ahead.
- Try to get the current code to compile and run, depending on the application requirements to run and debug it.
- This project may not be a suitable candidate for a migration upgrade or extend—meaning, we recommend the company should consider the replace methodology.
- Look to leverage language migration tools to automatically convert code from the legacy version to the latest version.
- Assess any areas that are not translatable (if any) and determine if any can be refactored to a workable state.
- Assess the code for best practices in architecture, design, and object-oriented patterns.
- A further assessment of the code base and its functions determines the required user stories for the migration work.
- Estimate the story points and get approval to migrate, extend, or replace for management to begin product development.
- Execute the project in an Agile / DevOps manner to reduce risk and deliver results quickly.
Shedding technical debt can be a challenging project, particularly when dealing with legacy code. The questions and matrix within this article are a good starting point, but in situations where code is beyond N-3 and/or internal knowledge has been lost or compromised, it's best to seek outside advice from a qualified vendor.