Refactoring code is very fundamental to any developer’s work. Yet I have come across relatively few resources that talk in-depth about this.
This blog post happened after this morning when I refactored my JavaScript code. It lasted just less than thirty minutes, but had me excited enough to return to writing here on Medium.
Let’s begin our story of the great refactor!
First, I had these two fetch
functions littered everywhere in my codebase with slightly different names that I wanted to refactor into a single module of reusable functions. Here are just two of them:
I’m not an extreme DRY advocate, but this felt cumbersome. Each function does very little from what could be achieved with just fetch over which it wraps. Apart from the encapsulating the endpoint URLs and the method property, these two look exactly alike and should be made reusable throughout the codebase.
Function should be pure when possible
My first and foremost criteria for a function is it should be refactored to be pure when possible. Purity means reusablity. If it needs to change any shared state, it might be a candidate for a method. This keeps functions easy to test and reusable. Functions with name like postLoginData
violates this. Here are a few ways to refactor it without thinking about the implementation:
user.login()
login(user)
post(loginUrl, user)
The above list was ordered from least generality to highly reusable. Actually, the first two share the same level of generality. Only the last one is reusable, and that’s what I was going for.
Now, you could see how my two functions are quite offending. Sometimes, you wear different hats and prioritize different things. It’s okay to rush through to get something working as long as we occasionally clean up things.
Justify refactoring
To decide if something should be refactored, I think of the intent and the worthiness of creating a function for it.
For example, a function which “POST” and another which “GET” data have fundamentally different intents, regardless of only a small difference in the implementation. The intentions are clearly distinguished enough to justify creating two functions.
However, wrapping an arbitrary URL inside a function, for instance, a login API endpoint, and then naming a function postLoginData
does not add much value to a function, considering its decreased generality. The URL, apart from being a one-liner string, should be a “story” of the caller. Consider an artist with oil paints, a palette, and brushes. What the artist wants to paint should be the artist’s story. The palette and collections of paints and brushes should provide variants to support the subject. Can you imagine a set of paints for painting oceanic scenes? That is sensible. Now how about one for painting a ship. Not so easy. The subject is just to specific to be encapsulated.
Without further ado, here is the second refactor:
Now this looks much cleaner with the repeated configuration object properties refactored into a constant baseConfig
. Also, I added an optional parameterconfig
to each function to make it configurable from outside. Object.assign
is used to merged the custom config
with the baseConfig
.
We can also see the object spreading in action. At this point, I was pretty pleased, but with spare time I decided to see if I could pull off something more. Here’s the final refactoring:
I personally like this version best because the get
and post
functions are very thin wrappers over the newly created send
function (which isn’t exported). This makes the latter the single point of debugging if bugs persist later on.
Refactoring is a tricky business, not because it’s hard but because it takes deeper design thinking. And make no mistake that you won’t get it right for everyone. Refactoring code to be reusable can surprisingly turn some people off, especially when the tradeoffs are much greater than the gain. Therefore balance is something to strive for. There are other factors for instance naming conventions and function parameters which can help with accessibility and should always be though hard about.