Don't Repeat Yourself (DRY) Principle ๐
Overview ๐ฏโ
The Don't Repeat Yourself (DRY) principle states that "every piece of knowledge must have a single, unambiguous, authoritative representation within a system." This principle aims to reduce code duplication and improve maintainability.
Real-World Analogy ๐โ
Think of a restaurant's recipe book:
- Each recipe is written once in a central cookbook
- Chefs reference the same recipe rather than having their own copies
- When the recipe needs updating, it's changed in one place
- All chefs automatically work with the updated version
- No risk of different chefs using different versions
Key Concepts ๐โ
Let's visualize the core concepts with a Mermaid diagram:
Core Componentsโ
-
Knowledge Representation ๐
- Business rules
- Algorithms
- Data structures
- Configuration
-
Single Source of Truth ๐ฏ
- Centralized definitions
- Shared libraries
- Common utilities
- Unified configurations
-
Code Organization ๐
- Modular design
- Reusable components
- Shared utilities
- Common interfaces
Implementation โ๏ธโ
Here's a practical example showing both violation of DRY and its correct implementation:
- Java
- Go
// Bad Example - Violating DRY
class OrderProcessor {
public double calculateOrderTotal(Order order) {
double total = 0;
for (Item item : order.getItems()) {
total += item.getPrice();
}
double tax = total * 0.1; // 10% tax
return total + tax;
}
}
class InvoiceGenerator {
public double calculateInvoiceAmount(Order order) {
double total = 0;
for (Item item : order.getItems()) {
total += item.getPrice();
}
double tax = total * 0.1; // Same 10% tax calculation duplicated
return total + tax;
}
}
// Good Example - Following DRY
class PriceCalculator {
private static final double TAX_RATE = 0.1;
public double calculateTotalWithTax(double subtotal) {
return subtotal + (subtotal * TAX_RATE);
}
public double calculateSubtotal(List<Item> items) {
return items.stream()
.mapToDouble(Item::getPrice)
.sum();
}
}
class OrderProcessor {
private final PriceCalculator calculator;
public OrderProcessor(PriceCalculator calculator) {
this.calculator = calculator;
}
public double calculateOrderTotal(Order order) {
double subtotal = calculator.calculateSubtotal(order.getItems());
return calculator.calculateTotalWithTax(subtotal);
}
}
class InvoiceGenerator {
private final PriceCalculator calculator;
public InvoiceGenerator(PriceCalculator calculator) {
this.calculator = calculator;
}
public double calculateInvoiceAmount(Order order) {
double subtotal = calculator.calculateSubtotal(order.getItems());
return calculator.calculateTotalWithTax(subtotal);
}
}
// Bad Example - Violating DRY
type OrderProcessor struct{}
func (op *OrderProcessor) CalculateOrderTotal(order Order) float64 {
var total float64
for _, item := range order.Items {
total += item.Price
}
tax := total * 0.1 // 10% tax
return total + tax
}
type InvoiceGenerator struct{}
func (ig *InvoiceGenerator) CalculateInvoiceAmount(order Order) float64 {
var total float64
for _, item := range order.Items {
total += item.Price
}
tax := total * 0.1 // Same 10% tax calculation duplicated
return total + tax
}
// Good Example - Following DRY
const taxRate = 0.1
type PriceCalculator struct{}
func (pc *PriceCalculator) CalculateTotalWithTax(subtotal float64) float64 {
return subtotal + (subtotal * taxRate)
}
func (pc *PriceCalculator) CalculateSubtotal(items []Item) float64 {
var subtotal float64
for _, item := range items {
subtotal += item.Price
}
return subtotal
}
type OrderProcessor struct {
calculator *PriceCalculator
}
func NewOrderProcessor(calculator *PriceCalculator) *OrderProcessor {
return &OrderProcessor{calculator: calculator}
}
func (op *OrderProcessor) CalculateOrderTotal(order Order) float64 {
subtotal := op.calculator.CalculateSubtotal(order.Items)
return op.calculator.CalculateTotalWithTax(subtotal)
}
type InvoiceGenerator struct {
calculator *PriceCalculator
}
func NewInvoiceGenerator(calculator *PriceCalculator) *InvoiceGenerator {
return &InvoiceGenerator{calculator: calculator}
}
func (ig *InvoiceGenerator) CalculateInvoiceAmount(order Order) float64 {
subtotal := ig.calculator.CalculateSubtotal(order.Items)
return ig.calculator.CalculateTotalWithTax(subtotal)
}
Related Patterns ๐คโ
-
Template Method Pattern
- Reduces duplication in similar algorithms
- Centralizes common logic
- Allows specific customizations
-
Strategy Pattern
- Eliminates duplicate decision logic
- Centralizes algorithm implementations
- Enables reuse across contexts
-
Abstract Factory Pattern
- Centralizes object creation logic
- Reduces duplicate construction code
- Maintains consistency
Best Practices ๐โ
Design & Implementation โ๏ธโ
- Extract common code into utility classes
- Use inheritance and composition effectively
- Implement shared libraries
- Create reusable components
- Maintain a single source of truth
Testing ๐งชโ
- Create reusable test fixtures
- Share test utilities
- Use test case inheritance
- Implement parametrized tests
- Centralize test configurations
Monitoring ๐โ
- Centralize logging configuration
- Use shared metrics collectors
- Implement common monitoring patterns
- Create reusable dashboards
Common Pitfalls โ ๏ธโ
-
Over-Abstraction
- Problem: Creating unnecessary abstractions
- Solution: Balance DRY with pragmatism
-
False Duplication
- Problem: Forcing unification of coincidentally similar code
- Solution: Identify true knowledge duplication
-
Tight Coupling
- Problem: Creating dependencies to avoid duplication
- Solution: Use proper abstraction levels
-
Premature Abstraction
- Problem: Abstracting too early
- Solution: Wait for clear patterns to emerge
Use Cases ๐ผโ
1. Form Validation Systemโ
- Scenario: Web application forms
- Implementation:
- Centralized validation rules
- Shared validation functions
- Common error messages
- Reusable form components
2. Report Generationโ
- Scenario: Business reporting system
- Implementation:
- Common report templates
- Shared formatting logic
- Centralized calculation methods
- Reusable data transformations
3. API Integrationโ
- Scenario: Third-party service integration
- Implementation:
- Shared HTTP client
- Common authentication logic
- Centralized error handling
- Reusable data mappers
Deep Dive Topics ๐โโ๏ธโ
Thread Safety ๐โ
- Shared resource access
- State management patterns
- Synchronization strategies
- Concurrent collections
Distributed Systems ๐โ
- Configuration management
- Service discovery
- Shared libraries
- Common protocols
Performance ๐โ
- Code optimization
- Resource sharing
- Caching strategies
- Memory optimization
Additional Resources ๐โ
Books ๐โ
- "The Pragmatic Programmer" by Andy Hunt & Dave Thomas
- "Clean Code" by Robert C. Martin
- "Code Complete" by Steve McConnell
Online Resources ๐โ
Tools ๐ ๏ธโ
- SonarQube - Code duplication detection
- PMD - Copy-paste detection
- JaCoCo - Code coverage analysis
FAQs โโ
Q: How do I identify when to apply DRY?โ
A: Look for:
- Copied code blocks
- Similar algorithms
- Repeated business rules
- Duplicate configurations
Q: Is DRY always the right choice?โ
A: No, consider:
- Development speed vs maintenance
- System complexity
- Team size and expertise
- Future change likelihood
Q: How does DRY relate to microservices?โ
A: In microservices:
- Share common libraries
- Use service templates
- Centralize cross-cutting concerns
- Maintain service independence
Q: How much abstraction is too much?โ
A: Balance these factors:
- Code clarity
- Maintenance overhead
- Team understanding
- System requirements
Q: How to handle similar but not identical code?โ
A: Consider:
- Template Method pattern
- Strategy pattern
- Parameterization
- Careful abstraction