Transitioning Legacy Code As A Developer
Transitioning legacy code as we developers all know, can be annoying. (Which is sort of like saying water can occasionally be wet.) But it can also be an exciting chance to fix that code and whip it into better shape. But is "exciting" too strong a word? Read on to find out, as Senior Software Developer Darren Hooper takes us through a case study in transitioning legacy code.
The TextNow web codebase was predominantly in a framework known as Marionette. For those of you who aren't aware of Marionette, I don't blame you - it's an older framework on top of an even older framework. (Though to their credit Marionette is still in active development.)
Some of you may ask, "Why didn't you build the application in React?" In retrospect I entirely agree. React would have been the better way to go. However, at the time it was built React had been out for only a couple months. As anyone who has worked in the web development field for a while knows, frameworks are a dime a dozen and drop like flies. So at the time there was no particular guarantee that React would even last the year. So the decision was made to stick with the existing framework. I won't say this was a mistake exactly, but it's certainly come with it's share of pain points.
Which is why it's so exciting that we're now knee-deep into migrating everything into React. I've been playing with it and dreaming of that promised day when I can do all my work in a more modern framework. Turns out there's a lot to be excited about, like for instance...
1. Shiny new toys.
One of the most annoying things working with an old framework or language is realizing all the improvements that have happened since that you obviously aren't able to use.
In the days before transcompilers like Babel you were often forced to eschew new syntax because Internet Explorer wouldn't support it. But with the new tools you're able to use the cutting-edge of things without too much concern. My team can attest to the fact I've been talking about nullish coalescing and optional chaining far longer and far more often than necessary, so finally being able to implement these tools will be a mercy to us all.
When we started we didn't have Typescript. Which meant that when you were integrating someone else's code you had to make your best guess as to what properties were supposed to be. The IDE wouldn't be able to give you any assistance. I'm sure we've all had the experience dozens of times where something was supposed to be a "drop-in replacement" and you get stuck working on it for a week. With Typescript you get all sorts of hints so you can start using that new library right away and with minimal issue. It's always nice when something "just works".
2. Correcting the sins of the past.
When working on a legacy code base you're always forced to deal with "mistakes'" in the architecture. When you, or more likely, someone else, designed the application it was done to the specs, or lack thereof, that existed that the time. Sadly these can bear little resemblance to the functionality existing in the modern version. As an example, when we designed the checkout for textnow.com, it was for the purchase of a device, and a plan. That was it. Then we added the ability to buy a device with no plan, then a plan with no device, then the ability to gift to another user. Each of these was added on to the best of our ability, but the assumptions and design choices made at the beginning were not optimal to handle all of these cases.
Transitioning the code allows you to design the application as it should have been done "in the first place". By taking all that has been learned since the original code's inception, we can use it to build a better application - not only to handle the current functionality, but perhaps to also future-proof it as much as possible.
But a word of caution. You aren't a prophet. You might be able to make the "perfect'" design, one that handles everything wonderfully and with the faith it can be easily extended. But things are going to come up that you never could have predicted, some offbeat functionality your design doesn't cleanly handle. So you'll find yourself bending the design to incorporate the new functionality, and the cycle will start again. All code is destined to become legacy someday. Do your best to predict change when you can, but don't drive yourself insane.
Also, if you are a prophet, please see check out our job openings because we'd love to talk to you.
3. Joining the larger community
As mentioned above, it was never fun having to support Internet Explorer. Even worse is when you realize all the information online about your framework is about how to make Internet Explorer handle it. The web development world moves at a mile a minute, so when all the Stack Overflow posts you find are from 2013, it can be concerning.
Moving onto a new framework allows you to plug into the latest and shiniest development which comes with a host of advantages. You're now able to use cutting edge tools which can simplify previously almost impossible tasks. You're able to ask questions and get answers from the wide array of other people using the exact same framework you are. You might even be able to be part of the conversation and give you input into where the framework should go next.
4. Easier ramp up for other people.
Part of that larger community can include potential new employees. Not only is it easier to find developers for the non-legacy framework, but it's easier to get them up to speed. You get to skip the time learning on a framework they might never have seen. And you get to skip those embarrassing moments where you have to explain why something was done in such a seemingly terrible way, and how you don't like it either.
That said, transitioning does come with its own challenges, both in the technical and the personal sense. Beware these two things:
The In-Between State.
Generally speaking you won't get the opportunity to stop all development on a legacy code base and turn your full attention to building the new one from scratch. Which means that the migration is going to be a process. A good migration plan will have the steps from get A-to-Z clearly laid out. But this plan often forgets one key part: How does it look when you are at step N?
Depending on the exact setup this in-between state can last for a significant portion of time, and can be more frustrating to work with than the purely legacy code. The new code has to bend to handle some of the "old way", and the old way can be annoying as you don't have the niceties of the new code. Not to mention that in some cases it requires "glue" to hold the old and the new code together, which can make tracing through the code more difficult than it otherwise should be.
This happened to us early in the migration of our wireless site. We were moving into using Redux, so we attempted to modify the data flow to use the action/reducer pattern in preparation. This ended up being a frustration for everyone involved. Trying to do "almost-but-not-quite-redux" just made everything more complex than required. Once everything was fully migrated all was well luckily, but the middle state made things more painful than required. So it's important, in the cases where migration can take some time, to consider unrelated development that occurs during the migration, and not just the development required to make the migration happen.
The Ramp-Up For You
I'm sure we can all agree on that you, or anyone, is usually the best at things you consistently work at. Which has meant, for me at least, that I've become rather adept at using Marionette. The hard won experience that comes from working on it almost daily for several years. So when we began to move into React and the related technologies I realized something, I wasn't as good at it. I'd done tutorials and lessons and even some personal projects, but those sorts of things rarely approach the size and complexity of a full production application. Which means, like it had with Marionette, it was going to take some time before I approached the level of mastery that I would be satisfied with.
This came with a second realization - not only was I not as good as I wanted, but in some cases the junior developers were able to outstrip me. At first this was rather embarrassing, I was the senior dev, wasn't I supposed to be better at everything? This of course is both arrogant and unreasonable. Being senior doesn't make you all knowing, sometimes you learn from those even more senior than you, and sometimes you learn something from someone who has barely started. So you have to swallow your pride and come to terms with the discomfort of struggling with something new. It will be better, and you'll become better, but there will be some pain to get there