July 15, 2021

How Do You Fix A Legacy JavaScript Monolith?

How Do You Fix A Legacy JavaScript Monolith?

5 steps to unravel messy, spaghetti and bolognese saucy code

The words ‘legacy code’ often conjures up the image of some ancient relic from a bygone time where software development lived in water plans, clunky Java-based interfaces, and strange creations that whispers I am old and riddled with security vulnerabilities.

React, Angular, Vue, and Node.js are supposed to be the young, cool, and hip things that are supposed to save us from all that. Startups are using it left, right, and center. Entire businesses are built on JavaScript nowadays — frontend, backend, and all the way to cloud infrastructure orchestration.

But that’s the thing — it’s JavaScript, and JavaScript has been around the block long enough to rack up some serious legacies that are just easier to scrap and start over than to attempt to salvage. However, that is easier said than done. Imagine going up to your boss and telling him the truth — that the guy he paid over $100k per year for the past three years built him rubbish and that you need to start again. What you’re actually telling him is that his $300k+ investment is worthless. If he paid for a few of them to make a team, that’s at least a million or two down the drain.

To the boss — everything is working. They know it’s working and it’s the developer’s job to do whatever is needed to make sure it keeps working. He doesn’t care about the inner workings, the clean code architectures, the low coupling, modularity, the pattern implementations — it’s all gibberish to him. What the boss cares about is that he can continue to run his business.

In short, you’ve somehow inherited a legacy — sometimes born out of lack of proficiency or general understanding of how JavaScript actually works. Maybe the guy before you got the code from someone else. Who knows what the real deal is with how things ended up the way they did — only that it’s in your hands now.

You take a look at it and find that it’s a beastly monolith, all with its shackling level of tight coupling, strange syntaxes, and nested loops. It’s a spaghetti and bolognese kind of situation and there’s no way not to get yourself messy if you start jumping in — or is there?

Here are five steps to fixing a legacy JavaScript monolith.

Step 1: Get some tongs and put everything into a bowl

One thing I’ve learned about legacy code is that sometimes there is no hope in salvaging it. You’re more likely to spend more time trying to unravel its mysteries than actually making a solution that works.

Sometimes, you just have to accept that the work done is a sunk cost. For project leads, this is something that can be difficult to sell to your boss. There’s a chance that they’re emotionally attached to it because they’re the ones signing off on systems that turned out to be bad lemons.

From experience, the trick is not to emphasize how bad reality really is — but to sell what you’re about to do next as an upgraded version. You’re still going to use the old system, but transition it into something that will generate more money for them in the long run. How? Because the speed of development will be faster, along with shipability and reduced number of bugs.

In reality, what you’re going to do is demarcate off all the old code and work on a parallel development game plan so that the entire business doesn’t come to a grinding halt. You can still make ‘improvements’ and ship new features, but also make time for rebuilding the old parts to the standard that you want rather than force yourself to work with what you have.

Step 2: leverage ringfencing

The beauty of JavaScript is that there are so many frameworks, libraries, and modules aimed at making dev life easier and faster. Leverage them. Use them like lego building blocks — but be sure to be clear about your tech stack. don’t overmix and match frontends and backends. Stick to a couple of things that your team truly understands.

Over the years, I’ve found that there is a trick to making legacy cold work for projects and that is to use the bridge and adapter patterns wherever you can when time is short. When things need to be delivered, you just have to plug into the old code and make do with what you’ve got.

The major perk of using these two patterns is that they help create a ringfence around code you want to phase out, whilst allowing you to move forward in a clean and productive manner. This also allows you to create layers of code and clearly mark which parts are old, new, and features that are transitioning out of the legacy.

When you really look at an app for what it is, it is made up of multiple layers.  Even inside your spaghetti code, there are features and elements that fit into these layers. The easiest way to make an impact is to work your way down the layers — starting with views for the frontend, and API and returned data structures for the backend. For example, when transitioning the frontend part, you might just want to sort out your views first. This means you bridge and adapter up your logical layers (which are already linked up somehow to the data layer) to your new views.

Once parts of that are completed, you can work on transitioning your data and logical layers out. During this time, because you’ve got one layer sorted, your ability to quickly make changes increases and you can adapt your topmost layer to your needs.

A basic representation of what a full-stack app looks like

Step 3: convince the boss that you’re making progress

Depending on how flat your organization is, your boss may be the actual business owner or your product lead manager. Whoever it is, there’s a chance that they’re not that technical.

The trick is to focus on the positives. Don’t outright lie to them. You know that the spaghetti code is bad but they don’t need to know how bad. This is more an office politics kind of game where you need tack and the ability to see things from their perspective. Remember, the money spent on hiring developers is a sunk cost. You don’t want to accidentally paint yourself as the next sunk cost — which can lead to people pulling plugs on your hard work and actual progress.

This step is often left for team leads and product owners to deal with. However, every now and then, you might encounter non-technical people in power that just need some good news rather than another stream of bad ones. You’re hired as a developer to solve problems, not over-share your feelings about them.

Step 4: gently decommission old code

Based on the previous steps, the first task involves ringfencing everything and completing at least one layer — or at least parts of one layer. For applications, you can split a stack into different domains, meaning that you can work on its transition in parts.

Decommissioning old code is a process of getting rid of the original code. Some call it ‘refactoring’, others call it ‘rewriting’. Whatever the case, you end up with new code that fits better on the cohesion scale and is much more modular in structure and design. These traits are often near non-existent in spaghetti code.

It’s good to note that the bridges and adapters you write will also become redundant over time. Why? Because they’re only supposed to be temporary fixtures of code aimed at slicing up your workload so you don’t have to build everything from scratch all at once. Using bridges and adapters can also help clarify assumptions and create learning points for you and your team as your project develops.

Breaking down an app further

Step 5: reassess your situation

Yes. At the end of the day, you’re essentially rebuilding an entire app — just in parts. That’s what PayPal did back in the early 2010s when they transitioned into node.js

What you need to keep in mind is that code tends to age like dog years. As you gain more experience and understanding of the project, you will start to see cracks and crinkles in the code that you’ve just created. Don’t be afraid to fix them, or at least schedule in their refactors. It doesn’t require a bug to instigate this. Rather, a recognition and acceptance that things can be coded in a better way. It’s easier to upgrade and update code that’s still fresh than one that’s been around so long that it’s built up the equivalent of backlog grime.

Conclusion

At some point, you will build enough of the new app to completely turn off the old app. Meanwhile, you’re also adding new features and outstripping the old app in delivery pace and number of features. That’s when you know your work of dealing with the spaghetti monster monolith is done.

It is good to note that during the transitioning and development processes, practices and rules should also be put into place in order to ensure that this doesn’t happen again. Code architecture matters and often leads to cleaner code by default.

The finer details of unraveling spaghetti code are a longer discussion and much more nuanced. Here are a few resource articles to help you out:

Rethinking separation of concerns in React

Declarative vs. Imperative Thinking In A JavaScript Context Explained Simply

Modules in your JavaScript projects

What’s the Big Deal With Functional JavaScript?

Thank you for reading and I hope you’ve found this piece useful.