The Valuable Dev

The DRY Principle: Benefits and Costs with Examples

Don't DRY everything blindly

Once upon a time, a fearful young developer (me) wanted to write magnificent code. I was seeing generations of developers speaking about it, as we speak about pyramids two thousands years after their construction. I wanted to let my mark in the world!

Therefore, I did what I thought would be the best: avoiding every traps everybody felt into, by following the Holy Coding Principles, created by the Ones Who Have the Knowledge.

I followed them drastically. It was my religion, my cult, my way of life. After hearing about the DRY principle, my code became suddenly as DRY as the Sahara. I swear to the moon, the sky, and the twelve deities: Nothing will be repeated in my code! Ever!

This lead to many, many problems.

I understood that principles need to be well understood, by reading the books where they are defined, and not only random comment on stack overflow. I also understood that principles should be used depending on the context.

A little reminder for those in the back who don’t follow: the DRY principle means “Don’t Repeat Yourself” and was first introduced in the book The Pragmatic Programmer. The principle itself was known and applied before this book came to life; but the Pragmatic Programmer defined it precisely and coined its name.

Without waiting more, let’s dive into the wonderful lands of DRY! If you feel you want to burn this article or to cover it with praise, fill free to leave a lot of comments to increase my glory.

DRY is About Duplication of Knowledge

Even if the phrase Don’t Repeat Yourself sounds simple enough, it also sounds a too generic. In The Pragmatic Programmer, DRY is defined as

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.

It begs the question: what’s a piece of knowledge? I would define it either as:

  • A precise functionality in the business domain of your application.
  • An algorithm.

To take an overly used e-commerce examples, a shipment class and its behavior would be part of the business domain of your application. A shipment is something real your company uses to send products to their customers.

Therefore, the logic of this shipment should only appear once in the application. Why?

Imagine that you need to send shipments to a warehouse. You need to trigger this behavior in 76 different places in your application. No problem: you repeat the logic 76 times.

After a while, your boss comes to you and asks you to change this behavior. Instead of sending shipments to one warehouse, you need to send them to three different ones.

The result? You’ll spend a lot of time on your shipment logic, since you’ll have to change it in 76 different places! This is a waste of time, and a good way to produce bugs to piss your boss off.

The solution: create a single representation of your knowledge. Put the logic to send the shipment in one place and then use the representation of this knowledge anywhere you need it. For example, sending a shipment could be a method of the class Shipment you can reuse everywhere you manipulate a bunch of these shipments. Congratulations! You’ve created a new abstraction.

Let’s take another example. Imagine that you coded a fancy class to parse B-trees. This can also be considered as knowledge: it’s an algorithm which should be defined once. The representation of that knowledge should be used everywhere without repeating the knowledge itself.

DRY and Code Duplication

So DRY is all about knowledge? All about business logic?

Let’s look at some code snippets to understand why:

<?php

interface Product
{
    public function displayPrice();
}

class PlasticDuck implements Product
{
    /** @var int */
    private $price;

    public function __construct(int $price)
    {
        $this->price = $price;
    }

    public function displayPrice()
    {
        echo sprintf("The price of this plastic duck is %d euros!", $this->price);
    }
}

$plasticDuck = new PlasticDuck(2);
$plasticDuck->displayPrice();

This code doesn’t look that bad, does it?

Dave, your colleague developer, thinks it’s horrible. After looking at this code you’ve written, he comes at your desk to scream:

  1. The word price is repeated 6 times!
  2. The method displayPrice() is repeated in the interface and its implementation!

Your answer is swift:

  1. Variables (and properties) like price need to be repeated in your code. It’s not a functionality.
  2. The knowledge (displaying the price) is only present once, in the method itself.

No DRY violation here.

Dave is speechless, feeling your powerful aura illuminating the whole room.

But you attacked Dave’s expertise: he’s angry. He wants to win the argument. Soon enough, he finds another piece of code you’ve written; he comes back to your desk, slapping it in your face:

<?php

class CsvValidation
{
    public function validateProduct(array $product)
    {
        if (!isset($product['color'])) {
            throw new \Exception('Import fail: the product attribute color is missing');
        }

        if (!isset($product['size'])) {
            throw new \Exception('Import fail: the product attribute size is missing');
        }

        if (!isset($product['type'])) {
            throw new \Exception('Import fail: the product attribute type is missing');
        }
    }
}

Dave, full of himself, claims: “You little pig! This code is not DRY!”. And you, aware of what the DRY principle is really about, answer: “But the business logic, the knowledge, is still not repeated!”.

Again, you’re right. The method validates some CSV output in only one place (validateProduct()). This is the knowledge, and it’s not repeated.

But Dave is not ready to accept it! “What about all those conditionals everywhere? Those if? Isn’t it an obvious violation of the DRY principle?”

You take a deep voice to answer, pronouncing every word perfectly, your knowledge bouncing on the wall to create an infinite echo of awareness:

“Well… no. It’s not. I would call that unnecessary code duplication, but not a violation of the DRY principle”.

Suddenly, your fingers type on your keyboard, at the speed of light, the following code:

<?php

class CsvValidation
{
    private $productAttributes = [
        'color',
        'size',
        'type',
    ];

    public function validateProduct(array $product)
    {
        foreach ($this->productAttributes as $attribute) {
            if (!isset($product[$attribute])) {
                throw new \Exception(sprintf('Import fail: the product attribute %s is missing', $attribute));
            }
        }
    }
}

It looks better, does it? There’s no code duplication anymore!

To summarize:

  1. Knowledge duplication is always a DRY principle violation.
  2. Code duplication doesn’t necessarily violate the DRY principle.

Dave is still not convinced. With a serenity defying the highest spiritual masters through the ages, you give the final stroke. You take a book on your desk and read the following:

