DRY - Don't Repeat Yourself
Definition

Code Duplication
// BAD: Copy-paste code
public class OrderService {
public double calculateOrderTotal(Order order) {
double subtotal = 0;
for (OrderItem item : order.getItems()) {
subtotal += item.getPrice() * item.getQuantity();
}
double tax = subtotal * 0.08; // 8% tax
double shipping = subtotal > 100 ? 0 : 10; // Free shipping over $100
return subtotal + tax + shipping;
}
public double calculateQuoteTotal(Quote quote) {
double subtotal = 0;
for (QuoteItem item : quote.getItems()) {
subtotal += item.getPrice() * item.getQuantity(); // Same logic!
}
double tax = subtotal * 0.08; // Same tax!
double shipping = subtotal > 100 ? 0 : 10; // Same shipping!
return subtotal + tax + shipping;
}
}
// GOOD: Extract common logic
public class PricingService {
public Money calculateSubtotal(List<LineItem> items) {
return items.stream()
.map(item -> item.getPrice().multiply(item.getQuantity()))
.reduce(Money.ZERO, Money::add);
}
public Money calculateTax(Money subtotal) {
return subtotal.multiply(TaxConfig.RATE); // Single source of truth
}
public Money calculateShipping(Money subtotal) {
return subtotal.isGreaterThan(ShippingConfig.FREE_THRESHOLD)
? Money.ZERO
: ShippingConfig.STANDARD_RATE;
}
public Money calculateTotal(List<LineItem> items) {
Money subtotal = calculateSubtotal(items);
return subtotal
.add(calculateTax(subtotal))
.add(calculateShipping(subtotal));
}
}
Knowledge Duplication
// BAD: Validation rules duplicated
public class UserController {
public void createUser(UserRequest request) {
if (request.getEmail() == null ||
!request.getEmail().matches("^[\\w.-]+@[\\w.-]+\\.\\w+$")) {
throw new ValidationException("Invalid email");
}
if (request.getPassword().length() < 8) {
throw new ValidationException("Password too short");
}
// ...
}
}
public class UserService {
public void updateEmail(String userId, String newEmail) {
// Same validation repeated!
if (newEmail == null ||
!newEmail.matches("^[\\w.-]+@[\\w.-]+\\.\\w+$")) {
throw new ValidationException("Invalid email");
}
// ...
}
}
// GOOD: Centralized validation knowledge
public class Email { // Value object
private static final Pattern PATTERN =
Pattern.compile("^[\\w.-]+@[\\w.-]+\\.\\w+$");
private final String value;
public Email(String value) {
if (value == null || !PATTERN.matcher(value).matches()) {
throw new InvalidEmailException(value);
}
this.value = value.toLowerCase();
}
}
public class Password {
private static final int MIN_LENGTH = 8;
public Password(String value) {
if (value == null || value.length() < MIN_LENGTH) {
throw new WeakPasswordException();
}
// ...
}
}
// Now validation is automatic
public void createUser(Email email, Password password) { }
public void updateEmail(UserId userId, Email newEmail) { }
Data Duplication

When DRY Doesn't Apply
// NOT duplication - Different knowledge!
// These look similar but serve different purposes
// Business rule: Cart discount
public Money calculateCartDiscount(Cart cart) {
if (cart.getTotal().isGreaterThan(Money.of(100))) {
return cart.getTotal().multiply(0.10); // 10% off
}
return Money.ZERO;
}
// Business rule: Loyalty reward
public Money calculateLoyaltyBonus(Customer customer) {
if (customer.getTotalPurchases().isGreaterThan(Money.of(100))) {
return Money.of(10); // $10 reward
}
return Money.ZERO;
}
// These both check "$100 threshold" but:
// - They represent DIFFERENT business rules
// - They might change independently
// - Forcing them together creates wrong coupling
// RULE: If two pieces of code might change for
// DIFFERENT REASONS, they're not duplicates!
DRY at Different Levels

Tips & Tricks
