Refactoring as a mental model

Dec 26, 2020 ยท 5 minutes ยท Loading views

I often hear the term refactoring being used as a solution to common problems in the development cycle. I don't want to be the annoying person who says how a word has to be used, but it is undeniable that there is a confusion when people talk about it.

It is very common to see the term being used for bug fixes, abstractions and even simple re-structuring. Below I will present two examples to help undo the confusion that hangs over the word.

First example

I had to build an ordered list component and one of the requirements was to show the ordinal number, e.g. 1st, 2nd and 3rd. At a glance it is straightforward, but it's also a trap for developers to start thinking about problems they don't need to solve yet:

  • Solution: Should I use a library, e.g. Number.js?
  • Bundle-size: If I choose a library, will it increase the client bundle size?
  • Internationalization: Does it needs to take in consideration user's locale?

Although I regularly put myself in the state of overthinking, I don't enjoy working on problems that doesn't exist yet and decided to KISS. I set to write the most naive function possible and refactor if needed:

function getOrdinal(n) {
if (n > 3 && n < 21) {
return "th"
}
if (n % 10 === 1) {
return "st"
} else if (n % 10 === 2) {
return "nd"
} else if (n % 10 === 3) {
return "rd"
}
return "th"
}

A very naive implementation

At this point I wasn't worried with efficiency and other topics. The goal was to visualize the ordinal numbers on the list and this is working! Cool, right? ๐ŸŽ‰

Like I said, the function is very naive and there are several small changes that will, at the same time, improve the design and keep the outcome unchanged. Let's replace the main conditional structure with a simple switch case:

function getOrdinal(n) {
if (n > 3 && n < 21)
return 'th';
switch (n % 10) {
case 1: return "st";
case 2: return "nd";
case 3: return "rd";
default: return "th";
}
}

Pretty sure this is StackOverflow #1

And that means we just refactored our code! The design was improved and the outcome is still the same. ๐ŸŽ‰

Second example

For the orderly list in the previous example, I needed to make an HTTP request to get a user list. Since the project is written in React, I wrote a hook to reuse the logic:

function useUsers() {
return useQuery("user-list",
() => getUsers()
));
}

straightforward react-query ๐ŸŽ‰

By the nature of the server response, I ended up with a deep nested object that had a strange access pattern, e.g. data.data.users

As data.data.users might not be there always and the same goes for any nested property from the HTTP response, our React components had to check for undefined all around.

<ComponentA
propA={data?.data}
/>
// or even
{
data?.
data?.
users.length > 0 ?
<ComponentA /> :
null
}

Unlimited undefined checks

This example works and we sucessfully list the users, but also introduces weird patterns inside the code. Sounds like a good refactor, right?

In this case a simple transformation on the API response already does the job: we simplify the React components that need the data and keep responsabilities in place. Let's take a look on how it can be done:

export function useUsers() {
return useQuery("list-users",
() => getUsers(), {
// select let's us transform a response ;)
select: (data) => {
return {
...data.data,
};
},
});
}
// And now the components:
{
data.users.length > 0 ?
<ComponentA /> :
null
}

bang!

In this case we changed a few lines on the service layer and sucessfully got rid of extra code all around. A refactor that was small, improved design and kept the outcome intact.

When to refactor

Now that we outlined what refactor is: when should a code be refactored? Short answer: always. But the reality is that this question is one of the most overlooked questions in the recent history of Software Engineering.

Refactoring should be a regular practice, a mental model. When we start building with the refactoring mental model some aspects of the development cycle becomes more efficient:

  • We can ship faster and iterate quickly with the consumers
  • Enhancements costs less to be done
  • Testing gets easier

The refactoring mental model ๐Ÿคฏ

The idea is to always build the most naive and deterministic piece of code that solves your problem. Work from it, improve the design and keep it deterministic. Test. Repeat. During the process, adding new features to your code will be easier because there is no complexity involved on it!

What I love the most about it is that it reduces the amount of paralysis by analysis that one can have. It always reminds me that codebases are not born with abstractions from day one and it's okay to not follow DRY sometimes.

Refactoring mental model can help any team to ship faster because the code is simple from day one, and if the code is simple, testing becomes easier. The end goal is to make refactoring a healthy habit, even when adding new features to the code. Profit! ๐Ÿ’ฐ

A whole company can benefit from this because less bugs means less money being spent with engineers fixing bugs and less frustrated customers. External

Similar ideas

Quick disclaimer here: I haven't came up with this mental model only by myself. There are several resources available that I studied from and practiced throughout the last years. These resources and my experience empowered me to build this mental model and sticky with it. I can recommend some:

Conclusion

  • Refactoring is a continuous activity that we all should keep in mind whether we're coding a new feature, testing or fixing a bug.
  • Focus on what matters: sometimes repeating yourself is all you need. (Sorry but not sorry DRY people)
  • Enjoy the coding process and understand that it takes time to achieve the perfect abstraction. Winners also take the opportunity to teach their peers while refactoring your code.

What do you think? Let me know your thoughts and I'll be more than happy to talk about!

And if you haven't checked yet, I wrote a super cool article about how was my 2020 ๐Ÿ˜‰

FIN

Share this article ๐Ÿ™Œ

โ€ข