Skip to content

The DRY Principle (Don't Repeat Yourself)

The DRY principle—short for Don't Repeat Yourself—is a foundational tenet in software development. Coined by Andy Hunt and Dave Thomas in The Pragmatic Programmer, DRY is best understood as a call to reduce duplication in code. At its core, the DRY principle encourages developers to express every piece of knowledge or logic within a system once and only once.

Why DRY Matters

Duplication is a common source of software decay. When the same logic or data appears in multiple places, it becomes harder to maintain. A change to one instance often requires changes to others; if one is missed, it can introduce bugs, inconsistencies, and costly regressions. Following DRY improves:

  • Maintainability: Changes are easier and safer when logic is centralized.
  • Readability: Code becomes more concise and understandable.
  • Reusability: Well-factored code components can be reused across projects or systems.
  • Testability: Modular, DRY code is easier to test in isolation.

Recognizing Duplication

Duplication in software isn't limited to identical lines of code. It includes:

  • Logic Duplication: Repeating the same calculations or conditional structures.
  • Structural Duplication: Replicating function bodies, class definitions, or data structures.
  • Semantic Duplication: Expressing the same idea or concept multiple ways in different parts of the codebase.
  • Configuration Duplication: Hard-coding values or repeating settings across modules.

It is important to understand that not all repetition is harmful. Sometimes, attempting to eliminate minor or coincidental duplication can lead to over-generalization and reduced clarity. The key is to identify meaningful duplication—cases where the same logic or concept is repeated with only superficial differences.

Applying DRY in Python

Python offers several tools and constructs to help enforce the DRY principle. Here are some illustrative examples.

Using Functions to Eliminate Logic Duplication

Before DRY:

def calculate_discounted_price(price):
    if price > 100:
        price -= price * 0.1
    return price

def calculate_discounted_shipping(shipping_cost):
    if shipping_cost > 100:
        shipping_cost -= shipping_cost * 0.1
    return shipping_cost

After Applying DRY:

def apply_discount(amount, threshold=100, discount_rate=0.1):
    if amount > threshold:
        amount -= amount * discount_rate
    return amount

Now the logic is centralized and changes (e.g., altering the discount rate) only need to happen in one place.

Eliminating Data Structure Duplication

Before DRY:

user = {
    "first_name": "Alice",
    "last_name": "Smith",
    "email": "alice@example.com"
}

admin = {
    "first_name": "Alice",
    "last_name": "Smith",
    "email": "alice@example.com",
    "permissions": ["read", "write", "delete"]
}

After Applying DRY with Inheritance:

class User:
    def __init__(self, first_name, last_name, email):
        self.first_name = first_name
        self.last_name = last_name
        self.email = email

class Admin(User):
    def __init__(self, first_name, last_name, email, permissions):
        super().__init__(first_name, last_name, email)
        self.permissions = permissions

This use of object-oriented principles avoids repetition and enables logical grouping of behavior.

Consolidating Repeated Logic with Decorators

def log_start_and_end(func):
    def wrapper(*args, **kwargs):
        print(f"Starting {func.__name__}")
        result = func(*args, **kwargs)
        print(f"Ending {func.__name__}")
        return result
    return wrapper

@log_start_and_end
def process_data():
    # process logic
    pass

@log_start_and_end
def generate_report():
    # report logic
    pass

Here, the decorator encapsulates common logic (logging), keeping the core functionality of the functions clean and focused.

When to Be Cautious

While DRY is a powerful guide, it must be applied judiciously. Overzealous adherence can lead to:

  • Over-abstraction: Generalizing prematurely before sufficient use cases exist.
  • Tight Coupling: Sharing logic between components that should evolve independently.
  • Reduced Readability: Abstracted code may obscure the intent if not well-named or documented.

A good heuristic is the Rule of Three: duplication is acceptable the first time and possibly even the second. Only when the pattern appears a third time should you consider extracting it.