My team and I recently finished a long-gestating project which involved taking a ten-year-old codebase and rewriting it onto a new stack. Said rewrite took the better part of a year, but it's running in production right now and doesn't appear to have too many pervasive, evil bugs. People are using it, when they're not emphatically complaining about it. In short, it's a rousing success.
(What? We have reasonable goals.)
This project has directly resulted in several of my recent blog posts, including ones about the ultimate RestSharp client, our caching scheme for Web API and my rant on why it's so difficult to get started with automated testing. Now that it's done and deployed (or, at least, deployed, since software is never truly done), it occurred to me that my team and I have learned quite a lot about how to rewrite legacy projects with a modern tech stack.
Below is a simple five-step process my team has informally adopted when planning a rewrite of a legacy project. Perhaps it will be useful to your team, as well.
1. Treat Rewrites as New Projects
This first stage is the part of the rewrite that a lot of good programmers screw up, with disastrous consequences. You should treat a rewrite as though it is a brand-new project.
When developers are rewriting a project, we are not actually rewriting the code. Instead, we are rewriting the concepts, the business rules, that the application uses. The code itself is merely a detail, used to implement the desired concepts.
I know that sounds counterintuitive. After all, rewrites aren't really a brand new project, they're just updates to already-existing application, mere upgrades, not a new project and the massive undertaking that requires. Right?
Let's have some complexity from Flickr, used under license
That kind of thinking is the insidious force that makes rewrite projects take much longer than they should, and it isn't because all the requirements weren't accounted for, it's because the rewrite was assumed to be just another feature.
If you have decided a rewrite is necessary, you have implicitly decided to begin a new project. That is not a simple upgrade. Before writing any code for your rewrite, discover and plan out the concepts, rules, and ideology that the code needs to support, exactly like you would for a brand-new project. You will need answers to these kinds of questions before you can even start writing code. I might even go so far as to suggest that you should ignore the code entirely until it becomes useful in getting a particular requirement implemented.
Requirements first, code second. Just like in a new project.
2. Identify Business Rules
"Weeks of coding will save you hours of planning."
We when started our rewrite, we received a directive from our users: "make it do what it always did, but better." This turns out to be more harmful than helpful, because "make it do what it always did" does not constitute a complete set of business rules.
Business rules are a nebulous thing. For rewrite projects in particular, the users will see them as obvious; so obvious they don't even need to explain them. To us developers, unless we also worked on the original application, the business rules are probably much less clear. Spending the time up front to discover what the actual rules are, whether in meetings, by reading the code and documentation, or some other method, will save you exponentially more time down the road.
Who are your users? What do they want to accomplish? How do they want to accomplish it? What can we develop to make it easier on them? Ask your business owners these questions! The answers are vital to ensuring that what you build is in fact what they want.
3. Identify Cross-Cutting Concerns
There are many concerns when building an application (whether a rewrite or a true greenfield project) which span the entire project and cannot easily be broken into smaller problems. These special issues should be planned out in advance of writing any code.
Problems such as authentication, security, logging, exception handling, and dependency management may very well be handled differently on your new tech stack.
For example, during my team's rewrite project we were moving from ASP.NET 2.0 Web Forms to ASP.NET 4.6 MVC, and so each of the cross-cutting concerns I listed above changed dramatically. In particular, our company developed a brand-new exception logging platform which our rewrite needed to utilize, and so we had to plan out and write down how we were going to integrate the two at the very beginning of the planning phase.
Identify the cross-cutting concerns and have a plan to handle them before starting to write code. This will save you a great deal of time in the long run.
4. Break the Problems Down
Software is complex, and rewrites are no different. Here again we see the problems imposed by the "make it do what it always did" order; namely, that what appears to be simple from a distance turns out to be quite complex up close.
From the business's perspective, a rewrite could accomplish several things that are comparatively minor: better practices, better DevOps, new front-end design, more features, etc. These are all just simple upgrades from the business's perspective. But the business's perspective ("Oh, this won't take much time at all") is not at all the same as the developer perspective.
Business people see an app which, though old, works just fine; we developers see a crazy mishmash of old requirements, dead code, unpaid technical debt, and ersatz business logic. We developers see a chance to make things better, when in reality "better" really is "the same as it always was, just with a new design" to the business people.
Ultimately, this step is why it is so important to treat rewrite projects the same as brand-new projects from the very beginning. In a typical greenfield project, you will hopefully reach this phase of the planning process with a very good idea of what needs to be done. But, in a typical rewrite project, you'll reach this step with the barest minimum requirements, a vague sense of "make it do what it always did", and eventually you WILL have to re-do something you wouldn't have otherwise needed had you had more thorough requirements.
Break the problems down to manageable chunks, and plan out those chunks. Just like we do for greenfield projects. Are you sensing a theme here?
Finally, we have what is possibly the single biggest mistake developers make when starting a rewrite project: they forget to...
5. Assume Everything Exists for a Reason
"Reforms should not be made until the reasoning behind the current state of affairs is understood."
"Well, that's stupid, I'll do it differently," is just about the worst thing a programmer can think during a rewrite project.
Except in extremely rare cases, code is never written without a plan, even if that plan isn't immediately obvious (or only exists in the original programmer's mind). We need to take the time to attempt to understand why things are the way they are before we can change them.
Reading the legacy code is good at telling us the what but not the why; it is our job to fill in the gaps. That starts by reading more than the code; read the docs, the comments, the design specs, everything you can possibly find that relates to the project at hand.
Even then, it might not be enough. Whether or not you've found something that explains the code, go talk to the users! They'll have important input that we need to consider when laying out the rewritten application.
The five-step process above works for both rewrite projects and greenfield projects. This is entirely intentional; you should treat them the same way. Missing out on these steps due to the "make it do what it used to do, only better" mentality WILL screw up the project later down the road. I know, because this is what happened to us.
(Really, you thought we came up with this process by getting it right on the first try?)
No amount of code can ever substitute for a proper plan. Take the time to understand what you need to build, and how to build it. Rewrites are projects; don't treat them as simple upgrades. Future you will thank you for it.