Avoiding common pitfalls when replacing legacy systems
As agencies continue to implement the customer experience executive order, they will increasingly need to modernize legacy systems so they are flexible enough t...
As agencies continue to implement the customer experience executive order, they will increasingly need to modernize legacy systems so they are flexible enough to meet changing user needs.
This is critical groundwork to enable progress towards building responsive, human-centered government services. However, many contracts to replace large legacy systems are still based on long-term detailed plans, fixed timelines and other outmoded assumptions that discourage modern software delivery practices.
With that in mind, we’ve outlined three common pitfalls our practitioners have experienced when working with agencies to replace legacy systems. We then provide specific, actionable suggestions that can mitigate each situation. By implementing these recommendations, teams can better prioritize successful delivery throughout the planning process, even before the development work begins.
Pitfall 1: Underestimating the legacy system
Legacy government systems can be large, built over many years or decades, have an outdated user interface, use technologies that are no longer supported, or require cumbersome workarounds for core functionality. If they don’t look impressive, there’s a temptation to underestimate how much they actually do.
But existing legacy systems usually have extensive, specific functionality that needs to exist in the new system, even after omitting functionality that is no longer used. This can be dictated by the project contract, the system the software needs to run on, or a critical task that users need to be able to perform. While a new system can often improve on the implementation, certain capabilities need to remain.
Building new functionality while keeping key legacy capabilities
Sometimes teams consider it a failure to just replace the essential parts of a legacy system without adding new functionality. They miss the fact that creating a usable version with clean, extendable, tested and easily deployable code and architecture is both difficult to deliver and valuable in itself because it enables future updates and maintenance. The urge to focus heavily on new features and functionality is well-intentioned and often results in good, high-level ideas, but those ideas would be most successful if iterated on within a functional system.
As a result, agencies shouldn’t ignore the legacy system in requirements gathering. While users are the subject matter experts on the current system, realistically, learning the ins and outs of a large legacy system will often require different sources that will vary based on what’s available. These can include:
User interviews
System documentation
User training materials
Exploratory app use
Application code
By understanding the details of the legacy system, teams can position themselves to complete the required functionality and minimize the risk of any last-minute surprises.
Agencies should prioritize the essential features first and iterate on additional changes from there to ensure they complete and deliver all necessary functionality. This will help develop a clearer understanding of the business rules and system constraints and better enable teams to implement the “nice-to-have” features next.
Pitfall 2: Relying on large, infrequent deployments and releases
Government projects sometimes rely on large, infrequent deployments and releases despite the increased risks to system stability, user experience and flexible services.
Code is considered “deployed” to production as soon as it’s moved there. Deploying to production in smaller, more frequent installments is beneficial because the changes are smaller and less complex, have fewer dependencies, and limit risk by interfering with fewer systems. As such, the changes often require less down time and have fewer, simpler rollbacks.
Similarly, code is “released” when it’s made available to end users. Releasing code frequently to users is beneficial because it isolates high-risk features and enables a quick feedback loop so the project can course correct if necessary. Code that is deployed to production can be released immediately, selectively released to only a subset of users, or hidden by a feature flag for future release.
Even legacy systems with established, active user bases that rely on complete workflows can be functionally replaced in smaller segments. The feasibility and implementation of these ideas will vary greatly based on the specifics of the project, but agencies often overlook opportunities to purposefully structure projects to enable early and frequent releases.
Enabling frequency to minimize risks and increase flexibility
Martin Fowler wrote an article in 2004 suggesting a safer way of replacing important legacy systems is to build the new app around the legacy system so it can grow over time until the legacy one is no longer necessary. The details of this approach will vary and require deliberate planning and prioritizing, but there are many examples that demonstrate its success in enabling early and frequent releases.
Along with finding ways to run the old and new apps side by side during development, releasing early and frequently requires focusing on building functionality in vertical slices where functionality from all layers of the app is included. Rather than building a bunch of UI components with mocked backend functionality, build complete features so the front end, backend, authentication, logging, deployment, etc. are all included and can be released together.
This approach also helps agencies better understand all the layers of the system earlier, which can improve teams’ decision making about other parts of the app. Many projects have specific backend system complexity or limitations (integrating with dependencies, databases that must be used, schemas that can’t change, etc.), and these realities will often limit what teams can do. Significant re-work might be required if those realities aren’t discovered until later in the project.
Pitfall 3: Delaying or ignoring basic software delivery practices to meet deadlines
Teams have all experienced an unexpected issue or something that took longer than planned. They want to deliver, so they find a quick workaround. But in doing so, they may skip important steps in the process.
Suspending basic software delivery practices like testing, addressing tech debt, and focusing on error scenarios likely won’t impact the project if done rarely. However, doing so too often or continuing without remedying the issue can greatly impact the app’s stability and development speed down the road.
Testing code throughout the process
When teams are in a hurry, the temptation can be strong to add tests later. But trying to backfill tests is harder and less effective than writing them along the way. Context is important for understanding the edge cases that need testing, so it’s easier and more effective to do this during development when the information is fresh.
Additionally, code architecture is a big part of testability. If the code isn’t built to be easily tested, it might need to be refactored before teams can write tests. But refactoring an untested codebase carries a greater risk of unintentionally breaking functionality. It also takes more time to refactor an established codebase than a small section of code. And once patterns are established, they tend to be continued. Establishing testable patterns instead of doubling down on ones that are difficult to test makes the entire process easier and less time-consuming.
Developers might be afraid to refactor code they don’t understand — even if it’s needed — because they have no way of knowing if they broke something. These effects are exacerbated on large projects or ones with a lot of turnover because many people might have authored the code, including some who are no longer available to provide context. The pressure to deliver isn’t going to decrease over time, but teams’ ability to deliver will decrease because the code will be harder to maintain and develop.
Addressing tech debt
In addition to writing good tests, it’s important to be disciplined about continuously addressing tech debt. Some teams deliberately set aside a certain percentage of sprint points for tech debt tickets; others will include those efforts when prioritizing all of the work as a whole. The important thing is for teams to find a workable approach so they can hold themselves accountable.
Tech debt is a normal part of the development process, but agencies might not understand that or may think it’s a reflection of poor quality. Openly discussing the process and benefits of addressing tech debt to the stability and speed of future development can help mitigate these misunderstandings.
Resilient systems will account for and gracefully handle edge and error cases. Projects that try to go fast by only accounting for happy path scenarios can quickly become unstable and give a false illusion of progress. Any form that takes user input should be validated and have useful feedback if the information provided doesn’t meet the criteria. And teams shouldn’t assume the data will be clean. Bugs or different validation rules in the legacy app can cause unpredictable data, which the new code needs to handle purposefully to avoid inevitable problems.
Legacy systems and customer experience
Identifying the pitfalls and recommendations listed above can help teams tackle challenges that are common when trying to replace legacy systems. While this list is by no means exhaustive, avoiding these common pitfalls can help maximize the chance of successfully transitioning to a more efficient system that benefits both the teams that maintain it and the public that relies on it.
Kelsey May is director of engineering at Ad Hoc, a digital services company that focuses on the federal government.
Avoiding common pitfalls when replacing legacy systems
As agencies continue to implement the customer experience executive order, they will increasingly need to modernize legacy systems so they are flexible enough t...
As agencies continue to implement the customer experience executive order, they will increasingly need to modernize legacy systems so they are flexible enough to meet changing user needs.
This is critical groundwork to enable progress towards building responsive, human-centered government services. However, many contracts to replace large legacy systems are still based on long-term detailed plans, fixed timelines and other outmoded assumptions that discourage modern software delivery practices.
With that in mind, we’ve outlined three common pitfalls our practitioners have experienced when working with agencies to replace legacy systems. We then provide specific, actionable suggestions that can mitigate each situation. By implementing these recommendations, teams can better prioritize successful delivery throughout the planning process, even before the development work begins.
Pitfall 1: Underestimating the legacy system
Legacy government systems can be large, built over many years or decades, have an outdated user interface, use technologies that are no longer supported, or require cumbersome workarounds for core functionality. If they don’t look impressive, there’s a temptation to underestimate how much they actually do.
Learn more about the paths cyber leaders are taking toward zero trust at CISA, the Interior Department and Secret Service in this Executive Briefing, sponsored by Verizon. | Download today!
But existing legacy systems usually have extensive, specific functionality that needs to exist in the new system, even after omitting functionality that is no longer used. This can be dictated by the project contract, the system the software needs to run on, or a critical task that users need to be able to perform. While a new system can often improve on the implementation, certain capabilities need to remain.
Building new functionality while keeping key legacy capabilities
Sometimes teams consider it a failure to just replace the essential parts of a legacy system without adding new functionality. They miss the fact that creating a usable version with clean, extendable, tested and easily deployable code and architecture is both difficult to deliver and valuable in itself because it enables future updates and maintenance. The urge to focus heavily on new features and functionality is well-intentioned and often results in good, high-level ideas, but those ideas would be most successful if iterated on within a functional system.
As a result, agencies shouldn’t ignore the legacy system in requirements gathering. While users are the subject matter experts on the current system, realistically, learning the ins and outs of a large legacy system will often require different sources that will vary based on what’s available. These can include:
By understanding the details of the legacy system, teams can position themselves to complete the required functionality and minimize the risk of any last-minute surprises.
Agencies should prioritize the essential features first and iterate on additional changes from there to ensure they complete and deliver all necessary functionality. This will help develop a clearer understanding of the business rules and system constraints and better enable teams to implement the “nice-to-have” features next.
Pitfall 2: Relying on large, infrequent deployments and releases
Government projects sometimes rely on large, infrequent deployments and releases despite the increased risks to system stability, user experience and flexible services.
Code is considered “deployed” to production as soon as it’s moved there. Deploying to production in smaller, more frequent installments is beneficial because the changes are smaller and less complex, have fewer dependencies, and limit risk by interfering with fewer systems. As such, the changes often require less down time and have fewer, simpler rollbacks.
Similarly, code is “released” when it’s made available to end users. Releasing code frequently to users is beneficial because it isolates high-risk features and enables a quick feedback loop so the project can course correct if necessary. Code that is deployed to production can be released immediately, selectively released to only a subset of users, or hidden by a feature flag for future release.
Read more: Commentary
Even legacy systems with established, active user bases that rely on complete workflows can be functionally replaced in smaller segments. The feasibility and implementation of these ideas will vary greatly based on the specifics of the project, but agencies often overlook opportunities to purposefully structure projects to enable early and frequent releases.
Enabling frequency to minimize risks and increase flexibility
Martin Fowler wrote an article in 2004 suggesting a safer way of replacing important legacy systems is to build the new app around the legacy system so it can grow over time until the legacy one is no longer necessary. The details of this approach will vary and require deliberate planning and prioritizing, but there are many examples that demonstrate its success in enabling early and frequent releases.
Along with finding ways to run the old and new apps side by side during development, releasing early and frequently requires focusing on building functionality in vertical slices where functionality from all layers of the app is included. Rather than building a bunch of UI components with mocked backend functionality, build complete features so the front end, backend, authentication, logging, deployment, etc. are all included and can be released together.
This approach also helps agencies better understand all the layers of the system earlier, which can improve teams’ decision making about other parts of the app. Many projects have specific backend system complexity or limitations (integrating with dependencies, databases that must be used, schemas that can’t change, etc.), and these realities will often limit what teams can do. Significant re-work might be required if those realities aren’t discovered until later in the project.
Pitfall 3: Delaying or ignoring basic software delivery practices to meet deadlines
Teams have all experienced an unexpected issue or something that took longer than planned. They want to deliver, so they find a quick workaround. But in doing so, they may skip important steps in the process.
Suspending basic software delivery practices like testing, addressing tech debt, and focusing on error scenarios likely won’t impact the project if done rarely. However, doing so too often or continuing without remedying the issue can greatly impact the app’s stability and development speed down the road.
Testing code throughout the process
When teams are in a hurry, the temptation can be strong to add tests later. But trying to backfill tests is harder and less effective than writing them along the way. Context is important for understanding the edge cases that need testing, so it’s easier and more effective to do this during development when the information is fresh.
Sign up for our daily newsletter so you never miss a beat on all things federal
Additionally, code architecture is a big part of testability. If the code isn’t built to be easily tested, it might need to be refactored before teams can write tests. But refactoring an untested codebase carries a greater risk of unintentionally breaking functionality. It also takes more time to refactor an established codebase than a small section of code. And once patterns are established, they tend to be continued. Establishing testable patterns instead of doubling down on ones that are difficult to test makes the entire process easier and less time-consuming.
Developers might be afraid to refactor code they don’t understand — even if it’s needed — because they have no way of knowing if they broke something. These effects are exacerbated on large projects or ones with a lot of turnover because many people might have authored the code, including some who are no longer available to provide context. The pressure to deliver isn’t going to decrease over time, but teams’ ability to deliver will decrease because the code will be harder to maintain and develop.
Addressing tech debt
In addition to writing good tests, it’s important to be disciplined about continuously addressing tech debt. Some teams deliberately set aside a certain percentage of sprint points for tech debt tickets; others will include those efforts when prioritizing all of the work as a whole. The important thing is for teams to find a workable approach so they can hold themselves accountable.
Tech debt is a normal part of the development process, but agencies might not understand that or may think it’s a reflection of poor quality. Openly discussing the process and benefits of addressing tech debt to the stability and speed of future development can help mitigate these misunderstandings.
Resilient systems will account for and gracefully handle edge and error cases. Projects that try to go fast by only accounting for happy path scenarios can quickly become unstable and give a false illusion of progress. Any form that takes user input should be validated and have useful feedback if the information provided doesn’t meet the criteria. And teams shouldn’t assume the data will be clean. Bugs or different validation rules in the legacy app can cause unpredictable data, which the new code needs to handle purposefully to avoid inevitable problems.
Legacy systems and customer experience
Identifying the pitfalls and recommendations listed above can help teams tackle challenges that are common when trying to replace legacy systems. While this list is by no means exhaustive, avoiding these common pitfalls can help maximize the chance of successfully transitioning to a more efficient system that benefits both the teams that maintain it and the public that relies on it.
Kelsey May is director of engineering at Ad Hoc, a digital services company that focuses on the federal government.
Copyright © 2024 Federal News Network. All rights reserved. This website is not intended for users located within the European Economic Area.
Related Stories
One-year anniversary of the CX EO: Embracing a citizen-centric mindset
Public sector technology trends in 2023: Less on zero trust, more on CX
Automation and orchestration can turn legacy systems into “jet fuel for achieving your mission”