Based on outdated technologies, but is critical to day-to-day operations, legacy apps can be a real pain.
Maintaining and upgrading existing legacy apps is a huge challenge for every software engineer today and they struggle with the problem of modernizing these systems while keeping their functionality intact, but today will be having an overview on how to rewrite a legacy application.
We start evaluating the application, identify flaws, weak spots, points of failures to fix first, most legacy apps suffer from rarely used but vital UI or Panels, dead code, outdated core operational functions, automated test suites, and non-uniform coding practices.
The team needs to understand the complexity of a legacy subsection of a product and how that impacts the effort to refactor, build, release pipelines, and test. Consider the value of completely rewriting outdated functionality with a tech stack used everywhere else in the application, document how many times instances occur. Analyzing these issues and removing dead code or outdated hacks saves valuable hours otherwise spent documenting, refactoring, deploying, and regression-testing code that does nothing, Identify and evaluate all the pros and cons of the current version. Assess if the code is understood and well-documented, runs on the supported OS/frameworks, and is scalable, the effort to rewrite non-conformant code needs to be documented and estimated so that the team has visibility into it. Express risks of non-uniform code in business terms (e.g., security risk, increased maintenance time, flawed end-user experience) so that these Agile backlog items are weighted and prioritized in upcoming planning sessions.
With that being said, the first step was the hardest to achieve due to many factors mentioned above, as evaluating a Legacy App is the first step to rewrtie it.
We shouldn't lose any git history while refactoring out app, even if we are doing massive reorganization of code (e.g., splitting code into multiple repositories or merging code from separate repositories into one), there are git techniques that preserve all git history. Look up the documentation for the git filter-branch
git merge -–allow-unrelated-histories. In order to avoid inadvertently losing some git history while doing modifications to subtrees of the code, make changes as a single commit by using
git mv. Also, if the situation warrants hosting code temporarily in two locations: move the code subtree to a new location, copy code to the old site, refactor as needed, and then delete the code from the former location.
Working on legacy application re-architecturing, refactoring, or re-platforming tasks need to move, rewrite, upgrade, and delete code a lot. For ease, unify tooling, including, but not limited to runtime versions, coding rules, artifact management etc.
In general, we should prefer code-based annotation files that reside in the root of the repository (e.g., editorconfig, .vsconfig) with a shared IDE ruleset coding style. By contrast, they do not want documentation that only exists in the company‘s intranet, development wiki, or even worse, undocumented coding standards.
Build time is probably the most critical factor influencing team behavior. A slow build on a local machine causes each individual to become less effective; slow builds on CI/CD incapacitate the whole team. Pull requests become too big and contain unrelated changes as developers subconsciously fight longer build times by batching more code into fewer feature branches.
More and more features or bug fixes bounce back because the simplest smoke checks failing on new code could be a sign that the local and CI/CD build definitions do not match. Drill-down deeper and try to understand if the local production-like build process is too manual and tedious, or, on the contrary, the CI/CD build process is more complicated than it should be and needs to be simplified.
Ensure there are enough testing environments. Most likely, working on product refactoring will need even more environments than we currently have to test old vs. new behavior frequently. For example, when evaluating a crucial upgrade for an application ORM upgrade, try spinning a dedicated environment to test proof of concept of this attempt just to find out if the performance results are unacceptable. Later on, to properly tackle this task, use two feature branches (though I don't prefer branching) and two environments. Ensure the first branch and environment contains code changes related to ORM upgrades and another pair contains performance-related upgrades. Monitoring and continuously comparing both environments bring visibility and certainty of each code commit. Finally, once happy with application performance, merge the latter feature branch into the main codebase.
We need to keep the application in a working state and limit the number of breaking changes to one at a time, while we may be tempted to break and fix multiple things at once, this approach is rarely successful in reality; failures start to cascade, you lose clear visibility of the progress and it takes more time than anticipated to stabilize the application. Red, Green, Refactor, the approach to take in TDD is perfectly valid in this scenario.
Keep a single track of potentially breaking changes and advance to the next one in line in sequence. Each partial refactoring or upgrade attempt needs to meet agreed quality standards before moving to the next one in line.
When assembling the project team, include stakeholders, subject matter experts, project managers, end-user representatives, and specialists from various engineering areas. Operations or support staff are sometimes under-represented in the team and shouldn't be. These people are the ones maintaining the legacy software and chances are they have a lot to suggest regarding application resilience, monitoring, and logging. If working with several teams (e.g., a team that is still working on feature delivery and a team dedicated solely for refactoring), think of ways to rotate team members to help with knowledge transfer.
Though in many cases the cost of rewriting legacy applications might be higher than writing a new one with similar functionalities if not the seem, but such practice should be generalized when you understand that it will probably take longer than you think, or even when the app has an extremely long development life cycle, or when the SW stack will be soon outdated, a lot of cases pop up when it comes to rewriting legacy apps.