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.