You already know you're in for some crazy code when you inherit a legacy system, especially one that is using a language or library that is outdated. So what do you do when you've been asked to both refactor that legacy system and implement new functionality in it? That's not an unrealistic situation for some web developers. There are plenty of AngularJS applications out there waiting to be switched over to a more modern framework.
When you get into the details of it all, it gets more complicated. You might be moving to a different way of model binding, your data is going to be handled differently, and the new library or language might not let you keep your current architecture in place. Balancing the legacy updates and the new code base takes time and a good strategy. Here are a few tips you can use to make this process easier and more efficient.
Break it down component by component
Since all of your business logic is on the back-end (right?), you can take the component approach on the front-end and try to keep the same architecture in place. This means you could add more components to make up a single component or it could mean you can merge multiple components into one. By going using this method you know exactly what each component is responsible for and how they should interact. You can take it a step further by creating documentation for each component as you go. That could be in the form of tests or written docs.
When you take the time to recreate each component in the new language, you're giving yourself time to work on refactoring and implementing new features and bug fixes. You have to go through and touch each component in the legacy system to make sure your new system has everything users are familiar with. Even in a system with hundreds of components, it pays to take the time to go through each one because you find ways to optimize the new application.
Refactoring an entire code base while implementing new stuff in the existing code is going to take time. Don't feel bad if you're moving "slow" because you are doing things the right way.
Go up to the directory level
Once you've converted all of the components in a directory, check to make sure that everything in the new system is connected correctly. Are all of the child components getting the right data from the parent components? Do you see any modules that you need to import to make your components work together? Going through the directories as you finish components will help you keep a big picture view of the application as you go through the details of refactoring.
When you have to go in and fix bugs in the legacy application, that's a good time for you to do a directory level check. If there are certain parts of the legacy app you keep hitting for your updates then spend some extra time on refactoring those parts. Looking at a directory as a whole helps you keep your new components in context as you maintain the old ones. It's also another way you can find places to improve the refactored version of the application.
Figure out your priorities
Prioritizing tasks is always a juggling act, but when you have to refactor and maintain an application it's like juggling triangles. Should updates come before refactoring or should bug fixes come before refactoring? Usually the business needs will dictate which set of tasks take priority over the others, although there comes a point where you have to tell them what you can focus on. Actively updating a legacy system makes it weird to refactor because you have to make sure any new functionality gets included in the new application.
So in a way you have to update an app that you haven't finished building. That's why you as a developer have to know when and where to draw the line. You only have so much time to work on your tasks which means management needs to clearly define what your priorities are and why. Once you have your main priorities set, don't deviate from them. It's easy to get in a situation like this and start to flip back and forth between the two systems. That makes it harder for you to focus on a particular library or language and slows you down.
Check the business logic as you go
Refactoring is more than translating code from one language or library to another. You have to make sure the new version is going to work with the same business rules as the existing app. While you're writing components or updating API calls, make sure that you aren't breaking business logic. As you are updating the existing app you'll notice that some things in the business logic are odd. You have a great opportunity here to ask product managers questions because you will have specific details to ask about.
The business logic is arguably the most important part of a system because it sets the rules for how the system should behave with users. This is important regardless of whether you're changing up the front-end or back-end. As changes were made to the legacy system over time, the business logic might have gotten fuzzy. Don't transfer that ambiguity to the new system. Part of a good refactoring is making a new code base that cleans up some of the issues with the old and that's especially true when it comes to business logic.
Focus on writing the new code well
One of the best parts of refactoring an app is that you get to start with fresh code. The new system can have all the best practices, the cleanest directory structure, and even good security practices. Take a note of all the things you wish were in the legacy system and implement them in the new one. That's one of the good things that comes from updating a legacy app while you refactor it. You get to start a new code base from scratch and that means you have the first shot at shaping how it grows in the future.
This is your chance to include all the tests and logging you ever wanted. You can keep the number of dependencies down and work on performance issues you noticed in the old app. Keep this same focus in the legacy app as well. Until you get an official deprecation date and it's been announced to more than just the engineering group, assume that you will be maintaining the legacy app for the foreseeable future. That means you need to keep that code base as clean as possible so don't let sloppy code sneak in just because the refactoring efforts have started.
What's something else you do when you have to refactor a code base? Do you handle the process differently depending on if it's a front-end or back-end refactor? Sometimes I'll refactor my side projects just to compare different languages or frameworks. It's a useful skill to have because it helps you form opinions about how different libraries implement the same features. Plus it's another way to practice coding!
Hey! You should follow me on Twitter because reasons: https://twitter.com/FlippedCoding
Top comments (3)
Thank you for your insightful content as usual. I'm currently going through this exact thing at work, with a back-end Nodejs Codebase. And my strategy is to breakdown the legacy App into loosely coupled MicroServices, one concern at a time. By moving away only what fits together (Auth, Sync etc) into http-based Services, it helps me take on a light cognitive load as and get some quick wins. I must admit though, it is not the most desirable situation to be in, but hey, gotta do the dirty work sometimes, right!
Thanks Ben! ☺