How often have you seen a goal or project objective stated as “Rewrite XYZ”? It’s something I’ve observed at many companies, surprisingly from startups to giants like Facebook. I don’t believe this is ever a good objective however, and I’m going to explain why.
To begin with, “Rewrite XYZ” is a terrible way of stating the goal, because it doesn’t refer to any actual benefits. Generally it could be better stated by asking what the expected benefits are. Then it might become “Make XYZ faster”, “Add Unicode support to XYZ”, or “Add automated tests for XYZ”. In the latter case for example, the existing code might be untestable, which is the motivation behind rewriting it, but the thing we ultimately want to achieve is covering its functionality with tests. Your users / stakeholders don’t care whether you have rewritten some code, but they do care about stability of a certain component. If you cannot rewrite the goal in this way it may indicate the business case is weak… perhaps a developer is looking for an excuse to rewrite it to use their favourite framework, but it’s not really necessary. (Note: that’s not always the case, so if your developers say some code is bad you should listen to them!)
Aligning the project objective closely with the benefits provides several advantages. Firstly, we’re less likely to do unnecessary work. “Rewrite” is a very blanket statement. Perhaps we don’t have to rewrite every line of code in order to achieve the desired outcome, and some parts might be reusable. Secondly, it leaves the door open to different ways to meet the goal. Take “Make XYZ faster” for example. In reality we would need to make the goal specific and measurable, so lets amend it to “Make XYZ run in less than 1 second”. Now we’re free to do anything we can think of to hit that performance target. Maybe we could achieve it by sticking a cache in front of the relevant subsystem and not have to touch the (presumably horrible) code at all. Perhaps a faster database or more efficient network protocol would help. Either way we stay focussed on doing the easiest possible thing to reach the required target.
Biting the bullet
Rewriting isn’t always a bad thing to do, but its often time consuming and high risk, hence not rewriting is generally preferable. What happens when we have to do it though?
The “time consuming” aspect is best addressed by making components relatively small. One of the nicest things about microservices, especially if you keep them compact, is that if one of your services is flaky or hard to work with you always have the option of simply throwing it away and rewriting. The small size ensures that will only take a few weeks, and the decoupling via APIs makes it easy to drop in a direct replacement without worrying about a ton of dependencies.
All of that assumes you have a nice architecture though, and lets face it with legacy code you often don’t. What about managing risk? The next thing you should do is work very hard indeed to create an iterative plan. You’re going to hear “it’s a complete rewrite, we can’t replace it in phases” but try not to accept this logic. Firstly, look for any pieces that are self-contained and could be split apart. Don’t be afraid to build temporary scaffolding so you can invoke part of the new code along with part of the old during the transition period. Secondly, look for anything which resembles an interface. Maybe there’s no internal API, but if intermediate results are written to a file or database that’s a point at which you can intercept the data and pick it up with your new system. It might be a little clunky but it will let you connect the two systems and start to test parts of the new solution as they are written.
If all else fails, and a hybrid of the old and new solutions is truly not feasible, get the two systems up and running in parallel on your real data as soon as possible. The new system won’t be complete yet so its outputs will just be discarded, but by doing it this way you’re guaranteed it will be able to scale to the required throughput and won’t barf on unexpected real-life data, as might be the case if you plug it in near the end of the project. Furthermore, as you start to build out the new system you can get it to dump its intermediate results and see if they look good, possibly comparing them with those from the old system. Finally, when its time to activate the new system it will be a simple matter to redirect part of your traffic to it and keep the old system live in case you have any problems.
One caveat is that if the system being rewritten is not in production (maybe it was a demo day project or hacky prototype which is now becoming a “real” project) of course it’s fine to bin the old code and rewrite it!
- Never state a goal as “rewrite XYZ”, dig deeper to what you are actually trying to achieve.
- Avoid rewrite projects as much as possible since they are typically expensive.
- When you have to rewrite, try insanely hard to come up with an iterative / hybrid approach.