Modernizing Legacy Code: Refactor, Rearchitect, or Rewrite?

Refactoring Legacy Code
Bob Quillin July 8, 2022

If your company is like most, you have legacy monolithic applications that are indispensable for everyday operations. Valuable as they are, due to their traditional architecture those applications are almost certainly hindering your company’s ability to display the agility, flexibility, and responsiveness necessary to keep up with the rapidly shifting demands of today’s marketplace. That’s why refactoring legacy code should be high on your priority list.

Almost by definition, legacy apps lack the functionality and adaptability required for them to seamlessly integrate with the modern, cloud-based ecosystem that defines today’s technological landscape. In an era when marketplace requirements are constantly evolving, continued dependence on apps with such limitations is a recipe for eventual disaster. That’s why the pressure to modernize is growing by the day.

Why Refactoring Legacy Code is Critical

Most enterprises today realize that they must do something to modernize the legacy apps on which they still depend. In fact, in CIO Magazine’s 2022 State of the CIO survey, 40% of CIOs say modernizing infrastructure and applications is their focus. But what will it take to make legacy app modernization a reality?

The basic issue that makes most legacy applications so ill-suited to fully participate in today’s cloud ecosystem is that they have a monolithic architecture. That means that the code is organized essentially as a single unit, with various functions and dependencies interwoven throughout.

Such code is brittle, inflexible, and hard to understand; modifying its functionality to meet new requirements is typically an extremely difficult and risky process.

As long as an application retains its monolithic structure, there’s little hope of any significant modernization. So, the first step in most efforts to modernize legacy applications is to transform them from a monolithic structure to a cloud-native, microservices architecture. And the first step in accomplishing that transformation is refactoring.

Related: Migrating Monolithic Applications to Microservices Architecture

The refactoring process restructures and optimizes an application’s code to meet modern coding standards and allow full integration with other cloud-based applications and systems.

But why “cloud-based”?

The Importance of the Cloud

The cloud has become the focal point of intense and continuous technological innovation—most software advancements are birthed and deployed in the cloud. That’s why Gartner projects that by 2025, 95% of new digital workloads will be cloud-native. What’s more, according to Forbes, 77% of enterprises, and 73% of all organizations, already have at least some of their IT infrastructure in the cloud.

The cloud is critical to modernization because it provides a well-established software ecosystem that allows newly cloud-enabled legacy apps to tap into a wide range of existing functional capabilities that don’t have to be programmed into the app itself.

That’s why today’s norm for modernizing legacy apps is to start by moving them to the cloud. Once relocated to the cloud and adapted to interoperate in that environment, such applications gain some substantial advantages, including improvements in performance, scalability, security, agility, flexibility, and operating costs.

But the degree to which such benefits are realized depends on how that cloud transfer is accomplished—will the app be optimized for the cloud environment, or just shifted basically intact from its original environment.

Migration vs Modernization

Many companies begin their modernization journey by simply migrating legacy software to the cloud. An app is transferred, pretty much as-is, without altering its basic internal structure. Some minor changes may be made to meet specific needs, but for the most part, the app functions exactly as it did in its original environment.

Because the app retains its original structure and functionality, it also retains the defects that undermine its usefulness in the modern technological context. For example, if the codebase was monolithic before migration, it remains monolithic once it reaches the cloud. Such apps bring with them all the limitations that plague the monolithic architectural pattern, including an inability to integrate with other cloud-based systems.

Migration represents an essentially short-term, tactical approach that aims at alleviating immediate pain points without making fundamental changes to the codebase.

Modernization, on the other hand, is a more long-term, strategic approach to updating legacy apps. The application isn’t simply shifted to the cloud. Rather, as part of the migration process much of the original code is significantly altered to meet cloud-native technical standards.

That enables the app to fully interoperate with other applications and systems within the cloud ecosystem, and thereby reap all the benefits that cloud-native apps inherit.

Options for Modernizing Legacy Applications

Gartner identifies seven options for upgrading legacy systems. These may be grouped into two broad categories:

  • Migration options that simply transfer the software to the cloud essentially as-is
  • Modernization options that not only migrate the application to the cloud but which, as an essential part of the migration process, adapt it to function in that environment as cloud-native software 

Let’s examine Gartner’s list of options in light of that distinction:

Migration methods

  • Encapsulate: Connect the app to cloud-based resources by providing API access to its existing data and functions. Its internal structure and operations remain unchanged.
  • Rehost (“Lift and Shift”): Migrate the application to the cloud as-is, without significantly modifying its code.
  • Replatform: Migrate the application’s code to the cloud, incorporating small changes designed to enhance its functionality in the cloud environment, but without modifying its existing architecture or functionality.

Modernization methods

  • Refactor: Restructure and optimize the app’s code to meet modern standards without changing its external behavior.
  • Rearchitect: Create a new application architecture that enables improved performance and new capabilities.
  • Rewrite: Rewrite the application from scratch, retaining its original scope and specifications.
  • Replace: Completely eliminate the original application, and replace it with a new one. This option requires such an extreme investment, in terms of time, cost, and risk, that it is normally used only as a last resort.

Since our concern in this article is with truly modernizing legacy apps rather than just migrating them to the cloud or entirely replacing them, we’ll limit our consideration to the modernization options: refactoring, rearchitecting, and rewriting.

Related: Legacy Application Modernization Approaches: What Architects Need to Know

Refactoring vs Rearchitecting vs Rewriting

Let’s take a closer look at each of these modernization options.

Refactoring

As we’ve seen, refactoring legacy code is fundamental to the modernization process. According to the Agile Alliance, one of the major benefits of refactoring is that it

“improves objective attributes of code (length, duplication, coupling and cohesion, cyclomatic complexity) that correlate with ease of maintenance.”

