Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions _rules/0125.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,39 @@
---
rule_id: 0125
rule_category: general
title: Don't Repeat Yourself (DRY) within boundaries
title: Don't Repeat Yourself (DRY), but only within boundaries
severity: 1
---
Avoid duplicating logic, knowledge or decisions within a component, service or [bounded context](https://martinfowler.com/bliki/BoundedContext.html). When the same logic exists in multiple places, a change in requirements requires multiple edits, and inconsistencies creep in over time. Use the [Rule of Three](https://en.wikipedia.org/wiki/Rule_of_three_(computer_programming)) as a practical guide: refactor duplication after the third occurrence.
Avoid duplicating logic, knowledge or decisions within a component, service or [bounded context](https://martinfowler.com/bliki/BoundedContext.html). When the same logic exists in multiple places, a change in requirements requires multiple edits, and inconsistencies creep in over time.

**Exception:**
Duplication across architectural or team boundaries is sometimes preferable to coupling. If sharing a piece of logic between two separate modules or services would require introducing a shared dependency, consider whether duplicating the simple logic is the lesser evil. Tight coupling across boundaries is harder to manage than a small amount of redundancy.
However, although the DRY principle (Don't Repeat Yourself) is valuable within such a boundary, blindly applying it across module or service boundaries can introduce coupling that is harder to manage than a small amount of duplication.

If sharing a tiny piece of logic between two separate modules would require:
- Adding a dependency on a shared library that both modules must coordinate upgrades for.
- Exposing internal types or contracts across a boundary.
- Introducing a third project or package just to hold a few lines of code.

…then duplicating the logic is often the simpler choice.

```csharp
// In Module A
private static bool IsValidEmail(string value)
=> value.Contains('@') && value.Contains('.');

// In Module B — a small duplication is fine here
private static bool IsValidEmail(string value)
=> value.Contains('@') && value.Contains('.');
```

This applies primarily to:
- Small utility functions (a few lines of code).
- Logic that is stable and unlikely to change frequently.
- Logic that doesn't carry domain meaning that must remain consistent.

For complex or domain-critical logic that must be consistent everywhere, a shared library is still the right choice. Consider the [Rule of Three](https://en.wikipedia.org/wiki/Rule_of_three_(computer_programming)) as a practical guide: refactor duplication after the third occurrence.

**Note:**
Taking a dependency on a source-only package like [Reflectify](https://github.com/dennisdoomen/reflectify?tab=readme-ov-file#readme) or [Pathy](https://github.com/dennisdoomen/pathy?tab=readme-ov-file#readme), or putting your utility code in a source-only package also avoids coordinated updates. But duplication may still be the safer choice.

**Exception:**
Duplication in tests is often beneficial as it will make the tests easier to understand without the need to dig into all kinds of shared helper methods.