Name: We have started calling this "Defactor" at work...
Problem: Someone (not you!) has obeyed DontRepeatYourself and EmergentDesign, but the resulting design sucks. (Blame WhatIsRefactoring!)
Solution:
- Perform the opposite of ExtractMethodRefactor (InlineMethod, not be confused with inline keyword)
- Perform it for each call site into that DRY code.
- As you go, make sure your utterly wet code is strictly parallel. Make all the cloned code look the same.
- Run all your UnitTests between each tick of these refactors, natch!
- When your code is incredibly flabby, now reverse direction, and start ExtractMethodRefactors again.
- Always always always RefactorLowHangingFruit first.
- Unless that greedy approach lead to the bad design you're now undoing. (It works most of the time, but might run into problems for CrossCuttingConcerns.)
- In this WikiZen's experience, the odds of getting the good design go up with RefactorLowHangingFruit, and the odds of needing a defactor session go down as refactors were high-level, not low-level.
Result: A design which is at least
different, and possibly
better, than it was before!
I find this practice useful when writing highly time-sensitive, performance critical code.
- Make it work using the best possible development practices. Verify the concept is even valid to begin with; keep this source code handy in case you need to go back and add new features.
- When that's done, "defactor" to eliminate calling overhead. Leave a comment in the code telling you where you defactored from, for this is the master copy. If that master changes, is known to be correct, and there are still lingering bugs, it's possible the new code hasn't been propegated to all the defactored call sites yet. Search for the comment to locate all defactorizations.
- Note that this is a one-way transform. All new features are to be added to the "correctly formed" code first, and should be verified against the properly refactored version of the code. Once demonstrated correct, defactor for performance again, anew.
Ugh. I'd hate to call that sort of hand optimization of code 'defactoring', even if it does have some similar methodologies. Defactoring solves a problem (backing up a design so you can try again) that cannot readily be solved by other means, whereas the problem you describe is more of a MissingFeatureSmell that can be resolved by a language feature - perhaps PartialEvaluation (even limited PartialEvaluation such as inlining of code) or CompileTimeResolution.
Q: Wouldn't it be easier -- and more reliable -- just to revert from your source control back to the point before the refactoring started?
A: ItDepends. If features have been added while working with a design (before deeming it 'bad' enough to motivate change) then you will need to port these features back into the new branch after reverting. In that case, it would be difficult to call it 'easier' or 'more reliable'. But if you have the foresight to recognize that a particular refactoring is 'bad' immediately after refactoring (i.e. before any non-refactoring action is taken) then you can more readily revert and try again.
The ReFactoring behavior is supposed to mix in, over time, with adding features. They resist a big revert. If you know you had a big refactor session, only, then you can revert it. But refactors should be chronic, not acute!
CategoryRefactoring