One of the arguments for microservices is that they’re small and shouldn’t need refactoring. Generally services that provide purely infrastructure features need very few changes, and therefore require very little refactoring. In contrast, services that manage business logic might require refactoring at some point, just as monoliths do. What will certainly happen with microservices is that wider systems require refactoring, and this will sometimes lead you to separate a microservice that does too much or merge the functionally of two microservices that are closely related.
Refactoring is like tidying up your bedroom: it’s something that you need to do from time to time if you don’t want to be swallowed by a mess that can easily get out of hand. Let’s picture the following scenario: your wardrobe is full of useless old clothes you rarely use, your shelves host a large number of different magazines piled up at random, and finally your bed is packed with a number of jackets you don’t have any space for.
We could say the following features in your bedroom can be performed:
- You can store and retrieve clothes from the wardrobe
- You can use your bed to sleep
- You can search for a magazine to read
However these tasks have become awkward, inefficient and difficult to scale, and they highlight the following problems in your room:
- If you tried to add more clothes inside the wardrobe, you’d face trouble as there’s no more space available. In your code this could very well translate into overcomplicated functions that are difficult to understand, or tightly coupled services which make your code hard to scale when you need to add new functions.
- Surely you can still go to bed and use it to sleep. However, you will have to choose between sleeping with the company of your jackets or to waste time taking them out somewhere else, to then put them back again the day after. This is an example of when a very frequent task becomes absolutely painful or requires an extra work that shouldn’t be needed.
- You might decide at some point to search for the sport magazine from last month. However since all of them are mixed up and there are lots, it could take you a good while to find the one you’re after. You get a similar feeling when your code is poorly structured and lacks proper design: it could take you ages to find the exact line you need to change.
In other words: your room smells and you really need to refactor it.
Refactoring consists of restructuring your code without adding new features. This restructure will allow you to grow your application in a more solid, cleaner way and make it more maintainable. In some cases your system can also end up being more efficient.
We could then consider implementing the following solutions:
- We need to get rid of the useless clothes in the wardrobe to make room for new ones
- We could put the jackets inside the wardrobe because now they’d have the space, freeing up our bed
- As a separate unrelated task, we have to sort the magazines by date, separating them out by publication
Notice that task 2 has a dependency in task 1, otherwise you couldn’t put your jackets inside the wardrobe because the old clothes would still be there taking the needed space. These kinds of tactical decisions in a refactoring are driven by logic, but also sometimes by intuition and experience, and this is when refactoring sometimes becomes a bit more of an art.
After finishing our changes, the situation has improved:
- We’ve got more space in the wardrobe for more clothes
- Going to bed is much more comfortable
- Searching for the magazine is much faster
Can you picture this situation in your mind? After you’re done you’d feel relaxed, happy and much more confident about the way you’re managing you room. It’s the same feeling you’d experience after a good tricky code refactoring.
As you noticed, we haven’t added new features, we’ve just kept the ones we were initially performing. If you needed to add a new one, it’d better to do the refactor first and then work on the additional feature. Otherwise you will be stepping on your own toes, most likely creating more mess and confusion.
A good refactor at the right time could provide you with the following benefits:
- Improves readability
- Reduces complexity
- Increases maintainability
- Improves performance
- Reduces code. One more line of code is one more thing to take care of
- Improves extensibility
If done well, a refactor could even fix hidden bugs or vulnerabilities as logic and processes are simplified. Less is more.
When should I do it?
A refactor is time and effort consuming, and it can also be risky even if you have comprehensive tests backing you up. That’s why you should avoid doing a refactor without objective reasons. Here I’m presenting some hints or “code smells” that generally convey the need for a change in your code design.
* God Objects: these are methods, classes or even micro services that have overgrown. Normally this anti pattern doesn’t happen right away, but as a result of accumulating code without taking the time to separate out concerns in different classes/functions/services.
* Mirrors: These ones are easy to detect. They happen whenever you have to alter the code in several places just to make a simple change. Refactoring out mirrors is normally more complicated.
* Dispensables: They are like the old clothes from the wardrobe: entities we don’t use anymore. If we removed them, the code would become cleaner and easier to understand.
* Couplers: As an example, imagine we have a service that sends an email every time it receives a notification via rabbitmq, for example. If we created a centralised manager to send the emails as well as to deal with the subscriptions, routing keys and publications, we would be coupling our main logic (sending an email) with the protocol we are using to trigger it. If we wanted to support a different protocol (e.g. HTTP), it would be impossible unless we hacked our email sender or refactored to separate out concerns. Logic and communication protocols are totally different things that shouldn’t be coupled.
Generally speaking, you should consider refactoring your code whenever you feel that it has become unreadable, hard to scale, inflexible, too complex or inefficient. Time spent refactoring should be considered an investment: it will normally not provide you with new features or big wins in the short term, but it will allow your project to grow stronger and with higher quality in the mid and long term.