In software, the “DRY” principle stands for “Don’t Repeat Yourself” and is an adage about deduplicating code. (The contrasting acronym is WET for Write Everything Twice.)

DRY is a helpful reminder to avoid the Copy+Paste+Tweak coding anti-pattern which leads to very similar, slightly different code throughout our application. It reminds us to create abstractions for frequently used ideas.

However, we have found over the past few decades of software development that centralization (in organization structure and code) creates bottlenecks. DRY centralizes, and centralization does not always suit.

Domain vs Helper

Domain code is the stuff that matters to our customers and product owners. It’s the stuff that defines the business rules; it’s the value-add of our application. The important stuff.

In addition to domain concepts like business rules, we have a lot of “plumbing” code. We sometimes call this helper code. It’s code that transforms between data formats, handles errors, translates text, etc. It’s a necessary evil – we can’t work without it, but if there was none of it, that would be fine.

DRY your Domain liberally

In Domain Driven Design, we endeavor to have a canonical place in the codebase where each important domain concept is described.

We DRY our domain code by doing things like:

We want our domain code DRY so that when we need to support new behavior, there is a canonical place to change the behavior.

If two domain concepts are unrelated but happen to have some similar math or similar data, we should not DRY them up and merge them together. In the real world they are independent and vary independently, and our code should have the same characteristics. To DRY those incidentally duplicated parts of our code would be to make our code harder to change, because we’ll need to change one usage of the code without changing the other usage. Unhelpful DRYing leaves our code SCORCHED (see below).

DRY your helpers judiciously

In the course of writing our domain code, we have to shore it up with helper code. If we insist on DRYing the helper code, we are likely to couple unrelated domain concepts together.

Does that mean we must never share helper code? Surely not! For example, every time you import an off the shelf library for a generic task, that’s helper code. If you use it throughout your codebase, that’s shared helper code. And then, sometimes a library doesn’t exist, so you write your own. That’s shared helper code, and it’s normal and useful. The principle here is not “zero shared helper code”.

But what can happen in a project is that helper code is shared code by default. It’s all public. This leads to “SCORCHED” code (see below).

As a rule of thumb, you’ll do well to start all helper code as local, private, and bespoke. When you discover a need for some shared utility, then extract the local, private, bespoke helper as a shared utility. Think of this like publishing a library – except you’re only “publishing” it within your codebase. You consider it to be mature and valuable enough to be worth the coupling cost, so you make it a shared helper.

But if every helper is a shared helper (out of dedication to DRY), we can end up with code so DRY that it’s SCORCHED.

So DRY it’s SCORCHED

If you over-DRY your helper code, it will end up SCORCHED.

DRY your ideas, not your code

It can be helpful to frame DRY as a tool for clarifying our ideas, not a tool for compressing our code. Let there be a single place in our code where each important idea is expressed (keep the ideas DRY). Accept duplication in the helper code that supports those independent ideas – accept WET code if it keeps the important ideas clear and independent.

Don’t Insist on DRY

When we constrain ourselves to repeat nothing, we have to give up other more important goals. While DRY is one useful principle, it is not always helpful. If we insist that our code is always DRY, we’re apt to make it SCORCHED.