Don't Repeat Yourself Can Be Harmful
Don’t Repeat Yourself (DRY) is a software design principle aimed at reducing duplication. This is a popular principle and easy to understand: if you have the same code in 2 different places you need to extract this into a single location, avoiding the duplication. The thinking behind this principle is also obvious: when you inevitably change this code you now need to remember to update it more than one place. If you don’t, you introduced a bug. Simply removing the duplication avoids this. Violations of the DRY principle is sometimes referred to as WET - which can mean ‘Write Everything Twice’, ‘We Enjoy Typing’ or ‘Waste Everyone’s Time’.
While the title of this post may suggest otherwise, I do think Don’t Repeat Yourself is a good design principle. I have seen this violated in some terrible ways: hundreds of lines duplicated while a few lines are modified. It’s obvious that this is problematic and will end badly. However, as with most sofware design principles, taking Don’t Repeat Yourself to the extreme will cause a different set of problems. In order to remove duplication we often reach for inheritance even though the business logic doesn’t really fit that abtraction.
The Wrong Abstraction
Sandy Metz illustrates this really well in her talk titled ‘All the Little Things’ - the main theme being that duplication is far cheaper than the wrong abstraction.
“Duplication is far cheaper than the wrong abstraction” - Sandy Metz, RailsConf 2014.
This does fly in the face of conventional wisdom, especially since we’ve had DRY beaten into us so many times. However, quite often the first time a piece of code is duplicated the abstraction is not obvious. Instead, waiting for another consumer to use the same piece of logic can often make it obvious what the correct abstraction looks like. At the very least we will have more information, leading to a better abstraction.
Dealing With Duplication
So, if we’re willing to tolerate some duplication, how do we avoid bugs caused by code duplication? My preferred solution is to write a test that fails when the one piece of logic changes, but the other does not. This requires a test at a high enough level that the duplicate code paths can be exercised and the results compared. If a test like this doesn’t make sense it can indicate that these 2 pieces of logic will likely diverge in the future and any premature abstraction will cause more pain.
Blindly applying DRY can quickly do more harm than good. Before you create the wrong abstraction, consider whether it’s better to simply repeat yourself.