YAGNI - You Aren't Gonna Need It
Definition

Common YAGNI Violations
// VIOLATION 1: Future-proofing that never gets used
// "We might support multiple databases someday"
public interface DatabaseAdapter { }
public class MySQLAdapter implements DatabaseAdapter { }
public class PostgreSQLAdapter implements DatabaseAdapter { } // Never used
public class OracleAdapter implements DatabaseAdapter { } // Never used
public class MongoAdapter implements DatabaseAdapter { } // Never used
public class DatabaseAdapterFactory { } // Complexity!
// BETTER: Just use what you need
public class UserRepository {
private final JdbcTemplate jdbc; // Using MySQL
// When you actually need another DB, refactor then
}
// VIOLATION 2: Configuration for everything
// "We might want to change this"
@Value("${greeting.prefix:Hello}")
private String greetingPrefix;
@Value("${greeting.suffix:!}")
private String greetingSuffix;
@Value("${greeting.uppercase:false}")
private boolean uppercaseGreeting;
// BETTER: Just write the code
public String greet(String name) {
return "Hello, " + name + "!";
}
// VIOLATION 3: Generic solutions for specific problems
// "We'll need to handle different calculation types"
public interface Calculator<T, R> {
R calculate(T input, CalculationContext context);
}
public class GenericCalculationEngine<T, R> {
private List<Calculator<T, R>> calculators;
private CalculationStrategy strategy;
// ... 200 lines of generic machinery
}
// BETTER: Solve the actual problem
public class TaxCalculator {
public Money calculate(Order order) {
return order.getSubtotal().multiply(TAX_RATE);
}
}
Real-World Examples

YAGNI vs Good Design
// QUESTION: Isn't good design about planning ahead?
// The difference is:
// - Good design: Making current code easy to change
// - YAGNI violation: Building features that might be needed
// GOOD DESIGN (easy to change, but only builds what's needed)
public class OrderService {
private final OrderRepository repository;
private final PaymentProcessor payment;
public OrderService(OrderRepository repository, PaymentProcessor payment) {
this.repository = repository;
this.payment = payment;
}
public Order createOrder(OrderRequest request) {
Order order = new Order(request);
payment.process(order);
repository.save(order);
return order;
}
}
// This is:
// ✓ Testable (dependency injection)
// ✓ Extensible (interfaces)
// ✓ Simple (only what's needed)
// NOT building:
// ✗ Multiple payment processors (until needed)
// ✗ Async processing (until needed)
// ✗ Event publishing (until needed)
// ✗ Batch orders (until needed)
// WHEN NEEDED, it's easy to add because the design is clean.
When to Apply YAGNI

Refactoring When Needed
// YAGNI doesn't mean code can't evolve
// Version 1: Single notification type (what's needed now)
public class NotificationService {
private final EmailSender emailSender;
public void notify(User user, String message) {
emailSender.send(user.getEmail(), message);
}
}
// Version 2: When SMS is actually needed (requirement is real)
public interface NotificationChannel {
void send(String destination, String message);
}
public class EmailChannel implements NotificationChannel { }
public class SmsChannel implements NotificationChannel { }
public class NotificationService {
private final Map<NotificationType, NotificationChannel> channels;
public void notify(User user, NotificationType type, String message) {
channels.get(type).send(user.getContact(type), message);
}
}
// The refactoring is:
// ✓ Done when actually needed
// ✓ Based on real requirements
// ✓ Properly tested with real use cases
// ✓ Not wasted if requirements change again
Tips & Tricks
