I’m a big fan of good code. Naturally, I’m also a big fan of refactoring.
Having said that, I certainly think it’s possible to refactor too much. I think the time to stop refactoring is when you’re no longer sure that the work you’re doing is an improvement.
Don’t keep a refactoring if you’re not sure it’s an improvement
If I refactor a piece of code and I’m not sure the refactored version is better than the original version, then I don’t help anything by committing the new version. I should throw away the new version and write it off as a cost of doing business.
I might be tempted to commit the new version because I spent time on it but that’s just sunk cost fallacy talking.
How to tell if a refactoring is an improvement
One way to tell if a refactoring is an improvement is to use heuristics: clear names, short methods, small classes, etc. Heuristics, together with common sense, can of course get you pretty far.
The only way to really, truly, objectively, empirically know if a refactoring is an improvement is to take a time machine to the next time that area of code needs to be changed and A/B test the refactored version against the unrefactored version and see which was easier to work with. Obviously that’s not a performable test.
So we have to do the closest thing we can which is to perform the time-machine test in our imaginations. Sometimes the time-machine test is hard because we’re so familiar with the code that it’s impossible to imagine seeing it for the first time.
When we reach this point, where it’s hard to imagine how easily Maintainer-of-the-Future would be able to understand our code, then it’s usually time to stop refactoring. It’s usually then more efficient wait and reassess the code the next time we have to work with it. By then we will have forgotten the context. Any faults in the code will be more glaringly obvious at that stage. The solutions will be more obvious too.
Refactoring too little
Having said all that, I actually think most developers refactor too little. They might be afraid of being accused of wasting time or gold-plating. But if you know how to tell with reasonable certainty whether a refactoring is an improvement, then you can be pretty sure you’re not wasting time. After all, the whole point of refactoring, and writing good code in general, is to make the code quick and easy to work with.
The surest way to avoid refactoring waste
The surest way to avoid refactoring waste is to keep a policy of doing refactorings before making behavior changes. If you need to make a change to a piece of code but that piece of code is too nasty for you to easily be able to change it, then refactor the code (separately from your behavior change) before changing it. As Kent Beck says, “make the hard change easy (warning: this may be hard) and then make the easy change”.
You can think of this as “lazy loading” your refactorings. You can be sure that your refactorings aren’t a waste because your refactorings are always driven by real needs, not speculation.
- The time before you change a piece of code is a great time to refactor it. That way you know that your refactoring is driven by a real need.
- It’s also good to refactor a piece of code after you change it. Just be sure you know how to tell when your work is no longer an improvement, and don’t succumb to the sunk cost fallacy.
- With those two points in mind, don’t be afraid to refactor aggressively.