Clean Code

Embracing the DRY Principle: Best Practices for Don't Repeat Yourself in Programming

I found the same validation logic in four different controllers. Someone had fixed a bug in one of them. The other three still had the original defect. Us...

16 Apr 2024

Embracing the DRY Principle: Best Practices for Don't Repeat Yourself in Programming

I found the same validation logic in four different controllers. Someone had fixed a bug in one of them. The other three still had the original defect. Users hit the fixed path 60% of the time. The other 40% got the broken experience for months.

That's the cost of duplication. It's not just ugly code — it's divergent behavior hiding in plain sight.

What DRY means

DRY — Don't Repeat Yourself — says that every piece of knowledge should have a single, authoritative representation in your codebase. If the same logic exists in two places, it will eventually disagree with itself.

It's not about eliminating every line that looks similar. It's about eliminating every concept that's duplicated.

How to apply DRY

Spot the patterns

Look for logic that appears in multiple places: validation rules, calculations, formatting, error handling. If you fix a bug in one spot and need to remember to fix it in three others, that's a DRY violation.

Extract into reusable units

Pull the shared logic into a function, a class, or a module. Give it a clear name. Let every caller use the single source.

Typescript
// BEFORE: duplicated logic
function calculateAreaOfRectangle(width: number, height: number): number {
  return width * height;
}

function calculatePerimeterOfRectangle(width: number, height: number): number {
  return 2 * (width + height);
}

// AFTER: shared through a focused utility
class Rectangle {
  constructor(private width: number, private height: number) {}

  area(): number {
    return this.width * this.height;
  }

  perimeter(): number {
    return 2 * (this.width + this.height);
  }
}

Parameterize, don't copy-paste

When two functions look almost identical but differ by one value, extract the value as a parameter. Don't maintain two near-identical copies.

Build shared libraries

For logic used across multiple projects or services, create a shared library. Publish it. Version it. Now there's one source of truth, and updating it updates everyone.

The trade-off

DRY is powerful, but over-applied DRY is destructive.

Two pieces of code that look the same aren't always the same concept. They might evolve differently. If you force them into a shared abstraction, you create coupling — a change for one use case breaks the other.

The rule of three helps: don't abstract until you see the pattern three times. The first two occurrences might be coincidence. The third confirms a real duplication.

Also, premature DRY can make code harder to read. Sometimes a little duplication is clearer than a clever abstraction. If the shared function needs five parameters and three boolean flags to handle all its callers, you've gone too far.

DRY is about eliminating duplicated knowledge, not duplicated syntax. Know the difference.