Code Duplication vs. The Wrong Abstraction

The Cost of Premature Abstraction

Maintaining duplicated code is generally cheaper than managing a flawed abstraction because wrong abstractions introduce accidental complexity that is harder to undo than duplication is to refactor. When developers prioritize the Don't Repeat Yourself (DRY) principle over the actual needs of the domain, they often create rigid structures that force future developers to implement awkward workarounds and contortions to accommodate new requirements.

The Danger of the "Wrong Abstraction"

A wrong abstraction occurs when code is unified based on superficial similarities rather than fundamental conceptual alignment. This leads to several systemic issues:

  • Accidental Complexity: As noted by community members, bad abstractions lead to "evolutionary dead ends" where the system becomes too hard to understand because the code no longer fits the problem it is solving.
  • Rigidity: When a single abstraction serves multiple callers with diverging needs, the abstraction often begins to require numerous flags or custom parameters to handle edge cases. This is a primary indicator of a failing abstraction.
  • Maintenance Burden: Refactoring a bad abstraction often requires more effort than removing duplication. One contributor noted that the only way to escape a mess created by strict adherence to DRY was through "widespread code duplication."

Strategies for Balanced Engineering

Effective software engineering requires finding a balance between over-engineering and excessive duplication, rather than dogmatically following a single rule. The goal is to maintain a codebase that is flexible and easy to reason about.

The Rule of Three

Many engineers advocate for the "Rule of Three" (or the "Three Strikes and You Refactor" rule). This approach suggests that duplication is acceptable for two instances, but once a pattern repeats a third time, it provides enough evidence of a a genuine commonality to justify an abstraction.

Distinguishing Accidental vs. Real Duplication

Not all duplication is created equal. It is critical to distinguish between:

  • Real Duplication: When the same logic (e.g., a physics formula) is used in multiple places; this should generally be abstracted.
  • Accidental Duplication: When two different business rules happen to use the same formula today but may evolve independently. Abstracting these creates a "long-distance coupling" where a change for one customer unexpectedly breaks another.

Abstracting for Replacement, Not Reuse

One high-leverage strategy is to abstract specifically for the parts of the system most likely to change. Instead of abstracting to reduce line count, focus on creating thin layers that allow for the replacement of a component (e.g., a JSON parser) without impacting the rest of the system.

Counterpoints and Modern Considerations

While duplication is often safer than wrong abstractions, it can become a liability at scale or when using automated tools.

The Scale Threshold

Some argue that duplication becomes prohibitively expensive once a project reaches a certain scale (e.g., between five and a hundred customers). At this scale, maintaining duplicated logic across dozens of locations can burn through developer resources and increase the risk of inconsistent updates.

The Impact of LLMs on the Cost Curve

Large Language Models (LLMs) are changing the cost-benefit analysis of duplication:

  • Lowering Maintenance Cost: LLMs are highly efficient at noticing inconsistencies and applying the same modification across multiple duplicated patterns, which traditionally was a manual and error-prone process for humans.
  • Increasing Risk of Inconsistency: Conversely, some warn that LLMs cannot be trusted to perform the exact same modification every time, potentially introducing subtle bugs into a duplicated codebase that a single abstraction would have prevented.

The "Single Source of Truth" Principle

For logic where divergence would constitute a bug, the "single source of truth" principle should override the preference for duplication. If two pieces of code must remain identical to function correctly, they must be abstracted to avoid invisible coupling and future regressions.

Sources