11. Errores comunes al aplicar SOLID

Adoptar los principios SOLID aporta enormes beneficios, pero hacerlo de manera apresurada puede derivar en anti‑patrones que afectan la calidad. En esta sección reunimos los errores más frecuentes, su impacto y estrategias para prevenirlos.

Los ejemplos utilizan Java, pero los conceptos aplican a cualquier lenguaje orientado a objetos.

11.1 Sobreingeniería motivada por SOLID

Un error habitual es introducir interfaces y capas adicionales sin una necesidad real. Esto genera código difícil de leer y tiempo extra de mantenimiento.

interface UsuarioValidadorStrategy {
    boolean validar(Usuario usuario);
}

class UsuarioValidadorStrategyImpl implements UsuarioValidadorStrategy {
    public boolean validar(Usuario usuario) {
        return usuario.nombre() != null;
    }
}

class UsuarioService {
    private final UsuarioValidadorStrategy validador;

    UsuarioService() {
        this.validador = new UsuarioValidadorStrategyImpl();
    }
}

Si la validación es simple y no va a cambiar, esta abstracción no aporta valor. Hasta que surja la necesidad, es preferible mantener una implementación directa. SRP se viola cuando la sobreingeniería oculta la responsabilidad principal.

11.2 Interfaces infladas bajo la excusa de ISP

Al intentar cumplir ISP, algunas personas crean una interfaz por cada método, generando fragmentos difíciles de gestionar.

interface PuedeGuardar {
    void guardar();
}

interface PuedeActualizar {
    void actualizar();
}

interface PuedeBorrar {
    void borrar();
}

Este enfoque extremo produce “fragmentación de interfaces”. La recomendación es agrupar métodos relacionados por el mismo caso de uso. ISP se refiere a no imponer métodos innecesarios, no a atomizar sin criterio.

11.3 Confundir herencia con reutilización (violando LSP)

Forzar jerarquías para “aprovechar código” suele terminar en subclases que rompen el contrato implícito de la clase base.

class Archivo {
    void abrir() { /* ... */ }
    void cerrar() { /* ... */ }
}

class ArchivoSoloLectura extends Archivo {
    @Override
    void cerrar() {
        throw new UnsupportedOperationException("No se puede cerrar");
    }
}

La subclase invalida el comportamiento esperado: un archivo siempre debería poder cerrarse. La solución es usar composición o interfaces específicas.

11.4 Dependencias invertidas pero rígidas

Otra trampa consiste en declarar interfaces para cumplir DIP, pero seguir creando las implementaciones concretas dentro de la clase.

class ReporteService {
    private final EmailSender emailSender;

    ReporteService() {
        this.emailSender = new EmailSender(); // dependencia rígida
    }
}

Si la clase instancia la dependencia, la inversión no se produce. Es necesario inyectar la dependencia desde el exterior, preferentemente por constructor, para permitir pruebas y cambios tecnológicos.

11.5 Falta de pruebas después de la refactorización

Aplicar SOLID sin respaldarlo con pruebas automáticas puede introducir fallas silenciosas. Un clásico: refactorizar para cumplir SRP y romper flujos que nadie prueba.

  • Impacto: regresiones en producción, pérdida de confianza en la refactorización.
  • Prevención: escribir pruebas antes o inmediatamente después de refactorizar, cubrir colaboraciones con dobles de prueba.

11.6 Desconocimiento del dominio

Sin un entendimiento claro del negocio, se crean abstracciones equivocadas. Esto deriva en nombres genéricos o capas que duplican conceptos reales.

interface Manager {
    void execute(Object request);
}

Este código dice poco sobre lo que hace el sistema. El principio SRP depende de modelar responsabilidades alineadas al dominio. La solución es trabajar con el equipo funcional y renombrar con intención.

11.7 Usar SOLID como dogma

Aplicar SOLID sin contexto lleva a decisiones que complican el proyecto. Algunas señales:

  • Abstracciones prematuras: interfaces para todo, incluso para clases que nunca cambiarán.
  • Parálisis en decisiones: temor a tocar código porque “rompe el principio”.
  • Lenguaje cargado: discusiones que priorizan la teoría sobre la entrega de valor.

Recordá que SOLID es una guía, no un objetivo final. El diseño evoluciona con el proyecto.

11.8 No documentar las decisiones de diseño

Después de refactorizar, si el equipo no deja registro del motivo, el diseño se degrada rápidamente. Nuevas personas pueden revertir decisiones acertadas sin saberlo.

  • Impacto: pérdida de consistencia entre módulos, aumento de discusiones repetidas.
  • Prevención: documentar brevemente en el repositorio, PR o wiki la intención detrás de cada abstracción.

11.9 Olvidar la simplicidad

El principio KISS (Keep It Simple, Stupid) debe convivir con SOLID. Antes de introducir una nueva capa, preguntate si simplifica o complica la solución.

  • ¿La abstracción se usa en más de un lugar?
  • ¿Existen métricas o pruebas que avalen la refactorización?
  • ¿El equipo entiende la intención del cambio?

11.10 Checklist para evitar errores

  • Hacer evidente el motivo: cada refactorización debe corregir un problema concreto (cambio frecuente, duplicación, test difícil).
  • Aplicar SOLID de forma incremental: priorizar secciones críticas del sistema, no intentar transformar todo de una vez.
  • Usar revisiones de código enfocadas: pedir feedback sobre responsabilidad, acoplamiento y sustitución.
  • Medir resultados: comparar métricas de defectos, tiempo de entrega y cobertura para validar que el esfuerzo valió la pena.

Aprender de estos errores acelera el dominio de SOLID. En la siguiente sección analizaremos cómo comparar código sin SOLID contra versiones refactorizadas para visualizar la mejora.