The Importance of Refactoring

the-importance-of-refactoring

Recently, my house had a leak. After spending a long time trying to figure out the source, we eventually pulled down the ceiling and figured it out. No more leak.

This brought our attention to another problem, one that’s been around for much longer. For years our heating pipes have been making a loud crashing sound in the middle of the night. Loud enough, some nights, to wake up the house. It’s another problem we tried a few things for, but never found a solution.

But pulling down the ceiling revealed the problem. The previous owners of the house crudely cut a 2×4 and stuck it underneath the pipe to hold it up. Every night, the heat expands and contracts the pipe and it bounces and bangs against this piece of wood.

I could have just pulled down that small strip of wood, and the noise would go away. But just from looking at it, it was clear that it was put there to do something. I wanted to understand that first, before completely removing it. Turns out, it was holding the pipe up, inefficiently. Installing a few pipe hangers first solved that problem, and then I pulled out the wood.

And that got me thinking about how we approach refactoring code.

Load Bearing Code

I’m often doing the same things with codebases. At Reaktiv, we have long been asked to work on codebases that have been around for quite some time. Some have originated with us, and many more were created elsewhere. When they are given to us, they arrive in different states of structure or disrepair. It’s our job to assess the damage, understand what’s going on, and recommend and implement a better path forward.

There are almost always bits of code that I refer to as load bearing code. It was set up at one time or another to fix a very specific problem. Some patch that was implemented as quick fix, and then never returned to. Or the first draft of a feature without a final pass. It has a purpose. A specific and important one. But when you look at it from the outside it looks crude, unintentional, and unnecessary. It’s an awful lot like that block of wood.

Removing it altogether, however, can have devastating consequences. That’s what makes it load bearing. The purpose is essential, even if the implementation is subpar. Pulling out such a critical feature isn’t an option, but it also would be a mistake to simply try to replace it quickly. At first glance, it can be difficult to spot the side effects or edge cases it is accounting for.

This is why engineering teams frequently turn to a rewrite. A rewrite is a useful way to circumvent the problem entirely by building everything back up from scratch. We have recommended rewrites too, on occasion. The problem with a rewrite, however, is that it requires stopping everything in its tracks and starting over. It can be disruptive to a web product that is still in the process of evolving. You also run the risk of running into the same exact problem you were trying to fix in the first place without careful consideration.

More often, we’ll recommend refactoring instead.

The Importance of Refactoring

The combined experience of the team at Reaktiv is incredible. I feel fortunate to be able to work alongside genuine experts across a lot of different technologies and contexts. Together, we push ourself to go deeper into code, to understand and analyze what’s there.

We start with the structure, recognizing the interfaces and predictable patterns visible in the code. We identify the places where this structure is broken, and where the greatest points of friction are introduced. These are the most important places to start, because they block quick iterations and prevent us from building something new.

Along the way, we document the codebase. Sometimes referred to as a friction log, this documentation focuses on parsing out what gives us the most trouble, and how different parts of the code connects. Writing is understanding, and that is useful, but it also helps spread institutional knowledge to the rest of the team.

Then we work out a solution that maintains the functionality and features in a way that better fits with the overall structure of the code. The goal of a refactor isn’t necessarily to make everything look right. It’s to break apart the most complex parts of a code that are nevertheless there for a reason, and implement a solution that is cleaner, more maintainable, and easier to understand. To hang the pipe up properly and remove a noisy block of wood. That way, we can get back to the real work.

Working Iteratively

A refactor can be incremental. One area of the code, or a feature, can be extracted and worked on. We can work to understand what it’s doing. We can rebuild and restructure that feature, test it in isolation, then add it back into an active codebase without the risk of having missed something.

This requires time and investment on top of ongoing feature development. But it also doesn’t have to disrupt that ongoing development, and the overall cost is far less than that of a full rewrite. It allows us, to extend the metaphor one more time, to get rid of the banging sound without having to shut the heat off for a while.

Code evolves over time. Using iterative refactors is a way to make sure that the foundation you are working on stays intact and reflects modern best practices. We’ve used this strategy countless times at Reaktiv, and it’s allowed us to maintain and update codebases without a rewrite for over a decade. We have found this to be invaluable.

Refactoring isn’t just about cleaner code, it’s about setting your team up for long-term success. Reach out if you’d like to explore how.

Get the latest from Reaktiv