Code Rewrite vs Code Refactoring. Choosing the Best Code Transformation Tactics

Author: Sergey Laptick

Implementing a perfect software solution on the first try is impossible. At some point, developers may start suspecting their codebase is a complete mess and doesn't correspond to what they had in their heads at the beginning of the project. It happens often and sometimes forces developers to make urgent code adjustments even before the product is launched, and users begin to find undetected bugs. As Fred Brooks wrote in The Mythical Man-Month, "The management question, therefore, is not whether to build a pilot system and throw it away. You will do that." It's not an action guide but rather a metaphor that nevertheless gives some clues about the state of affairs in the software development industry.

After the launch, the situation doesn't usually improve in the long run. Technical debt, obsolescence of initially used technologies and techniques, and other factors make code changes unavoidable. Here, developers have multiple options for getting the job done, but the choice is often between rewriting the code from scratch and refactoring it. Both these approaches have their benefits and drawbacks that we'll consider today.

Taking a Closer Look at Your Codebase

There are different signs that the source code of your project requires intervention. For example, maintenance may become pretty hard because developers can't say what a specific piece of code does. Dealing with such a code may require too much effort and make developers feel overwhelmed even by the idea of dealing with it. If you decide that living this way is no longer an option and want to introduce some fundamental changes, you should start with code assessment. It will help you identify what specific issues require your attention.

Code analysis tools are software applications that can become pretty useful. They inspect and evaluate the source code and generate reports or metrics that indicate the code's quality, performance, and complexity. Some analysis tools that can help with code refactoring are SonarQube, Code Climate, and ReSharper. Considering the pace of AI technology development and widespread adoption, it’s not a big surprise that there are a lot of AI/ML-powered code review tools. For example, CodeScene, DeepCode, Codacy, Reviewable, Snyk, and many others.

Technical debt and maintenance costs are the crucial factors signaling the need for quick changes. Technical debt refers to a situation when developers decide to postpone the implementation of technically complex solutions and choose a more straightforward path instead, which, over time, slows down development and makes code maintenance less convenient. In a way, it is similar to financial debt, where the cost of the debt increases over time if not repaid.

The application's inability to scale following increasing users is another major problem. More obvious signs include slow performance, lagging, and crashing of the app that you can detect via user feedback, which you, of course, carefully collect and analyze. Once all issues have been detected and analyzed, you can choose the optional way of introducing changes to the codebase.

Before choosing between code rewrite and refactoring, it's essential to understand what they are and what they are not. If fixing defects, delivering new features, or cleaning up the UI are among your goals, you better consider product enhancement techniques rather than code refactoring or rewriting. The approaches we're discussing today apply to a situation where the app does what it should do, but there's an issue with how it does it. Here, we deal with non-functional or quality attributes of a software system. If users are happy with the app's features but it crashes under peak loads and it's hard for developers to work with the code, code rewrite or refactoring may become the solutions you're looking for.

Code Rewrite. Tear It up and Throw It Away

Let's start with something anyone with no coding experience can understand. When we say "rewriting," we literally mean that you write the application code from a blank slate. Despite the apparent radicality of this method, it has its advantages and sometimes can feel like a relief since it frees developers from some limitations of the old codebase. In the case of legacy systems, some code parts may play significant roles, while the absence of proper documentation results in a poor understanding of how it actually works. Here, it's easier for the software modernization team to rewrite the code thoroughly.

When you need to rewrite the code, it doesn't necessarily mean that the development team has failed or made some bad decisions. Even big and successful companies sometimes resort to this tactic. In 2017, the Uber company that revolutionized how people move from one point to another was on a rocket-ship growth trajectory. Yet, the company decided to rewrite Uber's driver app to deal with technical debt, overcome product challenges, and align with engineering decisions made while rewriting the rider app in 2016. This code rewrite process is well-documented and considered super successful. However, it's crucial to remember that when rewriting the code, you must freeze the implementation of new features since, in this case, you'll be chasing a target that constantly moves. Not every business can afford not to pamper users with new features regularly.

Code rewrite is easy to understand but hard to implement. Creating the application code from scratch while preserving all its functionality requires two separate software development teams to maintain the old code and write the new one. Besides moving to new technology, developers may also move to more modern architecture, for example, shifting from a monolithic application to microservices.

However, rewriting doesn't mean replacing all your code. That's why you should start determining which code parts are still helpful and which ones need to go. Since you'll have to rebuild certain application parts, you'll need to create a new development plan, including project scoping, resource allocation, a clear understanding of the project goals and requirements, etc.

Code Refactoring. When There Is Still Hope

Refactoring is the most optimal way for those needing more readable code. Here, the risks are lower than code rewrite, and the rewards are high. It's a good practice to make refactoring a part of the routine to maintain the code base in the best possible conditions.

