How to know that you need to refactor?
There are always some clues that may suggest you need to refactor:
- You are modifying the same class over and over again, especially when there is no change in the requirements. There might be an addition to requirements: for example, your class should do more. But if that’s the case, you should probably extend it instead of editing it. And if you’re changing it over and over again, you are possibly violating the Open-Closed Principle.
- You are struggling with merge conflicts, or you are breaking one functionality by fixing another. It means that you are probably violating the Single Responsibility Principle.
- You don’t understand the code.
If I need to refactor, does it mean I’m a bad developer?
The answer is: not at all. There are many reasons why it makes perfect sense to refactor, for example:
- When you’re code is evolving in small steps for an extended time. Imagine that you are adding between two or five lines of code daily. After a year or two, you’ll end up with a class that has around one thousand lines. That’s a good reason to refactor.
- You never know where you’ll finish. You might start writing the code according to some assumptions, and when you finish, you notice that you’ve learned many new things writing it. Again, it makes perfect sense to refactor in this situation.
- Requirements tend to change. You might write the code, and after you’re done, the requirements change and you notice that your architecture is no longer sufficient.
- If you see that you need to clean up your code – that’s when you’ve already won! It means that you’re already a better developer, then you were when you started to write that code.
How to find refactoring opportunities?
My advice is: follow the ‘if’s.’ If you have a lot of ‘if’s’ in code and it’s hard to understand them, that’s an argument to refactor.
I think that the ‘if’s’ belong in two places in the code:
- Factories and builders
- Business requirements
It’s ok if business requirements need some conditional statements. But, if you are writing a lot of conditional statements, to have your pieces of code working together, something is wrong. If you find some complicated statements with repeatable structure, then again – something is wrong.
Flavors in code
There are many, but I would like to focus on two. The first one is ‘Object schizophrenia,’ which means that our object thinks it’s one of many objects. For example:
I’ve got the select component, and it has three flavors: singleSelect, multiPicker, and multiSelect. How can I find this in code? I can see the same ‘if’ in HTML and Typescript. It’s the same class, working in three different ways. The good news is that it’s easy to diagnose and repair that class:
Copy that class, as many times as you need (so for example: if you have three flavors of that class, copy it three times). Then use that original component as a facade – it’s really easy in Angular. You need to do two things.
- The first thing is to pass all the inputs to the lower-level components:
- And the second thing is to proxy all outputs:
When all of these actions are complete, you will have a working-class, and you will have three times more code. It doesn’t look like a success. And that’s why at this point you also need to reduce the code.
Code reduction is really simple. If you spot the conditional statements, it’s easy to reduce them in the single concrete implementation. By doing this, you’ll probably remove a lot of code and make the code simpler in the process. You should remove obsolete code from the facade component, as well.
Single Responsibility Principle or Open\Closed Principle Violation
Again, you can spot this by finding some ‘if’s’, and if you notice that there is some terminology that’s not part of your domain, that’s the exact spot to extract.
To extract, I personally always use notepad to write all the use cases.
Then I write the interface. I am just trying to guess what I’ll need to fulfill all the requirements that I had before.
How can I validate if my interface is ok? I need to put it in the code.
As you can see in the example above, I’ve put the interface in the component and replaced all the stuff on the left by using two single methods. Remember that, If the code is less readable after such change, you need to revert and check if you’ve chosen the right candidate to extract.
Of course, it’s one thing to replace the old functionality by the new interface, but it’s another thing to implement that interface. You can implement in two steps:
- Take all the conditional statements, that were there before, and put them into the single class; or
- You can build a family of classes:
If you extract it and it works, then congrats – you’ve done it. After that, you can refactor that extracted part.
To sum up
- If you have object schizophrenia in your code, then you can duplicate your code and reduce it.
- If you have SRP/OCP, you need to extract some code.
It’s easy to do, and I recommend using these strategies. There is one more thing that can help you a lot: writing tests.
Tests should be simple, descriptive, and they will guide you through the process of refactoring.