18. Casos prácticos en Java con aplicación de DRY, KISS y YAGNI

Aplicar DRY, KISS y YAGNI deja de ser teoría cuando enfrentamos código real. En estos casos prácticos en Java mostramos cómo identificar problemas concretos, refactorizar y evaluar el impacto de aplicar cada principio.

Cada caso detalla el contexto, la refactorización paso a paso y el resultado final para que puedas replicarlo en tus proyectos.

18.1 Caso 1: servicio de facturación repetitivo (DRY + KISS)

Problema: distintos módulos calculaban impuestos y totales de manera duplicada con pequeñas variaciones.

// Código original: lógica duplicada en dos servicios
class OnlineBillingService {
    BigDecimal calculateTotal(Order order) {
        BigDecimal subtotal = order.subtotal();
        BigDecimal iva = subtotal.multiply(new BigDecimal("0.21"));
        return subtotal.add(iva);
    }
}

class WholesaleBillingService {
    BigDecimal calculateTotal(Order order) {
        BigDecimal subtotal = order.subtotal();
        BigDecimal iva = subtotal.multiply(new BigDecimal("0.21"));
        BigDecimal bonificacion = subtotal.multiply(new BigDecimal("0.05"));
        return subtotal.add(iva).subtract(bonificacion);
    }
}

Refactorización: se creó un componente común para el cálculo de IVA y se simplificaron los métodos.

// Código refactorizado: DRY para calcular IVA y métodos simples
final class TaxCalculator {
    BigDecimal iva(BigDecimal subtotal) {
        return subtotal.multiply(new BigDecimal("0.21"));
    }
}

final class BillingService {
    private final TaxCalculator taxCalculator;

    BillingService(TaxCalculator taxCalculator) {
        this.taxCalculator = taxCalculator;
    }

    BigDecimal totalOnline(Order order) {
        BigDecimal iva = taxCalculator.iva(order.subtotal());
        return order.subtotal().add(iva);
    }

    BigDecimal totalWholesale(Order order) {
        BigDecimal iva = taxCalculator.iva(order.subtotal());
        BigDecimal bonificacion = order.subtotal().multiply(new BigDecimal("0.05"));
        return order.subtotal().add(iva).subtract(bonificacion);
    }
}

Resultado: se eliminó duplicación (DRY) y cada método quedó enfocado en su tarea (KISS).

18.2 Caso 2: controlador sobreingenierizado (KISS + YAGNI)

Problema: un controlador MVC gestionaba capas de validación redundantes, feature flags que nunca se usaron y transformaciones innecesarias.

// Código original: controlador con capas y flags especulativos
class OrderController {
    private final OrderService service;
    private final FeatureToggle toggles;

    OrderController(OrderService service, FeatureToggle toggles) {
        this.service = service;
        this.toggles = toggles;
    }

    ResponseEntity<?> create(OrderRequest request) {
        if (toggles.isEnabled("ASYNC_CREATE")) {
            // Camino alternativo no habilitado
        }
        ValidationResult result = validate(request);
        if (!result.valid()) {
            return ResponseEntity.badRequest().body(result.errors());
        }
        Order order = service.create(transform(request));
        return ResponseEntity.ok(mapResponse(order));
    }

    private Order transform(OrderRequest request) {
        // Conversión redundante
        return new Order(request.id(), request.items());
    }

    private OrderResponse mapResponse(Order order) {
        // Wrapper innecesario
        return new OrderResponse(order.id(), order.total());
    }
}

Refactorización: se eliminó código especulativo (YAGNI) y se simplificó el flujo.

// Código refactorizado: controlador simple y sin supuestos futuros
final class OrderController {
    private final OrderService service;

    OrderController(OrderService service) {
        this.service = service;
    }

    ResponseEntity<Order> create(OrderRequest request) {
        ValidationResult result = request.validate();
        if (!result.valid()) {
            return ResponseEntity.badRequest().build();
        }
        Order order = service.create(request.toOrder());
        return ResponseEntity.ok(order);
    }
}

Resultado: la ruta principal quedó clara (KISS) y se eliminó funcionalidad no validada (YAGNI).

18.3 Caso 3: pipeline de procesamiento (DRY + KISS + YAGNI)

Problema: un pipeline de procesamiento de reportes tenía duplicaciones, configuraciones anticipadas y pasos que mezclaban responsabilidades.

// Código original: pasos duplicados y configuraciones innecesarias
class ReportPipeline {
    private final CsvReader csvReader;
    private final XmlReader xmlReader;
    private final ReportSender sender;
    private final AuditService auditService;

    ReportPipeline(CsvReader csvReader,
                   XmlReader xmlReader,
                   ReportSender sender,
                   AuditService auditService) {
        this.csvReader = csvReader;
        this.xmlReader = xmlReader;
        this.sender = sender;
        this.auditService = auditService;
    }

    void processCsv(Path file, boolean sendAudit) {
        List<Record> records = csvReader.read(file);
        List<Record> normalized = normalize(records);
        if (sendAudit) {
            auditService.record(normalized);
        }
        sender.send(normalized, "CSV");
    }

    void processXml(Path file, boolean sendAudit) {
        List<Record> records = xmlReader.read(file);
        List<Record> normalized = normalize(records);
        if (sendAudit) {
            auditService.record(normalized);
        }
        sender.send(normalized, "XML");
    }

    private List<Record> normalize(List<Record> records) {
        return records.stream()
            .map(record -> record.trimmed())
            .toList();
    }
}

Refactorización: se aplicaron los tres principios: DRY para reutilizar el flujo, KISS para hacerlo legible y YAGNI para posponer configuraciones no requeridas.

// Código refactorizado: pipeline reutilizable y simple
final class ReportPipeline {
    private final Map<Format, Reader> readers;
    private final ReportSender sender;
    private final AuditService auditService;

    ReportPipeline(Map<Format, Reader> readers,
                   ReportSender sender,
                   AuditService auditService) {
        this.readers = Map.copyOf(readers);
        this.sender = sender;
        this.auditService = auditService;
    }

    void process(Path file, Format format, boolean audit) {
        Reader reader = readers.get(format);
        List<Record> normalized = reader.read(file).stream()
            .map(Record::trimmed)
            .toList();
        if (audit) {
            auditService.record(normalized);
        }
        sender.send(normalized, format.name());
    }
}

Resultado: el pipeline quedó reutilizable (DRY), fácil de seguir (KISS) y sin configuraciones futuras innecesarias (YAGNI).

18.4 Checklist para evaluar cada caso práctico

  • ¿Se eliminó la duplicación lógica? (DRY)
  • ¿El flujo principal quedó claro y sin capas innecesarias? (KISS)
  • ¿Se removió código especulativo o configuraciones sin evidencia? (YAGNI)
  • ¿Las pruebas se adaptaron a la nueva estructura?
  • ¿El equipo documentó los cambios y su impacto?

Practicar con ejemplos concretos demuestra que DRY, KISS y YAGNI no se excluyen: juntos sostienen un código evolutivo, fácil de entender y enfocado en el valor real.