TL;DR
DRY is great when duplication creates maintenance risk. Like duplicating a domain logic, like a discount.
But sometimes repetition is the better design.
A useful mental model is RUG — Repeat Until Good.
Instead of prematurely extracting abstractions, allow small duplication until the design becomes obvious.
Blindly chasing DRY can lead to:
- Fragile abstractions.
- Hidden intent.
- Tight coupling between unrelated parts of the system.
Sometimes, explicit repetition makes the code clearer and more resilient.
Read the Full Thing
DRY — Don't Repeat Yourself — is one of the most widely taught principles in software engineering.
And for good reason.
When the same logic is duplicated in multiple places, changing behavior becomes risky. One fix may require updates across several locations, increasing the chance of bugs and inconsistencies. That’s where DRY shines.
But there's a less discussed idea that appears in some engineering discussions:
RUG — Repeat Until Good.
It’s not a formally established principle like DRY, and you won’t find tons of articles about it. But the idea is simple:
Sometimes repetition is the safest way to let a design evolve.
Instead of abstracting too early, you allow the code to repeat until the correct abstraction naturally emerges.
The risk of blindly applying DRY
When we try too hard to eliminate duplication, we often end up creating fragile abstractions.
You may have seen nested method calls like this:
Controller
→ Service
→ Manager
→ Helper
→ Interface
Now imagine trying to assemble all the parts in order to understand a simple behavior. And to be honest, frequently when I'm making maintenance in legacy codebases, I often find myself asking Claude to explain the whole feature—what it’s about—because the code is more of a puzzle than explicit logic.
Sometimes those abstractions exist only to remove a few repeated lines of code.
They don’t add real value to the product.
They just hide the intent somewhere else.
And in many cases, they introduce something worse:
Tight coupling.
Suddenly, multiple parts of the system depend on the same abstraction — and changing it can create unintended side effects.
When repetition is actually safer
A good example appears in request validation.
In Laravel, it's common to use Form Request classes.
class StoreUserRequest extends FormRequest
{
public function rules(): array
{
return [
'email' => ['required', 'email'],
'birth_date' => ['required', 'date', 'before:today'],
];
}
}
Now imagine you have another request with a very similar validation.
class UpdateUserProfileRequest extends FormRequest
{
public function rules(): array
{
return [
'email' => ['required', 'email'],
'birth_date' => ['required', 'date', 'before:today'],
];
}
}
Yes — there is duplication.
But extracting a shared abstraction for this might actually increase coupling between unrelated requests.
If you change validation in one request, you probably don’t want to affect all the others.
Keeping them separate means:
- Changes stay localized
- Behavior is explicit
- The system becomes more resilient
Sometimes repetition protects isolation.
A practical rule of thumb
Use DRY when dealing with domain or business logic.
Use RUG in scaffolding code when repetition:
- Keeps logic explicit
- Avoids unnecessary coupling
- Makes the code easier to understand
The key is recognizing that not all duplication is bad.
Sometimes the best abstraction is the one that doesn’t exist yet.
To finish this thought, I always remember an idea that stuck with me while I was growing as a decision-maker in my roles:
Good code is the code that is easy to throw away.
Not because we want to delete it. But when code is simple, explicit, and loosely coupled, it becomes safe to change, safe to replace, and safe to evolve.
Top comments (0)