10. Buenas prácticas y errores comunes al aplicar los conceptos

Aplicar correctamente los conceptos de acoplamiento y cohesión exige técnicas con respaldo empírico. Sin un plan, las refactorizaciones pueden introducir nuevas dependencias o dispersar responsabilidades. Este capítulo recopila buenas prácticas y alertas frecuentes al momento de optimizar la arquitectura, ilustradas con ejemplos en Java.

10.1 Preparar las refactorizaciones con datos

Antes de modificar código conviene reunir evidencias. Las métricas de análisis estático, los reportes de defectos y los tiempos de revisión indican dónde la cohesión es baja o el acoplamiento presenta riesgos. Herramientas como SonarQube ayudan a identificar clases con alta complejidad o dependencias circulares sin necesidad de inspeccionar manualmente cada componente.

10.2 Buenas prácticas para mantener el equilibrio

  • Limitar el alcance de cada refactorización y desplegarla junto con pruebas automatizadas.
  • Planificar reuniones de diseño técnicas breves para validar el foco de cada módulo.
  • Organizar los paquetes según el dominio y no por capas técnicas arbitrarias; favorece la cohesión.
  • Establecer convenciones de nombres que cuenten la responsabilidad primaria de la clase.
  • Practicar revisiones entre pares orientadas a detectar dependencias innecesarias.

10.3 Errores habituales al reducir el acoplamiento

Desacoplar a cualquier precio puede generar anti-patrones:

  • Abstracciones vacías: interfaces sin significado que solo agregan capas y dificultan seguir el flujo.
  • Uso excesivo de eventos: reemplazar colaboraciones directas por eventos genéricos puede ocultar dependencias e introducir condiciones de carrera.
  • Inversión prematura: aplicar inyección de dependencias en objetos triviales agrega complejidad sin beneficio real.

10.4 Cuando la cohesión se diluye

Una refactorización mal planificada puede repartir responsabilidades sin criterio, reduciendo la cohesión. Algunos signos:

  • El mismo trabajo se replica en varias clases por dividir un componente sin analizar a fondo su propósito.
  • Los tests unitarios se vuelven frágiles porque cada combinación de objetos necesita configuraciones distintas.
  • Los nombres de los nuevos objetos incluyen sufijos genéricos como Helper o Manager.

10.5 Ejemplo práctico: de mala a buena práctica

El siguiente fragmento muestra una refactorización que cayó en excesos: se crearon interfaces múltiples sin intención concreta.

interface Procesador {
    void ejecutar();
}

class ProcesadorPedidos implements Procesador {
    private final ServicioPedidos servicioPedidos;
    private final ServicioNotificaciones servicioNotificaciones;

    ProcesadorPedidos(ServicioPedidos servicioPedidos,
                      ServicioNotificaciones servicioNotificaciones) {
        this.servicioPedidos = servicioPedidos;
        this.servicioNotificaciones = servicioNotificaciones;
    }

    public void ejecutar() {
        servicioPedidos.procesar();
        servicioNotificaciones.enviar();
    }
}

La interfaz Procesador no aporta contexto y todos los métodos de ProcesadorPedidos se limitan a delegar, por lo que el acoplamiento no disminuye y la cohesión se difumina.

Una mejora consiste en expresar la intención real y combinar las dependencias necesarias bajo un servicio con responsabilidad clara:

class CoordinadorPedidosPendientes {
    private final ServicioPedidos servicioPedidos;
    private final ServicioNotificaciones servicioNotificaciones;

    CoordinadorPedidosPendientes(ServicioPedidos servicioPedidos,
                                 ServicioNotificaciones servicioNotificaciones) {
        this.servicioPedidos = servicioPedidos;
        this.servicioNotificaciones = servicioNotificaciones;
    }

    void ejecutarCiclo() {
        servicioPedidos.procesarPendientes();
        servicioNotificaciones.notificarResultados();
    }
}

El nuevo nombre comunica el objetivo y los métodos mantienen cohesión funcional. Además, los tests pueden concentrarse en validar un ciclo específico sin dependencias artificiales.

10.6 Prácticas de validación continua

  • Automatizar pruebas unitarias y de integración para detectar dependencias rotas.
  • Usar linters y análisis estático para controlar métricas como LCOM o complejidad ciclomática.
  • Ejecutar auditorías de arquitectura periódicas para revisar decisiones de acoplamiento.
  • Registrar en un documento de arquitectura ligera los motivos de las decisiones, evitando repeticiones innecesarias.

10.7 Checklist contra errores recurrentes

  • ¿Cada abstracción tiene un lenguaje propio del dominio que la justifique?
  • ¿Existen tests que fallen si una clase pierde cohesión?
  • ¿La introducción de un nuevo servicio redujo las dependencias o solo reordenó el mismo accionar?
  • ¿Se documentó por qué una interfaz o patrón son necesarios para controlar el acoplamiento?

Las buenas prácticas en torno al acoplamiento y la cohesión son un camino constante. Al observar los errores habituales y anticipar sus causas, el equipo puede tomar decisiones más conscientes, preservar la claridad del diseño y responder con agilidad a los cambios del negocio.