Modularity metrics: medindo acoplamento e coesão em código real
1. Por que medir modularidade importa na Arquitetura de Software
1.1. Relação entre modularidade e sustentabilidade do sistema
A modularidade é um dos pilares fundamentais da Arquitetura de Software sustentável. Sistemas bem modularizados permitem que times trabalhem de forma independente, que mudanças sejam isoladas e que o custo de evolução se mantenha previsível ao longo do tempo. Sem métricas objetivas, no entanto, a percepção de "bom design" fica refém de opiniões subjetivas.
1.2. Consequências de acoplamento alto e coesão baixa em projetos reais
Em projetos reais, acoplamento excessivo se manifesta como: mudanças em cascata (um ajuste em um módulo quebra dezenas de outros), dificuldade de testar unidades isoladamente e aumento exponencial do tempo de build. Coesão baixa, por sua vez, gera classes que "fazem de tudo" (god classes) e pacotes que misturam responsabilidades distintas, tornando a compreensão do sistema um pesadelo.
1.3. Como métricas objetivas complementam revisões de arquitetura
Métricas de modularidade funcionam como fitness functions arquiteturais — testes automatizados que validam se a arquitetura está evoluindo na direção desejada. Elas não substituem revisões humanas, mas fornecem alertas precoces e dados objetivos para discussões técnicas.
2. Conceitos fundamentais: acoplamento e coesão
2.1. Definição formal de acoplamento
Acoplamento mede o grau de interdependência entre módulos. Duas métricas principais:
- Afferent Coupling (CA): número de classes externas que dependem de uma classe.
- Efferent Coupling (CE): número de classes das quais uma classe depende.
Exemplo de análise de acoplamento em um módulo de pedidos:
Classe: OrderService
- CE: 5 (depende de OrderRepository, PaymentGateway, EmailService, InventoryClient, AuditLogger)
- CA: 8 (é usada por OrderController, OrderReportService, OrderSyncJob, etc.)
2.2. Definição formal de coesão
Coesão mede o quanto os métodos de uma classe estão relacionados. LCOM (Lack of Cohesion of Methods) é a métrica clássica:
- LCOM1: conta pares de métodos que não compartilham atributos.
- LCOM4: considera grafos de conexão entre métodos — idealmente, uma classe coesa tem LCOM4 = 1.
2.3. Trade-offs entre modularização extrema e pragmatismo
Modularização extrema pode levar a micro-classes com coesão alta mas acoplamento excessivo por composição. O equilíbrio está em aplicar o Princípio da Responsabilidade Única sem cair em fragmentação desnecessária.
3. Métricas clássicas e sua interpretação prática
3.1. CE e CA no contexto de pacotes
Para pacotes, CA representa quantos outros pacotes dependem dele (instability inbound), e CE quantos pacotes ele usa (instability outbound).
3.2. Instability Index e Abstractness
- Instability (I) = CE / (CE + CA): varia de 0 (totalmente estável) a 1 (totalmente instável).
- Abstractness (A): razão entre classes abstratas e total de classes no pacote.
3.3. Distância da sequência principal
A métrica D = |A + I - 1| mede o quão distante um pacote está da "sequência principal" — zona ideal onde pacotes estáveis são abstratos e instáveis são concretos. Quanto mais próximo de 0, melhor.
Pacote: br.com.orders.domain
- A: 0.4 (40% de classes abstratas)
- I: 0.6 (CE=6, CA=4)
- D: |0.4 + 0.6 - 1| = 0.0 → Zona ideal
Pacote: br.com.orders.infrastructure
- A: 0.0 (nenhuma abstração)
- I: 0.8 (muitas dependências externas)
- D: |0.0 + 0.8 - 1| = 0.2 → Zona de dor (concreto e instável)
4. Métricas de coesão: do LCOM à coesão conceitual
4.1. Variantes do LCOM
- LCOM1: conta pares de métodos sem atributos compartilhados.
- LCOM2: normaliza LCOM1 pelo total de pares possíveis.
- LCOM3: baseado em componentes conectados do grafo de atributos.
- LCOM4: similar ao LCOM3, mas inclui chamadas de método como conexões.
Exemplo de classe com baixa coesão:
Classe: OrderManager
Atributos: orderId, customerName, emailTemplate, smtpConfig
Métodos:
- createOrder() → usa orderId, customerName
- sendEmail() → usa emailTemplate, smtpConfig
- calculateDiscount() → usa orderId
- validateAddress() → usa customerName
LCOM4 = 3 (três componentes: {createOrder, calculateDiscount, validateAddress}, {sendEmail}, {})
→ Indicador de que a classe deveria ser dividida
4.2. Coesão de pacotes em bounded contexts
Em DDD, bounded contexts devem ter alta coesão interna (classes do mesmo contexto colaboram intensamente) e baixo acoplamento externo. Métricas de coverage (quantas classes do pacote são usadas internamente) e coherence (quão focadas são as dependências) ajudam a validar os limites.
4.3. Limitações em linguagens modernas
Herança e injeção de dependência podem distorcer métricas de coesão. Uma classe com DI bem feita pode ter LCOM alto (métodos que usam diferentes dependências injetadas), mas ainda assim ser coesa em termos de responsabilidade.
5. Ferramentas e técnicas para extrair métricas de código real
5.1. Análise estática com ferramentas consolidadas
- NDepend (C#): gera relatórios completos de acoplamento, coesão e zones de risco.
- SonarQube: oferece métricas de complexidade e duplicação, além de plugins para LCOM.
- jQAssistant (Java): permite consultas customizadas em grafos de dependência via Cypher.
5.2. Extração de grafos de dependência
Ferramentas como Structure101 e Dependometer constroem grafos direcionados onde nós são classes/pacotes e arestas são dependências. A partir desses grafos, calculam-se métricas estruturais.
5.3. Integração contínua como fitness functions
Métricas podem ser validadas em pipelines CI/CD:
Pipeline stage: "Arquitetura Fitness Functions"
1. Extrair métricas de acoplamento do módulo "payment"
2. Verificar se D < 0.3 para todos os pacotes
3. Verificar se LCOM4 <= 2 para classes do domínio
4. Falhar o build se thresholds forem violados
6. Interpretando resultados: thresholds e sinais de alerta
6.1. Thresholds recomendados para projetos enterprise
| Métrica | Valor Saudável | Alerta | Crítico |
|---|---|---|---|
| CA (classe) | < 20 | 20-50 | > 50 |
| CE (classe) | < 10 | 10-20 | > 20 |
| I (pacote) | 0.0-0.6 | 0.6-0.8 | > 0.8 |
| D | < 0.2 | 0.2-0.5 | > 0.5 |
| LCOM4 | 1-2 | 3-4 | > 4 |
6.2. Padrões de degradação comuns
- God Classes: LCOM4 > 5, CA > 30, CE > 15.
- Dependency Cycles: métricas de acoplamento mostram dependências circulares entre pacotes.
- Cyclomatic Entanglement: classes com alta complexidade ciclomática e alto acoplamento simultaneamente.
6.3. Correlação com DDD e anti-corruption layers
Em bounded contexts bem definidos, pacotes de domínio devem ter I próximo de 0 (estáveis) e A alto (abstratos). Anti-corruption layers, por outro lado, tendem a ter D elevado — o que é aceitável, pois são adaptadores que traduzem entre contextos.
7. Aplicando métricas para guiar decisões arquiteturais
7.1. Refatoração orientada por métricas
Priorize hotspots com base em uma combinação de métricas:
Hotspot identificado:
- Classe: PaymentGatewayIntegration
- CA: 45 (muitos dependentes)
- CE: 18 (muitas dependências externas)
- LCOM4: 5
Ação: Extrair interface PaymentGateway (aumenta A do pacote),
separar métodos de validação e notificação em classes distintas (reduz LCOM4)
7.2. Validando evolução arquitetural ao longo do tempo
Mantenha um histórico de métricas em cada release. Uma tendência de aumento no CE médio por classe ao longo de 3 sprints indica degradação arquitetural.
7.3. Estudo de caso: reduzindo acoplamento entre bounded contexts
Cenário: Contexto de "Pagamentos" e "Faturamento" compartilhavam 40% das classes. Após aplicar métricas:
- Antes: CA entre contextos = 34, I médio = 0.7
- Depois: CA = 8, I médio = 0.3 (após introduzir eventos de domínio e anti-corruption layer)
8. Armadilhas comuns e boas práticas
8.1. Não confundir baixo acoplamento com isolamento excessivo
Sistemas precisam de comunicação entre módulos. Acoplamento zero geralmente significa duplicação de lógica. O objetivo é acoplamento controlado e explícito, não inexistente.
8.2. Métricas como indicadores, não como regras absolutas
Um pacote com D = 0.4 pode ser perfeitamente aceitável se for um adaptador de infraestrutura. Contexto importa. Use métricas para gerar discussão, não para aprovar ou reprovar cegamente.
8.3. Combinando métricas quantitativas com revisão qualitativa
Métricas mostram o que está acontecendo; revisões de arquitetura explicam por que. A combinação de ambas é mais poderosa que qualquer uma isoladamente.
Referências
- NDepend - Metrics Definitions — Documentação oficial com definições detalhadas de CA, CE, LCOM e métricas derivadas.
- SonarQube - Metric Definitions — Catálogo completo de métricas suportadas, incluindo coesão e complexidade.
- Martin Fowler - Metrics — Reflexões sobre o uso e abuso de métricas em arquitetura de software.
- Robert C. Martin - The Principles of OOD — Artigo seminal sobre acoplamento, coesão e os princípios SOLID.
- jQAssistant - Dependency Analysis — Ferramenta open source para análise de dependências em grafos, com suporte a consultas customizadas.
- Structure101 - Architecture Metrics — Plataforma especializada em visualização e medição de modularidade em código real.