How to refactor
Refactoring should be done as a series of small changes, each of which makes the existing code slightly better while still leaving the program in working order.
Checklist of refactoring done right way
The code should become cleaner.
If the code remains just as unclean after refactoring... well, I'm sorry, but you've just wasted an hour of your life.
Try to figure out why this happened.
This frequently happens when you move away from refactoring with small changes and mix a whole bunch of refactorings into one big change. So it's very easy to lose your mind, especially if you have a time limit.
But it can also happen when working with extremely sloppy code. Whatever you improve, the code as a whole still remains a disaster.
In this case it's worthwhile to think about completely rewriting parts of the code. But before that you should have written tests and set aside a good chunk of time. Otherwise you'll end up with the kinds of results we talked about in the first paragraph.
New functionality should not be created during refactoring.
Do not mix refactoring and direct development of new features. Try to separate these processes at least within the confines of individual commits.
All existing tests must pass after refactoring.
There are two cases when tests can break down after refactoring:
You made a mistake when changing the code.
This is easy - just go ahead and correct the mistake.
Your tests were too low-level (for example, testing private class methods).
In this case, the tests are to blame, and the only way to fix this is to refactor the tests themselves and write new, higher-level tests.
A great way to avoid this kind of situation is to write tests in the BDD style.