Fundamentals 10 min read

Rethinking DRY: Eliminate Knowledge Duplication, Not Just Similar Code

The article clarifies that the DRY principle targets hidden knowledge duplication rather than superficial code similarity, explains why over‑abstraction can be harmful, and provides concrete Java examples of both violations and correct applications, while also discussing how to balance DRY with YAGNI.

Programmer1970
Programmer1970
Programmer1970
Rethinking DRY: Eliminate Knowledge Duplication, Not Just Similar Code

DRY (Don’t Repeat Yourself) was introduced by Andy Hunt and Dave Thomas in 1999 to combat hidden duplication of knowledge, not merely to shorten code.

1. Meaning of DRY: Knowledge ≠ Code

Understanding DRY as “don’t write duplicate code” is a dangerous simplification. The core is “knowledge” – business rules, algorithm logic, configuration strategies, state‑transition conditions, etc. Superficially similar code does not necessarily represent the same knowledge.

If two places repeat the same business rule (e.g., “users must be at least 18 years old to register”), the rule should be defined once.

If two places only happen to look alike (e.g., both contain if (list != null && !list.isEmpty())), forcing them together creates coupling.

The goal of DRY is that a requirement change requires modification in only one place.

2. Counter‑example: Violating DRY in Java

Three services each embed the “age ≥ 18” rule with different messages, illustrating knowledge duplication. Changing the legal adult age would require edits in all three locations, a typical bug‑prone scenario.

public void registerUser(User user) {
    if (user.getAge() < 18) {
        throw new IllegalArgumentException("年龄必须大于等于18岁");
    }
    // ...
}
public void createOrder(Order order) {
    if (order.getUser().getAge() < 18) {
        throw new IllegalStateException("未成年用户不能下单");
    }
    // ...
}
public boolean isEligibleForPromotion(User user) {
    return user.getAge() >= 18; // same logic, different wording
}

This is a classic case of “knowledge duplication” that violates DRY.

3. Proper Application: Centralising the Rule

public class AgePolicy {
    public static final int MIN_AGE_FOR_REGISTRATION = 18;

    public static boolean isAdult(int age) {
        return age >= MIN_AGE_FOR_REGISTRATION;
    }

    public static void validateAdult(int age) {
        if (!isAdult(age)) {
            throw new IllegalArgumentException("用户必须年满 " + MIN_AGE_FOR_REGISTRATION + " 岁");
        }
    }
}
...
public void registerUser(User user) {
    AgePolicy.validateAdult(user.getAge());
}
public void createOrder(Order order) {
    AgePolicy.validateAdult(order.getUser().getAge());
}

Advantages:

Business rule is managed centrally; a change requires only one edit.

Method names clearly express intent.

Constants carry business semantics instead of magic numbers.

4. Beware of “pseudo‑DRY”: Over‑abstraction Pitfalls

Treating visual repetition as knowledge duplication leads to unnecessary abstraction. A generic validator that tries to handle unrelated checks becomes a “catch‑all toolbox” with vague responsibilities and loses context‑specific error messages.

public class GenericValidator {
    public static <T> void validateNotNull(T obj, String name) {
        if (obj == null) throw new IllegalArgumentException(name + " 不能为空");
    }
    public static void validateListNotEmpty(List<?> list, String name) {
        validateNotNull(list, name);
        if (list.isEmpty()) throw new IllegalArgumentException(name + " 不能为空列表");
    }
}
...
public class UserValidator {
    public void validate(User user) {
        GenericValidator.validateNotNull(user.getName(), "姓名");
        GenericValidator.validateListNotEmpty(user.getRoles(), "角色列表");
    }
}
...
public class OrderValidator {
    public void validate(Order order) {
        GenericValidator.validateNotNull(order.getId(), "订单ID");
        GenericValidator.validateListNotEmpty(order.getItems(), "商品列表");
    }
}

Problems:

The validator becomes a “universal toolbox” with blurred responsibilities.

Error messages lose specific business context.

User and Order validation have no real commonality; forcing a single abstraction hides their distinct semantics.

A better approach is to keep “benign repetition” where each validator expresses its own business meaning.

5. Complex Scenario: Combining DRY with Strategy or Configuration

public interface ShippingRateCalculator {
    BigDecimal calculateShippingCost(Order order);
}
@Component
public class DomesticShippingCalculator implements ShippingRateCalculator {
    @Override
    public BigDecimal calculateShippingCost(Order order) {
        return applyWeightBasedRate(order.getTotalWeight());
    }
}
@Component
public class InternationalShippingCalculator implements ShippingRateCalculator {
    private final DomesticShippingCalculator domesticCalc;
    public InternationalShippingCalculator(DomesticShippingCalculator domesticCalc) {
        this.domesticCalc = domesticCalc;
    }
    @Override
    public BigDecimal calculateShippingCost(Order order) {
        return domesticCalc.calculateShippingCost(order).add(new BigDecimal("20.00"));
    }
}

Here the domestic shipping logic is shared knowledge reused via dependency injection, avoiding duplication while remaining extensible.

6. Balancing DRY and YAGNI

DRY often conflicts with YAGNI (You Aren’t Gonna Need It). Over‑early abstraction violates YAGNI, while allowing unnecessary duplication violates DRY.

Accept a small amount of duplication until the same rule appears two or three times, then refactor.

Ask yourself, “If this rule changes, how many places need updating?” If the answer is greater than one, it’s time to DRY.

Prefer extracting methods rather than classes or interfaces; method‑level reuse has the lowest cost.

7. DRY Is a Means, Not an End

The ultimate goal of DRY is to reduce system cognitive load and change cost, not to minimise line count. This means ensuring a single authoritative source for true business knowledge, tolerating coincidental code similarity, and using clear naming to let each line tell its own story.

Remember: Duplicate code fragments ≠ duplicate knowledge.
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javasoftware designcode reuseYAGNIDRYknowledge duplication
Programmer1970
Written by

Programmer1970

Formerly called 'Code to 35'. Add our main WeChat ID to access a wealth of shared resources (algorithms, interview prep, tech stacks: Java, Python, Go, big data). We mainly share serious development techniques, focusing on output-driven input. Occasionally we post life snippets and gossip. Our aim is to attract precise traffic and test advertising opportunities.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.