I was recently working on a project with a developer who held a very dogmatic view of enforcing the DRY principle (Don’t Repeat Yourself). Personally I think perfectly DRY code is brittle, hard to understand, and hard to maintain. I’m certainly not the first to suggest this, but I prefer repetition to abstraction in many cases.
At what point does DRY help with creating good architectural patterns? First I think it is important to consider that using the term “architectural pattern” indicates a certain level of repetition. Patterns are good! If all code was entirely unique, all code would have to be learned an understood on its own. Instead if code follows very similar patterns, learning it in one place, leads to understanding in all of its other cases. It is less useful to abstract code so that a pattern is only used once. At that point it ceases to be much of a pattern.
A DRY zealot is going to produce functions or classes that require vast amounts of understanding to know what that function or class can do. This is because in order to use a particular logic pattern, a function or class must account for all of its various use cases. With overDRYing, options and arguments will abound, and responsibility and complexity will increase over time.
Thus I think in order for the DRY principle to be useful it must be more broadly considered. While I think abstracting sound patterns and understandable code is a poor use of DRY, I think that architecting code such that business logic only occurs in one place is important. If a behavior of your product changes, you want to be able to address that behavior in a single location, and not in all of the places you can remember across the app. This means if you are regularly modifying data to display it a particular way, make sure you’re doing that in one place, but apply DRY to the business logic itself, and not to how the business logic is being modified.
For instance you might find that you are consistently filtering a list of products a particular way in various places of your app. It would be best to make sure this is done in one place in code. However if you also find that you are filtering your users in a similar way, it may not be and is probably not in your best interest to make a generic filter that handles both products and users, as when the use case for either products or users change, your DRYness becomes an issue. Not to mention once you’ve added five other entities to the same generic filter solution, changes to the filter may have broad, potentially difficult to fully understand, implications across your app instead of on a singular entity.
Finally I think one of the more important aspects of DRY to consider is that you should only use it if you find you are already repeating, or are about to repeat, yourself. Spending time and energy pre-DRYing code is inefficient and destined for refactoring. It is much harder to guess at future use cases with perfect code than it is to handle them with code that is easy to modify. If I’ve learned anything in years of development, it is that I’m wrong a lot, and it is easier to plan for that than to improve my ability to understand the future.
Simply put: Don’t DRY until you repeat yourself. aka: DDRYUYRY.