As a system grows, constant refactoring is needed in order to keep the code understandable.
There are four possible times to perform any piece of refactoring.
- During a change
- Independently of changes
- After a change
- Before a change
Some of these times to perform refactorings are good and some are less good. Let’s evaluate each of these times.
Evaluating the various times to refactor
Refactoring during a change
Of the four possible times to refactor, refactoring during a change is the worst time to do it. The reason is because when you look back at the changes you made, you have no easy way to see what’s a behavior change and what’s a refactoring. If you introduce a bug during your work, you won’t know if the bug came from the behavior change or a mistake in your refactoring. It also makes pull requests harder to review.
Mixing refactoring with behavior changes also creates problems around version control. If you discover later that your commit introduced a bug and you need to roll back the commit, you’ll be forced to roll back both the refactoring and the behavior change, even if only one of the two is problematic and the other one is fine. Mixing refactoring with behavior changes also makes it hard to pinpoint bugs using git bisect
.
Independently of changes
Performing refactoring independently of changes is an okay time to do it but it’s not my favorite. Bad code only costs something if and when that code needs to be worked with. Having bad code sitting in your codebase is kind of like having a dull saw sitting in your garage. Sharp saws are better than dull saws, but dull saws don’t cause problems until you actually have to use them. Better in my opinion to sharpen a saw either right before or right after you use it, since that way you can be more sure your sharpening is worthwhile because you’re sharpening a saw that you know gets used.
After a change
Refactoring code right after a change is a great time to do it. For anything but the smallest changes, it’s basically impossible to write good code on the first try. Sometimes a change triggers small, local refactorings. Sometimes a change alters the story of the codebase enough that a broad refactoring scattered across the whole application is called for.
One downside to refactoring after a change is that it can be hard to tell exactly what needs refactoring. If you’re refactoring something you wrote five minutes ago, then you’re not a very good simulation of a future maintainer who has to look at your code for the first time and try to understand it. For this reason I don’t refactor my code to an obsessive degree when I’m performing refactorings right after a change because I know that my idea of what’s “best” is likely to be wrong at this point in time. I’ll probably have a better idea of how to clearly express the code after I’ve had some time to forget what it does so that the gaps between what the code does and how well the code explains what it does are more obvious.
Before a change
Refactoring before a change is also a great time to do it. As mentioned above, it’s often better to refactor a piece of code you don’t understand than a piece of code you do understand, because if you don’t understand the code, then you have a stronger need for the code to clearly explain to you what it does. If you understand a piece of code throughly then the code might seem clear enough even when it’s not.
Another reason that refactoring before a change is a good idea is that you know for sure that your refactoring is going to be helpful. In fact, you may well discover that the change you want to make is basically impossible without some refactoring first.
A metaphor to remember this by
The Boy Scout Rule, “leave the campground cleaner than you found it”, is a popular rule among programmers, but I think the Boy Scout Rule too easily misinterpreted. In what way exactly are you supposed to leave the campground cleaner than you found it?
I prefer a kitchen metaphor. I think “clean the kitchen before you make dinner” and “clean the kitchen after you make dinner” make pretty good metaphors for refactoring before and after a change. Obviously it’s more annoying and time-consuming to try to cook dinner in a kitchen full of dirty dishes than a clean one. And if you clean the kitchen after you make dinner, it just makes it that much faster and easier to make dinner next time because there will be less cleaning.
Takeaways
Don’t mix refactoring with behavior changes. The best time to do refactoring is either right before or right after a change.