As a result of those improvements, refactored legacy code is simpler and cleaner; it’s also easier to understand, update with new features, and integrate with other cloud-based resources. Plus, the app’s performance will typically improve.

Because no functional changes are made to the app, the risk of new bugs being introduced during refactoring is low.

One significant advantage of refactoring is that it can (and should) be an incremental, iterative process that proceeds in small steps. Developers operate on small segments of the code and work to ensure that each is fully tested and functioning correctly before it is incorporated into the codebase.

As a result, when refactoring is done correctly, the operation of the overall system is never disrupted. This also eliminates the necessity of maintaining two separate codebases for the original and the updated code.

The fundamental purpose of refactoring legacy code is to convert it to a cloud-native structure that allows developers to easily adapt the application to meet changing requirements. A valuable byproduct of the process is the elimination of technical debt through the removal of the coding compromises, shortcuts, and ad hoc patches that often characterize legacy code.

Rearchitecting

Rearchitecting is used to restructure the application’s codebase to enable improvements in areas such as performance and scalability. It’s often employed when business requirements change and the application needs to add functionality that its current structure doesn’t support. Rearchitecting allows such changes to be incorporated without developers having to rewrite the app from scratch.

Because it goes beyond refactoring by making fundamental changes to the structure and operation of the code, rearchitecting is more complex and time-consuming, and it carries a higher risk of introducing bugs or business process errors into the code.

One of the major risk factors associated with rearchitecting (and with rewriting as well) is that with most legacy applications, documentation about not just the original requirements, but also of how the code has been modified along the way (and for what reasons) is inadequate or entirely missing.

For that reason, any rearchitecting or rewriting effort must be preceded by a thorough assessment of the original code so that developers gain a deep level of understanding before making changes. Otherwise, there is a high risk that even if the new code is technically bug-free, important business processes may be omitted or inadvertently changed because developers overlooked their implementations in the original code.

Rewriting

Full rewrites most often occur with legacy applications that are specialized and proprietary. Usually, the intent is not to modify the functionality or user interface in major ways, but to move to a modern (usually microservices) architecture without having to deconstruct the existing code to understand how it works.

Rewriting allows developers to start with a clean slate and implement the application requirements using modern technologies and coding standards.

As with rearchitecting, rewriting brings with it a significant danger of overlooking business process workflows that are implicit in the legacy code because of ad hoc patches and modifications made over the years, but which were never explicitly documented. Developers also shouldn’t forget that the legacy app is still in use because it works—it will have been heavily debugged and patched through time so that even low probability or extreme operational conditions are handled, if not gracefully, at least adequately.

For these reasons, developers involved in a rewrite must be extremely careful to ensure that all of the application’s use scenarios, whether documented or not, are uncovered and explicitly implemented in the new code.

One of the greatest dangers with a rewrite is that until it is completed, it may be necessary to freeze the functionality of the original app—otherwise, the rewrite is chasing a moving target. And in today’s environment of ever-accelerating technological change, that can be a recipe for disaster.

Joel Spolsky, formerly a Program Manager at Microsoft, and now Chairman of the Board at Glitch, cites a case in point. Netscape was once the leader in the internet browser market, but it made a fatal mistake by attempting a full rewrite of its browser code.

That effort took three years, during which Netscape was unable to update the functionality of its product because the original codebase was frozen. Competitors forged ahead with innovations, and Netscape’s market share plummeted. The company never recovered. According to Spolsky,

Netscape made “the single worst strategic mistake that any software company can make: They decided to rewrite the code from scratch.”

Doing a complete rewrite of a legacy application may be necessary in some cases, but such a project should not be undertaken without a full evaluation of the associated costs and risks. It’s tempting to just clear the decks and start over without all the complexities of dealing with inherited code. But, as experts like Spolsky are quick to say, doing so is usually a mistake.

Refactoring is Key

Refactoring, rearchitecting, and rewriting are not mutually exclusive options. They can be seen as points along a continuum in the process of modernizing legacy applications:

  1. Start by refactoring legacy code into microservices. This gives the app an essentially cloud-enabled codebase that can be easily integrated with other cloud-based resources and positions it for further updates and improvements.
  2. If the application needs new functionality or performance levels that can’t be achieved with its original structure, rearchitecting may be in order.
  3. If rearchitecting to achieve the required functionality appears to be too complex or risky, starting from scratch by completely rewriting the app may be the best option.

Whichever option is ultimately pursued, refactoring should be the starting point because it produces a codebase that’s far easier for developers to understand and work with than was the original.

Plus, refactoring will unveil hidden dependencies and business process workflows buried in the code that may be missed if a development team goes straight to rearchitecting or rewriting as their initial step.

Note that all of these options require a substantial investment of time and expertise, especially if they are pursued through a mostly manual process using tools that were never designed for application modernization. But that need not, and should not, be the case.

Simplify Legacy App Modernization

The vFunction platform is specifically designed for AI-driven, cloud-native modernization of legacy applications. With it, designers can rapidly and incrementally modernize their legacy apps, and unlock the power of the cloud to innovate and scale.

The vFunction Assessment Hub uses its AI capabilities to automatically assess your legacy applications estate to help you prioritize and make a business case for modernization of a particular app or set of applications. This analysis provides a data-driven assessment of the levels of complexity, risk, and technical debt associated with the application.

Once this assessment has been performed, the vFunction Modernization Hub can then, under the direction of architects and developers, automatically transform complex monolithic applications into microservices. Through the use of these industry-leading vFunction capabilities, the time, complexity, risk, and cost of a legacy app modernization project can be substantially reduced. To see how vFunction can smooth the road to legacy application modernization at your company, schedule a demo today.