Many people took it [the DRY principle] to refer to code only: they thought that DRY means “don’t copy-and-paste lines of source.” […] DRY is about the duplication of knowledge, of intent. It’s about expressing the same thing in two different places, possibly in two totally different ways.

This is from the 20th anniversary edition of the Pragmatic Programmer, the same book which coined the DRY principle.

DRY Everything: the Recipe for Disasters

Dangerous Generalities

Let’s take a more interesting example. Something which happened in the Real Life™.

I was working on an application for filmmakers. They could upload their movies and their metadata (title, description, cast and crew…) using the app’s user interface. This information was displayed on a VOD platform.

The architecture of the app was inspired from the MVC pattern and looked like this:

basic project filetree

The content team of the company could also use this application. They could create the movie’s metadata when the filmmakers didn’t want to do it themselves.

The filmmakers and the content team had different needs. The content team is used to work with content management systems, the filmmakers are not. Therefore, we decided to create two interfaces:

  • The first one for the content team, without guidance or explanation, where you can enter the content as fast as possible.
  • Another one for the filmmakers, with a more friendly user experience.

Here’s what we did:

basic project filetree

The controllers from the two different applications are almost the same. It’s not only about their names: their implementations, too. We basically copy-pasted them.

This looks like an obvious and ugly violation of the DRY principle: views and controller repeated all over the place! We could have created an abstraction to group the common logic, like an abstract class for example. But this would have coupled the controllers of the two different applications together.

Change the abstract class and every single of your controllers need to support the change without breaking.

In many cases, we knew that the interface would evolve differently in the future. We would have created a lot of conditionals in the controllers’ actions if we would have created one set of controller for both applications. Nobody wants a forest of if statements; the resulting code would have been way more complex.

Additionally, the controllers shouldn’t contain any business logic. If you recall the definition of the DRY principle, it’s this knowledge, this business logic which should not be duplicated.

In short, trying to apply DRY everywhere can have two results:

  1. Unnecessary coupling.
  2. Unnecessary complexity.

You don’t want any of these in your codebase.

Premature Refactoring

You shouldn’t apply the DRY principle if your business logic doesn’t have any duplication yet. Again, it depends of the context, but, as a rule of thumb, trying to apply DRY to something which is only used in one place can lead to premature generalization.

If you begin to generalize something because “it could be useful later”, please don’t. Why?

  1. You will spend time to create abstractions (abstract classes and whatnot) which might be only used in one place, forever. Business needs can change very quickly and drastically: today, everybody will tell you that you need some generalization “for the future”, and tomorrow, everybody will forget about it.
  2. Again, you will possibly introduce complexity and coupling in your code for zero benefit.

Code reuse and code duplication are two different things. DRY states that you shouldn’t duplicate knowledge, not that you should use abstractions to reuse everything, everywhere.

This is what I learned over the years: code for the specific at the beginning, and don’t try to generalize. Even if your managers would love to have 90% of your application reusable for every single use case. In practice, this is almost never possible.

Two functionalities, even if they look very similar at first glance, can become very different in the future. If you have any doubt, it’s better to copy your code and let it takes different path.

Sandy Metz said it better than me:

Duplication is far cheaper than the wrong abstraction.

Code first, make it work, and then keep in mind all these principles you know to refactor after, and only if you need it.

DRY principle violations should be handled when the knowledge is already and obviously duplicated.

Similar Domain Knowledge… ?

I’ve written above that repetition of knowledge is always a violation of the DRY principle. This only apply when the same knowledge is repeated. Let’s take an example:

<?php


/** Shipment from the warehouse to the customer */
class Shipment
{
     public $deliveryTime = 4; //in days

     public function calculateDeliveryDay(): DateTime
     {
         return new \DateTime("now +{$this->deliveryTime} day");
     }
}

/** Order return of a customer */
class OrderReturn
{
    public $returnLimit = 4; //in days

    public function calculateLastReturnDay(): DateTime
    {
         return new \DateTime("now +{$this->returnLimit} day");
    }
}

You can hear Dave, your colleague developer, gently screaming in your ears once again: “This is an obvious violation of everything I believe in! What about the DRY principle? My heart is bleeding!”.

However, Dave is again wrong. From an e-commerce perspective, the delivery time of a shipment to a customer (Shipment::calculateDeliveryDay()) has nothing to do with the last day the customer can return his ordered products (Return::calculateLastReturnDay). What appears to be a duplication of knowledge is just a pure coincidence.

What happens if you combine those two methods in one? If your company decide that the customer has now one month to return his products, you will have to split the method again. If you don’t, the shipment delivery will take one month too!

This is not the best way to please the customers.

DRY is not Only About Code

even the Gin can be DRY! Even the Gin can be DRY nowadays!

DRY is not something you should only respect in your code. You shouldn’t repeat the knowledge of your business domain in anything related to your project.

To quote Dave Thomas again:

A system’s knowledge is far broader than just its code. It refers to database schemas, test plans, the build system, even documentation.

The idea of DRY is simple in theory: you shouldn’t need to update in parallel multiple things when one change occurs. If your knowledge is repeated two times in your code and you need to change it, you might forget to change it everywhere. In your documentation, it could lead to misconceptions, confusion, and ultimately wrong implementations.

Applying DRY Depending on the Context

At the beginning of my career, I was often victim of analysis paralysis. All those principles where holding me back to be productive and efficient. It was too complex and I didn’t want to screw everything.

However, principles are not rules you must follow. They are tools for you to go in the good direction. It’s your job to adapt them depending on the situation.

Most good principles are, in fact, trade-offs: when you apply the DRY principle, you make your code easier to understand, but the scope of your changes becomes wider.

Share Your Knowledge