Why do we have bad code (code smells)?
Have you ever walked into a class/function that felt like everything was wrong? You start wondering how could this piece of code, be approved by someone. If quality is a constant in our development process, why does some code look like it's below the desired level?
This happens because we increase our capacity and our knowledge, and this is reflected in the way we code. So the legacy code reflects the time of the past. And that's also true for the community and technology. Things are evolving, and with that comes better infrastructure, libraries, frameworks, and concepts... Every development ecosystem is in constant development.
This evolution allows us to see problems where we were not able to before. That's why when you see a code from years ago, it will most likely make you want to throw it all away and do it again.
So we need to refactor to: make sure that our code meets our current standards/patterns. Remembering again that standards are constantly evolving.
What are we looking to achieve?
Fixing problems is not part of the refactor, or should not be related. If a problem is fixed with a refactor, that's a bonus. It's also not about performance upgrades, because if performance is a concern, it should be done in a separate step.
A good refactor doesn't need to make the code perfect, but to improve clarity, readability and simplicity. The refactor is the moment to:
Remove duplicated code;
Optimize suspicious logics that are working based on luck almost;
Break long methods into small pieces.
And much more...
Is it required?
By that I mean... If I don't do it, will it fail?
Well, in the short term, most likely not (at least it shouldn't). In the long run, it becomes vital to ensure the product's scalability (refactoring makes implementing new features a faster and easier task). Developing new features on top of legacy is like building a house on a faulty foundation. It might work, but if it fails, it's going to be challenging to fix the foundation with a house on top.
So it is with the code, at some point the code will succumb, because the old code will no longer have the strength to support the new one.
When to do it?
When the cost of refactoring is less than the cost of not refactoring;
Before implementing a feature, if it facilitates the implementation;
When coming into contact with dirty code for the second time;
When it's something fast;
When the pain of leaving code is greater than the pain of fixing it;
After the feature release.
Where to start?
With the unit tests, ofc.
Conclusion
We need to make the code adapt to us, not the developers that must adapt to the code.