Skip to content

YAGNI - You Aren't Gonna Need It


Definition

YAGNI 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 in Practice


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

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

YAGNI Tips