What is the DRY principle?

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system. - by Andrew Hunt and David Thomas

These words in the subtitle refer to DRY (Dont repeat yourself). But DRY is not just about not repeating code that looks the same, it is about knowledge within the system.

This raises an important question: what is a piece of knowledge? We can define it as being:

  • A unique feature in the business rule (in the domain) of your software.

  • An algorithm: Just remembering that an algorithm is a finite set of clear steps that are applied systematically (in an orderly fashion) until a solution is reached. Simply put, we can say that an algorithm defines the path that must be followed to reach the solution to a given problem.

Let's start with what is called the knowledge piece of the system. Let's imagine that a developer needs to create a feature that validates that the user name is between 10 and 200 characters long. The developer ends up creating the validation, but carelessly does not create a class that contains only the responsibility to validate the user's name. What can happen? Another developer that needs to use this validation in another component of the software ends up replicating that same logic, which we can call knowledge, in another class, that needs to validate a username as well. Now two classes have duplicated knowledge and know how to validate a username. This is a clear violation of the DRY principle. The system will continue to have duplicates of unnecessary logic until someone creates a unique representation of that knowledge within the system, which in this case is about the Name. See below for an example of what a correct way to centralize this knowledge would look like:

import { left, right, Either } from '@/shared'
import { InvalidNameError } from '@/entities/errors'

export class Name {
  public readonly value: string

  private constructor (name: string) {
    this.value = name
  }

  public static create (name: string): Either<InvalidNameError, Name> {
    if (!Name.validate(name)) {
      return left(new InvalidNameError(name))
    }

    return right(new Name(name))
  }

  public static validate (name: string): boolean {
    if (!name) {
      return false
    }

    if (name.trim().length < 10 || name.trim().length > 200) {
      return false
    }

    return true
  }
}

We are not always violating the DRY

Here is one more example. Imagine that we have this code:

interface Product {
  color: string
  size: string
  type: string
}

class CsvValidation
{
     public validateProduct(product: Product)
    {
        if (!product.color) {
            throw new Error('Import fail: the product attribute color is missing');
        }

        if (!product.size) {
            throw new Error('Import fail: the product attribute size is missing');
        }

        if (!product.type) {
            throw new Error('Import fail: the product attribute type is missing');
        }
    }
}

The method validates some CSV output in only one place validateProduct(). This is the know and never repeat.

The if-else conditions repeating, are not a violation of the principle, because the business logic will not repeat anywhere else in the system. We can call this unnecessary code duplication, syntax that does not duplicate knowledge or system concepts. In trying to identify duplication, we can ask ourselves:

"Are we looking at syntax duplication or knowledge duplication?" - Antonio Sciamanna.

In the case we have repetition of the conditional if structure. And of Error release as well. BUT THE KNOWLEDGE IS NOT DUPLICATED.

We can improve a lot our code, its structure, with a refactoring:

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

    public validateProduct(product: Product)
    {
        for (const attribute of this.productAttributes) {
            if (!product) {
                throw Error(`Import fail: the product attribute is missing ${attribute}`);
            }
        }
    }
}

No more code duplication! And what can we learn? First, we have seen that duplicate knowledge clearly violates the Dont Repeat Yourself principle. Second lesson, having duplicate code does not necessarily violate the DRY principle.

Unambiguous

In the sentence that begins this article, we have the word Unequivocal. Let's try to get deeper into it. The word Unequivocal means according to dictionaries, something that allows no doubt, mistake, error. So we also have a warning. We must be careful not to let anything that is part of the knowledge within the system end up becoming doubt for other developers and even lead others down the wrong path. We should not only be concerned with avoiding duplication of knowledge, but it is also our obligation to be concerned with the clarity that our code is written. Here is an example:

class DtaRcrd102 {
 private Date genymdhms;
 private Date modymdhms;
}

if (student.classes.length < 7) {
   // Do something
}

Note that in the above codes we have some problems, even if they are as simple as possible. In the first example it is impossible to understand what DtaRcrd102 is about and genymdhms, modymdhms? In the second example what does the number 7 represent in this conditional? If I change this number to 8, 9. Will this affect the business rule in any way?

But you might be thinking: But this is about clean code, not DRY!

Yes, these concepts were covered in Robert Martin's book, but they are linked to Dont Repeat Yourself! What is the point of being obsessed with having as little duplication of knowledge or code as possible, when the biggest problem is in a developer looking at a reusable component that you hastily built and not being able to understand the logic, even the variable names inside the class?

That's why this piece of knowledge, allows for no doubt! It must be clear what that knowledge is about in your component.

// Exemplos refatorados
if (student.classes.length < MAX_CLASSES_PER_STUDENT) {
    // Do something
}

class Customer {
 private Date generationTimestamp;
 private Date modificationTimestamp;
}

It is always worth remembering the words of Martin Fowler in the book Refactoring: Improving the design of existing code:

" One of the most important parts of clear code are good names, so we should think carefully when naming functions, modules, variables, and classes, so that they clearly communicate what they do and how they are used "

Warnings about Premature Refactoring

You should not apply the DRY principle if your business logic has no duplication yet! Obviously it depends a lot on the context but, as a general rule, trying to apply DRY to something that is only used in one place can lead to premature generalization. See what can happen if every implementation you already implement the principle:

You will possibly introduce complexity and coupling into your code which will bring no benefit!

You will spend time creating abstractions (abstract classes) that can be used only in one place, forever. Business needs can change drastically: today, everyone will tell you that you need some generalization "for the future", and tomorrow, everyone will forget about it. Remember that changes occur all the time.

Reuse and code duplication are two different things. DRY says that you should not duplicate knowledge, never encourages that you should use abstractions to reuse everything and everywhere.

Two features, even if they are very similar while the software is under construction, can become very different in the future. If you are not sure what the system will look like two months from now, it is better to copy your code and let it go a different way.

A duplication of code is cheaper than creating misleading abstractions.

Violations of the DRY principle should be dealt with when the knowledge already exists and is obviously duplicated.

Conclusion

I could put many more examples in code and tell you about situations I have seen in my day to day life as a Software Developer. But the goal is to make it clear that DRY is not just about avoiding code duplication. And on that we can conclude this article with the following quote from the authors of the book The Pragmatic Programmer in the 20th anniversary edition of the book:

"Many people have taken [the DRY principle] to refer only to code: they have thought that DRY means "don't copy and paste source lines." [...] DRY is about duplication of knowledge, of intention. It's about expressing the same thing in two different places, possibly in two totally different ways."

It is important to remember that the knowledge within a piece of software should be unique!

I'm done with this article. Any questions or constructive criticism please contact me. See you next time!

ย