Over-DRY Anti-Patterns
When to Use
When deciding whether to abstract or duplicate. Over-DRY is worse than under-DRY because wrong abstractions are harder to remove than duplication.
Common Over-DRY Mistakes in Drupal
| Anti-Pattern |
What It Looks Like |
Why It's Bad |
Better Approach |
| God Service |
Service with 20+ methods, handles "everything" |
Violates Single Responsibility, hard to test, unclear dependencies |
Split into focused services by domain |
| Premature Base Class |
Shared base class created after 1 similar class |
Wrong abstraction, inflexible, hard to change |
Wait for Rule of Three, then abstract |
| Over-Configurable Plugin |
Plugin with 30+ config options covering every use case |
Complexity explosion, hard to use, hard to maintain |
Create separate plugins for distinct use cases |
| God Trait |
Trait with 15+ methods for "utilities" |
Hides complexity, unclear dependencies, namespace pollution |
Split into focused traits or use services |
| Forced Reuse |
Making unrelated things fit same abstraction |
Coupling unrelated code, breaks when requirements diverge |
Allow duplication when domains differ |
Pattern: God Service Anti-Pattern
// WRONG: God service doing everything
class SiteHelper {
public function formatDate($date) { }
public function sendEmail($to, $subject, $body) { }
public function processPayment($amount) { }
public function generatePdf($content) { }
public function validateAddress($address) { }
public function geocodeLocation($address) { }
public function calculateShipping($weight) { }
public function applyDiscount($price, $code) { }
// ... 20+ more methods
}
// RIGHT: Focused services by domain
class DateFormatter { public function format($date) { } }
class EmailService { public function send($to, $subject, $body) { } }
class PaymentProcessor { public function process($amount) { } }
class PdfGenerator { public function generate($content) { } }
class AddressValidator { public function validate($address) { } }
class GeocodingService { public function geocode($address) { } }
class ShippingCalculator { public function calculate($weight) { } }
class DiscountService { public function apply($price, $code) { } }
Pattern: Premature Base Class
// WRONG: Base class created after 1 similar class
abstract class MyProjectFormBase extends FormBase {
// Shared code that only applies to ONE form so far
protected function validateCustomField($value) { }
}
class MyForm extends MyProjectFormBase { }
// RIGHT: Wait until you have 3+ forms needing same code
// Keep validation in the form until duplication appears elsewhere
class MyForm extends FormBase {
protected function validateCustomField($value) { }
}
// Later, when you have 3 forms with same validation...
abstract class MyProjectFormBase extends FormBase {
protected function validateCustomField($value) { }
}
Decision Framework: When to Abstract
| Signals to Abstract |
Signals to Keep Duplicate |
| Exact same logic in 3+ places (Rule of Three) |
Similar code but different reasons (knowledge domains differ) |
| Changes to logic require updating all copies |
Code looks similar but is changing independently |
| Logic represents same business rule |
Logic serves different features with different stakeholders |
| High cost of bugs from inconsistency |
Low coupling between instances |
Common Mistakes
- Abstracting at first duplication — Wait for Rule of Three (3 instances before abstracting)
- Making unrelated code share abstraction — Accidental duplication (similar code, different knowledge) is acceptable
- Creating "flexible" config to avoid creating new classes — Results in complex, hard-to-use abstractions
- Using inheritance when composition would work — Favor services (composition) over base classes (inheritance)
- Assuming all duplication is bad — Some duplication is healthy; eliminates coupling
See Also