Más allá de SOLID, existe un conjunto de principios pragmáticos que actúan como brújula en el día a día del diseño. DRY nos recuerda no repetir conocimiento; KISS aboga por la simplicidad; YAGNI nos frena de construir lo que aún no necesitamos. A ellos se suman el principio de mínima sorpresa y la preferencia por la composición frente a la herencia. Estos principios son aparentemente sencillos, pero su aplicación equilibrada distingue a un diseñador experimentado: cada uno tira en una dirección y saber cuándo aplicarlos —y cuándo no— es una habilidad arquitectónica clave. En esta lección los estudiaremos con ejemplos prácticos en Java.

Contenido

  1. DRY: no te repitas
  2. KISS: hazlo simple
  3. YAGNI: no lo vas a necesitar
  4. La tensión entre DRY, KISS y YAGNI
  5. Principio de mínima sorpresa
  6. Composición sobre herencia
  7. Otros principios útiles
  8. Errores comunes y consejos
  9. Ejercicios
  10. Conclusión

  1. DRY: no te repitas

DRY (Don't Repeat Yourself) establece que cada pieza de conocimiento debe tener una representación única, inequívoca y autorizada dentro del sistema. El matiz importante es que DRY trata de conocimiento, no de coincidencias textuales de código.

// VIOLACIÓN de DRY: la regla del IVA (21%) está duplicada y dispersa
public class Carrito {
    public double total(double base) { return base + base * 0.21; }
}
public class Factura {
    public double importe(double base) { return base + base * 0.21; }
}

Explicación del problema: la regla "el IVA es el 21%" vive en dos sitios. Si cambia el tipo impositivo, hay que encontrar y modificar todas las copias, con riesgo de olvidar alguna.

// CORRECCIÓN: una única fuente de verdad
public class PoliticaImpuestos {
    private static final double IVA = 0.21;
    public double aplicar(double base) { return base + base * IVA; }
}
public class Carrito {
    private final PoliticaImpuestos impuestos;
    public Carrito(PoliticaImpuestos impuestos) { this.impuestos = impuestos; }
    public double total(double base) { return impuestos.aplicar(base); }
}

Cuidado con el DRY falso: dos fragmentos de código pueden parecer idénticos hoy por casualidad pero representar conocimientos distintos que evolucionarán por separado. Forzar su unificación crea un acoplamiento dañino. La regla es: unifica conocimiento duplicado, no código coincidente.

  1. KISS: hazlo simple

KISS (Keep It Simple, Stupid) recomienda elegir siempre la solución más sencilla que resuelva el problema. La complejidad debe estar justificada por un requisito real.

// VIOLACIÓN de KISS: complejidad innecesaria para comprobar si es par
public boolean esPar(int n) {
    String binario = Integer.toBinaryString(n);
    char ultimo = binario.charAt(binario.length() - 1);
    return ultimo == '0';
}

Explicación del problema: convierte a binario, manipula caracteres... todo para algo que tiene una expresión trivial y directa.

// CORRECCIÓN: la solución obvia
public boolean esPar(int n) {
    return n % 2 == 0;
}

KISS también aplica a la arquitectura: no introduzcas microservicios, colas de mensajes o capas de abstracción si un monolito modular bien organizado resuelve el problema. La complejidad accidental es deuda futura.

  1. YAGNI: no lo vas a necesitar

YAGNI (You Aren't Gonna Need It) advierte contra implementar funcionalidad por anticipado "porque algún día hará falta". La mayoría de esas predicciones fallan, y el código especulativo añade coste de mantenimiento sin aportar valor.

// VIOLACIÓN de YAGNI: parametrización especulativa
public class ExportadorCsv {
    // Nadie ha pedido otros separadores, codificaciones ni compresión
    public String exportar(List<String> filas, char separador,
                           String codificacion, boolean comprimir,
                           boolean incluirCabecera, String pieDePagina) {
        // ... lógica enorme manejando combinaciones que nunca se usan
        return "";
    }
}

Explicación del problema: se han añadido seis parámetros para escenarios hipotéticos. Cada uno hay que probarlo, documentarlo y mantenerlo, aunque solo se use la coma.

// CORRECCIÓN: resuelve solo el caso real de hoy
public class ExportadorCsv {
    public String exportar(List<String> filas) {
        return String.join("\n", filas); // separador por comas dentro de cada fila
    }
}

Si mañana se necesita otro separador, se añade entonces, con el requisito real delante. YAGNI no es excusa para un mal diseño; es una invitación a no resolver problemas que aún no existen.

  1. La tensión entre DRY, KISS y YAGNI

Estos principios a veces se oponen, y el arte está en equilibrarlos:

Situación Principio que tira Principio en tensión Criterio de decisión
Veo código repetido DRY (unificar) KISS/YAGNI (no abstraer aún) ¿Es el mismo conocimiento o coincidencia?
Quiero una abstracción genérica DRY YAGNI ¿Hay 2-3 casos reales o uno solo?
El diseño se vuelve complejo KISS DRY ¿La simplicidad justifica algo de duplicación?

Una heurística práctica muy citada es la regla de tres: la primera vez escribes el código; la segunda vez que aparece algo parecido, lo toleras; la tercera vez, abstraes. Esto concilia DRY con YAGNI: esperas a tener evidencia real de un patrón antes de unificarlo.

  1. Principio de mínima sorpresa

El principio de mínima sorpresa (Principle of Least Astonishment) dice que un componente debe comportarse como un usuario razonable espera. El código no debe esconder efectos secundarios sorprendentes ni romper convenciones.

// VIOLACIÓN: un getter con efectos secundarios sorprendentes
public class Cuenta {
    private double saldo;
    public double getSaldo() {
        registrarAcceso();   // sorpresa: un getter que escribe
        saldo -= 1;          // sorpresa: consultar cobra comisión
        return saldo;
    }
}

Explicación del problema: nadie espera que getSaldo() modifique el estado ni cobre comisiones. Quien lo use se llevará una sorpresa difícil de depurar.

// CORRECCIÓN: nombres honestos y comportamiento predecible
public class Cuenta {
    private double saldo;
    public double getSaldo() { return saldo; } // solo consulta
    public double consultarConComision() {       // intención explícita
        saldo -= 1;
        return saldo;
    }
}

Aplicado a APIs: respeta convenciones de nombres, devuelve tipos esperados, lanza las excepciones documentadas y evita comportamientos ocultos.

  1. Composición sobre herencia

La herencia crea un acoplamiento fuerte y estático entre clases. La composición —construir objetos combinando otros— suele ser más flexible.

// PROBLEMÁTICO: herencia rígida y explosión de subclases
public class Pajaro {
    public void volar() { /* ... */ }
}
public class Pinguino extends Pajaro {
    // ¡los pingüinos no vuelan! Herencia mal aplicada
    public void volar() { throw new UnsupportedOperationException(); }
}

Explicación del problema: heredar volar() obliga al pingüino a tener un comportamiento que no le corresponde. Además, combinar capacidades (nada, vuela, corre) por herencia provoca una explosión de subclases.

// MEJOR: componer comportamientos
public interface Desplazamiento { void mover(); }
public class Vuelo implements Desplazamiento {
    public void mover() { System.out.println("Vuela"); }
}
public class Nado implements Desplazamiento {
    public void mover() { System.out.println("Nada"); }
}
public class Animal {
    private final Desplazamiento desplazamiento; // se compone, no se hereda
    public Animal(Desplazamiento desplazamiento) { this.desplazamiento = desplazamiento; }
    public void mover() { desplazamiento.mover(); }
}
// Pinguino = new Animal(new Nado()); Aguila = new Animal(new Vuelo());

Explicación de la mejora: el comportamiento se inyecta como un objeto. Se puede cambiar en tiempo de ejecución y combinar libremente. Esto es la base del patrón Strategy. La regla "favorece la composición sobre la herencia" viene del libro de los patrones de diseño (GoF) y evita jerarquías frágiles.

Aspecto Herencia Composición
Acoplamiento Fuerte, estático Débil, dinámico
Reutilización Limitada a la jerarquía Flexible y combinable
Cambio en runtime No
Riesgo Romper LSP, jerarquías profundas Más objetos que orquestar

  1. Otros principios útiles

  • Separación comando-consulta (CQS): un método o cambia estado (comando) o devuelve datos (consulta), pero no ambas cosas. Refuerza la mínima sorpresa.
  • Fail-fast: detectar y reportar errores lo antes posible (validar parámetros al entrar) en lugar de propagar estados inválidos.
  • Encapsulación: ocultar el estado interno y exponer solo comportamiento; base del bajo acoplamiento.
  • Alta cohesión / bajo acoplamiento: ya vistos en la lección anterior, son el telón de fondo de todos estos principios.
  • Boy Scout Rule: deja el código un poco más limpio de como lo encontraste; mantenimiento incremental que combate la deuda técnica.

Errores Comunes y Consejos

  • Aplicar DRY de forma dogmática y crear abstracciones prematuras que acoplan conocimientos distintos. Recuerda: duplicación es más barata que la abstracción equivocada.
  • Confundir KISS con simplista. Simple no es hacer menos de lo necesario; es no hacer de más. Un sistema demasiado simplista que no cubre los requisitos también es un error.
  • Usar YAGNI como excusa para no diseñar. YAGNI rechaza funcionalidad especulativa, no el buen diseño base ni la extensibilidad razonable.
  • Heredar para reutilizar líneas de código. Si la relación no es un "es-un" genuino, usa composición.
  • Sorpresas en los nombres: un método llamado validar() que además guarda en base de datos viola la mínima sorpresa. Nombra según lo que realmente hace.
  • Consejo: usa la regla de tres como árbitro entre DRY y YAGNI. Y mide: si una abstracción tiene un solo uso real, probablemente sobra.

Ejercicios

Ejercicio 1 (DRY). Detecta la duplicación de conocimiento y corrígela:

class Empleado { double bono(double s) { return s * 0.1; } }
class Directivo { double bono(double s) { return s * 0.1; } }

Ejercicio 2 (KISS/YAGNI). Simplifica este código aplicando KISS y YAGNI:

public String saludar(String nombre, boolean formal, boolean mayusculas,
                      String idioma, boolean conEmoji) {
    String saludo = formal ? "Estimado " : "Hola ";
    saludo += nombre;
    if (mayusculas) saludo = saludo.toUpperCase();
    if (conEmoji) saludo += " :)";
    return saludo; // solo se usa saludar(nombre)
}

Ejercicio 3 (Composición). Convierte esta jerarquía de herencia en composición:

class Coche { void arrancar() {} }
class CocheElectrico extends Coche { void cargar() {} }
class CocheGasolina extends Coche { void repostar() {} }

Soluciones

Solución 1. El cálculo del bono es el mismo conocimiento; lo centralizamos:

class PoliticaBonos {
    private static final double TASA = 0.1;
    double calcular(double salario) { return salario * TASA; }
}
// Empleado y Directivo reciben/usan PoliticaBonos en lugar de duplicar la regla.

Solución 2. Eliminamos los parámetros especulativos no usados:

public String saludar(String nombre) {
    return "Hola " + nombre;
}

Si en el futuro hace falta el modo formal o los idiomas, se añaden con el requisito real.

Solución 3. Modelamos el tipo de energía como un componente:

interface FuenteEnergia { void recargar(); }
class Electrica implements FuenteEnergia { public void recargar() { /* cargar */ } }
class Combustion implements FuenteEnergia { public void recargar() { /* repostar */ } }

class Coche {
    private final FuenteEnergia energia;
    Coche(FuenteEnergia energia) { this.energia = energia; }
    void arrancar() {}
    void recargar() { energia.recargar(); }
}
// new Coche(new Electrica());  new Coche(new Combustion());

Conclusión

DRY, KISS y YAGNI forman un trío que regula el equilibrio entre rigor, simplicidad y pragmatismo: no repetir conocimiento, mantener la sencillez y no construir de más. Los hemos complementado con el principio de mínima sorpresa, que exige comportamiento predecible, y con la preferencia por la composición sobre la herencia, que evita jerarquías frágiles. Aplicados con juicio —y arbitrados por heurísticas como la regla de tres— estos principios reducen la complejidad accidental. En la siguiente lección daremos un salto del diseño de clases al diseño del sistema, estudiando las tácticas arquitectónicas con las que alcanzamos atributos de calidad como disponibilidad, rendimiento o seguridad.

Curso de Arquitectura de Aplicaciones

Módulo 1: Fundamentos de la Arquitectura de Aplicaciones

Módulo 2: Principios y Tácticas de Diseño

Módulo 3: Estilos y Patrones Arquitectónicos

Módulo 4: Arquitecturas Distribuidas y Microservicios

Módulo 5: Arquitecturas Dirigidas por Eventos y Mensajería

Módulo 6: Diseño Dirigido por el Dominio (DDD)

Módulo 7: Datos y Persistencia

Módulo 8: Arquitectura en la Nube y Despliegue

Módulo 9: Calidad, Seguridad y Observabilidad

Módulo 10: Evolución, Gobernanza y Casos Prácticos

© Copyright 2026. Todos los derechos reservados