Here, you implement slight changes to the app code without rewriting it entirely or modifying how your software solution works in terms of functionality. You can think of code refactoring as a house renovation, where you make improvements without changing the initial structure. Under-the-surface modifications, however, are necessary to improve the system's efficiency, simplify its maintenance, and make the source code easier to read.

As you already know what your code issues are, you know where to start from and can apply changes incrementally and test if everything went fine. For example, code smells are solid signs that you need refactoring. They are specific patterns or characteristics in the source code that indicate potential problems or areas where the app code could be improved. Code smells are not necessarily bugs or errors. The app can work perfectly with them, but they make the code more difficult to understand and maintain. Some common examples of code smells include:

  1. Duplicate code. Repeated blocks of code that perform similar functions;
  2. Long methods or functions. Large and complex functions that perform multiple tasks. Long methods can be challenging to understand, test, and maintain;
  3. Large classes. Classes that have grown too large and handle multiple responsibilities. It violates the Single Responsibility Principle and can lead to a lack of cohesion;
  4. Feature envy. When a method in one class excessively uses the methods or fields of another class. It may indicate that the method should be part of the other class;
  5. Comments. Excessive or unclear comments can be a smell, suggesting that the code is not self-explanatory or could be refactored to improve readability;
  6. Primitive obsession. Overuse of primitive data types (e.g., using strings to represent dates) instead of creating dedicated classes to represent concepts;
  7. Inconsistent naming. Inconsistent or unclear naming conventions can make the code harder to understand;
  8. Too many parameters. Methods or functions with a large number of parameters can be difficult to use and maintain;
  9. Inappropriate intimacy. Excessive coupling between classes or modules, where one class knows too much about the internal workings of another. It can make the code less modular;
  10. God class is a class that has grown too large and handles too many responsibilities.

It's essential not to give up on code refactoring early. It may require multiple iterations until your code reaches the needed state, or you may decide that the only way to move forward is to rewrite it. It's essential to remember that one day, you'll have to rewrite, so refactoring must be backed by better tests, good readability, and clean interfaces. Plus, don’t forget that there are some helpful AI code refactoring tools, such as Refraction, Sourcery, and Code Refactor Assistant.

How to Choose the Proper Approach

In general, refactoring is a more secure measure compared to a complete code rewrite. You make one step after another, so planning the whole process and applying tests is simpler. However, in some scenarios, such a gentle approach is not enough, and developers must choose a riskier code rewriting method.

You should choose code rewrite when:

  • It takes more time to regression test than to introduce actual changes;
  • Tests cover the system well, the team knows it well, and the risks of code rewrite are low;
  • The code must be revised or have severe limitations that minor changes won't address;
  • You need to introduce significant design changes;
  • You want to implement new technologies or frameworks incompatible with the current codebase;
  • You're prepared for a potential interruption in delivery and are ready to invest very considerable time and resources.
  • The app uses or depends on outdated technologies;
  • The project is not under active development for a long time

It’s better to consider refactoring when

  • There's a lack of understanding of how a specific app module works and interacts with other modules;
  • The code works as intended but requires cleanup for better readability and maintainability;
  • You need to correct minor architectural flaws or make small adjustments;
  • Continuous delivery of new features is crucial;
  • Your team's resources and time are limited.

It's important to remember that many valuable tools and techniques can help the team perform code rewriting or refactoring with minimal possible harm. For example, a characterization test aims to capture and document the behavior of a software system. Testing helps to describe how the app should function under various conditions and scenarios. This info helps ensure that any changes to the app code do not introduce unintended side effects. After each refactoring step, developers can run the characterization tests to confirm that the system still behaves as documented in the baseline tests. When rewriting the code, the characterization tests ensure that the new implementation maintains the same behavior as the old one.

Let’s sum up the major differences between code rewrite and refactoring:

Refactoring

Rewriting

Main Goal

Improve code without changing app behaviorDeliver a new code version that provides the same functionality

Time and Resources

Made in small increments, usually less time-consumingTime-consuming and costly

Risks

Low risks as changes are smallHigher risks as it can introduce new bugs

Business Continuity

Continuous delivery of new featuresCan interrupt the delivery of new functionalities

Scale

Modifying a few parts of the codeScraping or rebuilding the code

Testing Requirements

Requires less testing as changes are small and incrementalRequires thorough testing of all the application features

Suitable For

Code that requires minor improvementsLegacy apps, code that is poorly structured, hard to maintain or understand

Results

Code that is easier to understand and maintainNew codebase with better structure and potential performance improvementsConclusion

Both code rewrite and refactoring are intended to improve the app's internal state without changing how it works. But despite the common goals, rewriting is always more challenging than making small incremental changes to the code. Therefore, you must constantly fight the urge to delete and rewrite all your code if you face some issues. Unless you deal with a decades-old app that won't work with modern database management systems and completely replace its outdated code with something new, the only way is to try refactoring. If your app works flawlessly, there's still a place for code refactoring since it allows you to leave the code in better shape than when you started working on the project.