19. Relación entre estos principios y los principios SOLID

Los principios DRY, KISS y YAGNI comparten propósito con los principios SOLID: guiar el diseño hacia soluciones mantenibles, comprensibles y evolutivas. En proyectos con Java, combinarlos permite sostener arquitecturas limpias sin caer en sobreingeniería.

Este capítulo explica cómo se conectan, qué tensiones surgen y cómo resolverlas mediante ejemplos concretos.

19.1 Conexiones conceptuales

  • DRY y Single Responsibility (SRP): ambos reducen la duplicación de motivos de cambio. Una clase sin responsabilidades mezcladas facilita centralizar la lógica compartida.
  • KISS y Interface Segregation (ISP): interfaces pequeñas y directas promueven soluciones simples y evitan dependencias innecesarias.
  • YAGNI y Open/Closed (OCP): al posponer extensiones, implementamos solo las variaciones justificadas. Cuando llegan, OCP ayuda a agregarlas sin modificar lo existente.
  • DRY/KISS y Dependency Inversion (DIP): abstraer dependencias reales evita duplicar implementaciones y mantiene el código legible.

19.2 Tensiones habituales y cómo resolverlas

  • Abstracciones prematuras: intentar cumplir OCP sin evidencia rompe YAGNI. Solución: crear extensiones cuando aparezca la primera variación real.
  • Interfaces demasiado generales: forzar ISP para evitar duplicaciones puede violar KISS. Prefiere interfaces específicas, incluso si inicialmente son pocas.
  • Uso excesivo de patrones: aplicar patrones de manera preventiva contradice KISS y YAGNI. Evalúa si el patrón resuelve duplicaciones o cambios reales.

19.3 Ejemplo en Java: refactorizar respetando DRY, KISS, YAGNI y SOLID

Partimos de un servicio que viola SRP y Liskov Substitution (LSP) al mezclar múltiples tipos de notificación con condicionales.

// Implementación inicial: mezcla de responsabilidades y futuros escenarios
class AlertService {
    private final EmailClient emailClient;
    private final SmsClient smsClient;

    AlertService(EmailClient emailClient, SmsClient smsClient) {
        this.emailClient = emailClient;
        this.smsClient = smsClient;
    }

    void send(Alert alert) {
        if ("EMAIL".equals(alert.type())) {
            emailClient.send(alert.recipient(), alert.message());
        } else if ("SMS".equals(alert.type())) {
            smsClient.send(alert.recipient(), alert.message());
        } else if ("PUSH".equals(alert.type())) {
            // Futuro: push notifications
        }
    }
}

La refactorización aplica SRP, LSP y DIP (SOLID) junto con DRY/KISS/YAGNI: se extrae una jerarquía mínima de notifiers, se elimina el canal especulativo y el servicio queda simple.

// Implementación refactorizada: SOLID + DRY, KISS, YAGNI trabajando juntos
interface Notifier {
    void send(Alert alert);
}

final class EmailNotifier implements Notifier {
    private final EmailClient emailClient;

    EmailNotifier(EmailClient emailClient) {
        this.emailClient = emailClient;
    }

    @Override
    public void send(Alert alert) {
        emailClient.send(alert.recipient(), alert.message());
    }
}

final class SmsNotifier implements Notifier {
    private final SmsClient smsClient;

    SmsNotifier(SmsClient smsClient) {
        this.smsClient = smsClient;
    }

    @Override
    public void send(Alert alert) {
        smsClient.send(alert.recipient(), alert.message());
    }
}

final class AlertService {
    private final List<Notifier> notifiers;

    AlertService(List<Notifier> notifiers) {
        this.notifiers = List.copyOf(notifiers);
    }

    void send(Alert alert) {
        notifiers.forEach(notifier -> notifier.send(alert));
    }
}

Los canales existentes están modelados con clases concretas (SRP), nuevas variantes se agregan sin modificar el servicio (OCP), no hay duplicación de lógica (DRY), el flujo es simple (KISS) y no se implementaron canales hipotéticos (YAGNI).

19.4 Estrategia de adopción conjunta

  1. Diagnóstico: identifica duplicaciones, complejidad y funcionalidad ociosa. Usa métricas de duplicación, complejidad ciclomática y telemetría.
  2. Refactorización incremental: aplica SOLID para lograr modularidad, DRY para consolidar lógica, KISS para simplificar flujos y YAGNI para recortar exceso.
  3. Reforzar con pruebas: agrega pruebas unitarias e integrales que validen las abstracciones creadas.
  4. Documentar decisiones: registra en ADRs qué principio motivó el cambio y cuándo revisarlo.

19.5 Checklist combinada

  • ¿Cada clase posee una única responsabilidad clara? (SRP + KISS)
  • ¿Las dependencias se invierten solo cuando se necesita desacoplar? (DIP + YAGNI)
  • ¿Las extensiones se agregan mediante nuevas clases, no modificando las existentes? (OCP + DRY)
  • ¿Las interfaces son pequeñas y específicas? (ISP + KISS)
  • ¿Las abstracciones se crearon para resolver duplicaciones reales? (DRY + YAGNI)

Equilibrar DRY, KISS y YAGNI con SOLID mantiene sistemas flexibles y con un diseño progresivo. Cada principio ofrece una perspectiva distinta, y juntos permiten construir software sostenible en el tiempo.