“Don’t Repeat Yourself” is one of the most valuable ideas from one of the most valuable books I read during my software development career. If you can refactor away duplicate code, you will produce more general, more stable code. When you start DRY-ing up your code, though, you’ll start to run into some problems: code that doesn’t handle edge cases well, code that’s too generalized and hard to read, or code that’s hard to find. If refactoring toward DRYness doesn’t work all the time, how do you know when you should refactor?
Too-DRY code comes from a misunderstanding of what kind of duplication you should try to refactor away. You need to be able to identify the difference between essential duplication and accidental duplication.
Essential duplication is code that solves the class of problems you’re working on. This is the kind of duplication that you should kill right away.
Accidental duplication, though, is duplication that’s unrelated to the problem at hand, “duplication by coincidence.” If you clean up accidental duplication too aggressively, you’ll end up with more brittle code that has to be un-refactored as new cases are added.
But how can you tell the difference? With experience, you might get better at this, but even after decades of programming, I still can’t do this right even half the time. Luckily, there’s a general rule that helps a lot: Three Strikes and You Refactor.
The first time you do something, you just do it.
The second time you do something similar, you wince at the duplication, but you do the duplicate thing anyway.
The third time you do something similar, you refactor.
Can you see why this helps? By the third time, you should start to see where the patterns are. You should have an idea of which duplicate code is necessary for solving your problem, and which code just looks the same coincidentally. You can start to generalize (and DRY up) just the code that’s fundamentally the same across all instances of the problem you’re trying to solve.
Take a moment and think about the last time refactoring caused you pain. Were you trying to DRY up something that was duplicated between two copies of the problem, but the third copy was just a little different?
And the next time you feel like you have to duplicate some code, try waiting until the third copy before you refactor. (It’s really hard and feels terrible, but close your eyes and try it anyway). Then, think about it as you DRY up the code. Are you refactoring differently than you would have if you refactored right after writing the second copy?