A guide to agile for the maintenance-focused enterprise

Legacy software is a challenge to maintain. As much as you want to move on and build new software, keeping the old system running is important. Often, that old system is a company's flagship product, and will be until your new software is released.

There are many issues to tackle when keeping a legacy system running. Here are some of the more critical ones and how agile teams should deal with them, based on my own experiences. Get out in front of these challenges before they become overwhelming to your team—and your company.

World Quality Report 2018-19: The State of QA and Testing

Contrasting the old and the new

When using modern agile software practices to start on a new project with a blank slate, you follow best practices from the outset. You design with security in mind, build a safety net of automation, and use the fast feedback of a DevOps pipeline to work efficiently.

You may choose to work in a behavior-driven development (BDD) methodology, for example, to eliminate ambiguity and build quality into your product from the beginning, rather than testing it in later.

Practices such as these also put up guardrails against over-engineering and writing code that isn't necessary; working in this way reduces complexity that could introduce unintended risks.

However, automated tests were rarely developed for legacy software. Today's test engineers ideally produce a large number of unit tests, API tests, and a well-selected set of UI tests as a safeguard against regressions. But when your legacy systems were first developed, you likely had only a meager set of unit tests at best.

Also, the waterfall approach that was used to create legacy software was fraught with pitfalls. These included having developer and tester silos and associated communication gaps that, even though documentation was highly valued, could lead to misunderstood requirements. That in turn led to quality issues that plagued legacy software throughout the product's lifecycle.

From a coding standpoint, it was also less common to have gating practices such as code reviews, which enforce both the use of good design patterns and the writing of clear, easy-to-read code. These habits, common today, make maintenance much easier.

Keeping your old codebase going is tough—especially since legacy code can run for millions of lines. Some team members may understand parts of it, but no one truly understands the whole thing, especially when the original developers who wrote the code have long since moved on.

The unexpected longevity of legacy code

It is usually more cost-effective to build something entirely new than to update the venerable old legacy product. The problem with this version 2.0 approach is that it takes time to build—a lot more time than people think. And that, in turn, means your legacy systems need to be maintained a lot longer than you might expect.  

Meanwhile, version 1.0 remains the workhorse moneymaker for the company, and its upkeep requires resources. Because of the complexity of the legacy product, that maintenance may require senior-level resources, the sort of folks who can track down hard-to-find things such as memory leaks. If those senior-level resources are busy maintaining the legacy system, they won't be able to work on version 2.0.

Also, migrating customers to the new system can be complicated, due to their schedules, the training required to use the new software, or people's resistance to change.

As a product outlives its anticipated lifetime, it can run up against modern-day issues it was not designed to handle. You are likely to run into two major problems here: technical debt and security.

The technical debt challenge

Technical debt frequently accumulates in legacy software, and addressing it becomes more expensive the longer you wait.

On a recent project, the set of third-party web controls on our legacy application were no longer supported, and the issues we ran into could only be resolved by using a new set of web controls.

Because of the complexity of the codebase, this involved much more than just swapping out old controls for new. Development and testing the updates took more than one person-year of effort. That threw the project off track and required long and frustrating hours for the team.

We needed to update the web controls, which was unscheduled work, along with continuing our regular agile sprints of new-feature development and bug fixes.

Lesson learned: When considering wholesale changes such as this, keep in mind that those changes will be expensive.

At this point, you need to ask yourself pointed questions, such as whether it is worthwhile to go ahead with the updates, or whether you should use the resources required to do this instead for developing new software to replace the legacy system.

Experienced testers, with their knowledge of risk, should bring these points forward if no one else does so.

The guidance here for agile teams: When you're tackling technical debt at the same time you're building new features, build a buffer into your sprints. Where your team might have a normal velocity of 40 story points per sprint, consider cutting story work to 30 points per sprint when also undertaking technical debt work.

The problem with security

Security is another problematic area. When your legacy software was written, the developers probably had little idea of how sophisticated security attacks could get or how expensive and damaging they could become to the company's reputation.

Unfortunately, old code is hard to test. And because of the way the development process once worked, it's often complex as well. When analyzing an old codebase, you're likely to come across duplication, logical gaps, and "gold-plating"—the practice of adding unnecessary enhancements to the code that, while well-intentioned, can produce new sources of risk.

These days, software development teams use "test-first" strategies such as test-driven development and BDD to guard against such occurrences. That means testers can take very little for granted; extra risk means that if error handling is not impeccable, serious functional problems can occur.

You must carefully scrutinize any changes to legacy code, be they bug fixes or additional features, through the lens of risk. That places a burden on testers. The best thing a seasoned tester can do here is to go through any new code additions with the developer, and both must consider the risks that ensue from the changes.

Automate those tests

Updating the defenses of legacy systems places onerous demands on the testing team. Testers should arm themselves with modern software security knowledge and testing practices to better understand the risks associated with new features and bug fixes, as well as security updates to the legacy software.

While it may not be feasible to build an automated regression safety net into a legacy system, any new changes should be accompanied by automated tests at the appropriate level, be it unit, API, or, occasionally, UI.

Testers should include these tests and ensure that the tests are meaningful and effective. In grooming new stories for legacy systems, they should always think about how to incorporate automated tests. If an existing area of code is changing to address a defect, they should add automated tests in those places as part of the acceptance criteria for a bug fix.

Get the right skills

Testers have a great responsibility on their shoulders here, so make sure your team has the right skills. Ideally, the team will include at least one senior tester with experience working on complex, fragile systems. Such testers have a feeling for where risks can arise and how potential bugs can accumulate around problem areas of the code.

Code literacy is also a plus; testers need not be developers, but the ability to read, understand, and discuss code is beneficial. And testers should be clear and assertive communicators, calling out risks as they see them and helping to instill a culture of quality in the development team. They also need to be flexible, handling the unforeseen as it comes, be it in terms of schedule or priorities.

Weigh the risks, and get started

With legacy software you need to be much more mindful of risk than you would be for greenfield projects. You should be flexible, adaptive, and prepared to learn new things when developing for and testing old codebases. Finally, count on your legacy product living longer than you expected, and take into account technical debt, security, and accessibility as part of the care and feeding of your legacy product. 

Topics: Dev & Test

Popular Posts