What to do with legacy code
Legacy code, that old piece of software no one wants to touch, no one knows who wrote it and everyone’s afraid to replace it. Every developer has experienced that gut wrenching feeling of having to dig into an old code component to extend it or fix a bug. Best case scenario it’s an obscure piece of code that’s responsible for a minor part of your product, worst case, it’s the core of your business.
“Code becomes legacy as soon as it’s been written.” – Something I heard once and stuck with me. Although morbid, it’s not that far off.
Today is the day you decide it’s time to dump it. You have a choice, you can declare it maintenance only and go on a rewriting adventure – choosing the best technologies and eliminating everything that is wrong with the old system or, slowly but surely, you refactor it, incrementally improving and bringing it to your standards.
There’s no right answer, for every company, every developer, and every product there’s a method that will give the most value for the lowest price – in terms of money, time and effort. In this article, I’ll explain the pros and cons of each method and when to use each.
What is code rewrite vs. code refactor?
A code rewrite gives you the benefit of experience – you know the weaknesses of the old system, the design flaws, current requirements and future road map. You can plan for it, design a system that will overcome the challenges and plan for the future. The drawback is that you have to maintain two systems while you write the new one and trust me, even though the old system may be in “maintenance mode”, there will be that crucial bug or important client request that will require you to work on it. And don’t forget, once you have implemented that client request into the legacy system, it still needs to be added to the new system.
A code refactor will allow you to slowly replace old pieces of code in your system with new ones. , Take a function, a class or a module, and you rewrite it in the scope of your project. All of your tests and integrations are there, it’s easy to verify that you didn’t break anything, to make sure the functionality stays the same. But you can’t do wonders with code refactoring, you can’t choose a different programming language and most of the time you can’t even replace the core framework of your project.
A refactor will be much easier to sell to your product manager. Which do you think has a greater chance of success with your product/ marketing/ sales team?:
“We need a 3-month features freeze on the core part of the product while we write it from scratch. After these 3 months, everything will be the same but in the future it’ll be easier for us (R&D) to maintain and extend the product.”
“In the coming 4-months, we’ll be advancing slower on the feature requests and bug fixes for this part of the product as we work to improve the stability and maintainability of the project”
What would be easier for a customer of a small startup to understand; waiting 3 months with zero progress while R&D does something only they can appreciate or, have a small chunk of time bitten off every sprint to improve the product?
I know it seems as though code refactoring is the only practical way to get rid of legacy code without shutting down your company for a few months but read on, you may be surprised.
How to choose between rewriting vs. refactoring
I’ve had my fair share of rewrites and refactors, ranging from reverse engineering a 1000+ lines of a single PHP file and rewriting it as a microservice, to better scaling it for performance. This gives it some semblance of a maintainable system. I have also gradually shifted a service to work with Mongo instead of Postgres by writing to both DBs during the transition and splitting the logic to accommodate both. Can’t say I made the right choice every time but even the wrong choices helped me understand how to make the right decision in the future.
Here are a few guidelines to help you choose when to refactor and when to do a full rewrite. Ask yourself these 4 questions:
Is it part of a larger architectural shift?
If you plan on moving from a monolith to microservices or to serverless or any other major shift in your product’s architecture, it’s a great opportunity to rewrite, and every piece that’s taken out of your old architecture can be written from scratch.
Will the basic technology be able to support the requirements of your system for long?
Is it the core of your business?
If this legacy system is the core of your product and you can’t afford to stop or lose velocity. Your customers are waiting, your competitors are on your heels. In this case you can’t afford a rewrite and a progressive refactor is in order.
Are the people who built the system still around?
If not, it may be easier to rewrite instead of digging into unfamiliar code in an attempt to understand it and understand how to improve it.
I gave a presentation about code refactoring or rewrite at the <CODE> Components Meetup Tel Aviv, watch the session below:
A third option: "Strangle" your app
There is another way. Martin Fowler calls it StranglerApplication. It’s a way to “strangle” old code parts by rewriting and then, by event interception, calling the new components instead of the old code.
Let’s say we have a monolithic application to sell books online. It has the user history component, profile management component and the purchasing component. At some point we decide it’s no longer maintainable and we no longer want to scale the entire application when there are peaks in purchases. We want to rewrite the purchasing component. We will start writing it in a different project and we will deploy it to a different server. Every time a piece of functionality is ready we’ll replace it in the old system.
Assume that the purchase and confirmation order is ready. At this point, we’ll find the parts in our old system that call for the old purchase function and simply replace it with a call to the new system.
We’ll keep going like this until every use of the old purchasing component was replaced with calls to the new system and then we can safely delete that old code. The greatest benefit of this method is it reduces risk. You incrementally replace parts of the old system, you can still use your E2E and integration tests because the functionality hasn’t changed and the calling code is still in the old system. Another benefit is that you can pause or slow down the rewrite if the business requires it while having the ability to choose any stack you wish.
“We’re programmers. Programmers are, in their hearts, architects, and the first thing they want to do when they get to a site is to bulldoze the place flat and build something grand.”- Joel Spolsky
I very much agree with this statement and it’s important that every developer takes this into account when they choose how to approach legacy code. Rewriting always seems like the perfect choice, an opportunity to do everything better, to avoid all the mistakes of the old system. But you have to take into account the situation you’re in, do you have the capacity to maintain both systems? Can you spare the time? Will it actually be so much better? Sometimes code refactoring is the safer and the more practical choice. Sometimes doing a code refactoring may give you a quick win in terms of maintainability and performance while allowing you to invest in building new parts of your product from scratch.
The danger in those decisions is somewhat alleviated by microservices and serverless architecture, allowing rewrites of smaller code components, a process that takes less time and affects a smaller part of the product.