1. Introducción al diseño estructural del software y la importancia del equilibrio

Un diseño de software sostenible comienza por cuidar su esqueleto: la manera en que se relacionan los módulos y cómo se agrupan las responsabilidades. Ese tejido invisible determina si el proyecto acompaña el crecimiento del negocio o se vuelve una carga difícil de modificar. En este primer tema profundizamos en la idea de calidad estructural, el equilibrio entre acoplamiento y cohesión, y por qué de ello depende la mantenibilidad a largo plazo.

1.1 Concepto de calidad estructural

La calidad estructural describe la solidez interna del sistema: claridad de dependencias, delimitación de responsabilidades y facilidad para aislar componentes. No se trata solo de que el software funcione hoy, sino de que mantenga una organización capaz de absorber requisitos futuros. Desde los inicios de la programación orientada a objetos, distintos autores han coincidido en que el código es un ecosistema que convive con cambios constantes. Medir fan-in/fan-out, contar métodos por clase o evaluar la profundidad de herencia son formas concretas de observar esa calidad estructural.

1.2 Equilibrio entre acoplamiento y cohesión

Acoplamiento y cohesión son dos caras de la misma moneda: el primero expresa cuánta información necesita un módulo de otro para funcionar, mientras que la segunda revela si ese módulo mantiene un propósito coherente. Un diseño equilibrado busca dependencias explícitas, estables y fáciles de razonar, apoyadas en contratos claros. Al mismo tiempo, procura que cada clase, paquete o servicio se concentre en resolver un conjunto acotado de problemas. Ese balance evita que ajustes menores desencadenen refactorizaciones masivas o que una funcionalidad quede atrapada en una clase todopoderosa con demasiado contexto.

1.3 Impacto en la mantenibilidad

La mantenibilidad se ve favorecida cuando los cambios se pueden localizar, comprender y desplegar rápido. El acoplamiento excesivo obliga a coordinar ediciones en varias capas, incrementa la probabilidad de defectos colaterales y reduce la velocidad de entrega. Por su parte, la falta de cohesión genera código ambiguo, dificulta la reutilización y rompe la trazabilidad entre la necesidad del usuario y la implementación. Cuando el equipo resguarda el equilibrio, surgen beneficios tangibles: menor tiempo para diagnosticar bugs, ciclos de prueba más cortos, y una curva de aprendizaje más suave para quienes se incorporan al proyecto.

1.4 Principios de apoyo y métricas

La aplicación consistente de los principios SOLID ofrece guías concretas para sostener este equilibrio. Por ejemplo, el Principio de Responsabilidad Única promueve cohesión alta, mientras que la Inversión de Dependencias reduce acoplamientos innecesarios al depender de abstracciones. A nivel organizativo, arquitecturas como la hexagonal o el diseño guiado por el dominio facilitan separar el núcleo del negocio de las preocupaciones tecnológicas. Complementar estas ideas con métricas como LCOM (Lack of Cohesion in Methods) o la complejidad ciclomática permite detectar puntos críticos de forma anticipada.

1.5 Ejemplo introductorio en Java

Consideremos un servicio de pedidos que mezcla validación, persistencia y notificación en una misma clase. Allí la cohesión se diluye y cada cambio exige revisar varias dependencias:

class PedidoService {
    private final Connection connection;

    PedidoService(Connection connection) {
        this.connection = connection;
    }

    void crearPedido(Pedido pedido) throws SQLException {
        validar(pedido);
        connection.prepareStatement("INSERT INTO pedidos ...");
        EmailClient.enviarConfirmacion(pedido);
        Auditoria.log("Pedido creado " + pedido.getId());
    }
}

La situación mejora al introducir contratos específicos que encapsulan dependencias y enfocan el propósito de cada pieza. Así, el servicio conserva la lógica del negocio, mientras que la persistencia y la notificación evolucionan de manera independiente:

interface RepositorioPedidos {
    void guardar(Pedido pedido);
}

interface NotificadorPedidos {
    void notificarCreacion(Pedido pedido);
}

class PedidoService {
    private final RepositorioPedidos repositorio;
    private final NotificadorPedidos notificador;

    PedidoService(RepositorioPedidos repositorio, NotificadorPedidos notificador) {
        this.repositorio = repositorio;
        this.notificador = notificador;
    }

    void crearPedido(Pedido pedido) {
        validar(pedido);
        repositorio.guardar(pedido);
        notificador.notificarCreacion(pedido);
    }

    private void validar(Pedido pedido) {
        // Reglas de negocio concentradas en un solo lugar
    }
}

Con esta versión, el servicio se especializa en el flujo del dominio, mientras que las colaboraciones dependen de interfaces. Esto permite probar la lógica con dobles en Java, intercambiar implementaciones según el entorno (base de datos real, stub o API externa) y evolucionar cada componente sin arrastrar cambios en cascada. Mantener vivo este enfoque, apoyado en métricas y conversaciones de equipo, garantiza que la calidad estructural del sistema se mantenga alineada con los objetivos del negocio.