diff --git a/_rules/0125.md b/_rules/0125.md index 80f54c3..b608516 100644 --- a/_rules/0125.md +++ b/_rules/0125.md @@ -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.