Coesão e acoplamento: as métricas fundamentais

1. Introdução: O que são Coesão e Acoplamento?

Coesão e acoplamento são duas métricas essenciais para avaliar a qualidade de um projeto de software. Elas medem, respectivamente, o quão bem organizadas estão as responsabilidades dentro de um módulo e o quão dependentes os módulos são entre si.

Coesão refere-se ao grau em que os elementos de um módulo (funções, métodos, classes) pertencem logicamente uns aos outros. Um módulo com alta coesão tem um propósito único e bem definido, enquanto um módulo com baixa coesão agrupa funcionalidades não relacionadas.

Acoplamento mede a força das conexões entre módulos. Baixo acoplamento significa que os módulos são independentes e podem ser alterados sem impacto significativo em outros. Alto acoplamento cria dependências rígidas, tornando o sistema frágil e difícil de modificar.

Essas métricas são fundamentais porque impactam diretamente a manutenibilidade, testabilidade e escalabilidade do software. Um sistema com alta coesão e baixo acoplamento é mais fácil de entender, modificar e testar.

2. Coesão: Tipos e Níveis

A coesão pode ser classificada em uma escala do melhor para o pior:

  • Coesão funcional (ideal): Todos os elementos contribuem para uma única função bem definida.
  • Coesão sequencial: A saída de uma parte serve como entrada para outra.
  • Coesão comunicacional: Elementos operam sobre os mesmos dados.
  • Coesão procedural: Elementos são agrupados porque são executados em sequência.
  • Coesão temporal: Elementos são agrupados porque ocorrem no mesmo momento.
  • Coesão lógica: Elementos realizam funções similares, mas não relacionadas.
  • Coesão coincidental (pior): Elementos são agrupados aleatoriamente, sem relação.

Exemplo de código

Baixa coesão (coesão coincidental):

class Utilitarios {
    fun calcularImposto(valor: Double): Double { ... }
    fun enviarEmail(destinatario: String, mensagem: String) { ... }
    fun gerarRelatorioPDF(dados: List<Dados>) { ... }
    fun validarCPF(cpf: String): Boolean { ... }
}

Alta coesão (coesão funcional):

class CalculadoraImposto {
    fun calcularImpostoRenda(valor: Double): Double { ... }
    fun calcularICMS(valor: Double): Double { ... }
    fun calcularISS(valor: Double): Double { ... }
}

class ServicoEmail {
    fun enviarEmail(destinatario: String, mensagem: String) { ... }
    fun validarDestinatario(email: String): Boolean { ... }
}

3. Acoplamento: Tipos e Impactos

O acoplamento também possui uma escala do pior ao melhor:

  • Acoplamento de conteúdo (pior): Um módulo modifica dados internos de outro.
  • Acoplamento comum: Módulos compartilham dados globais.
  • Acoplamento de controle: Um módulo controla o fluxo de outro.
  • Acoplamento de dados (melhor): Módulos trocam dados simples por parâmetros.

Exemplo: dependência direta vs. injeção de dependência

Alto acoplamento (dependência direta):

class PedidoService {
    private val repositorio = PedidoRepositorio() // instância direta

    fun salvar(pedido: Pedido) {
        repositorio.inserir(pedido)
    }
}

Baixo acoplamento (injeção de dependência):

class PedidoService(private val repositorio: PedidoRepositorio) {
    fun salvar(pedido: Pedido) {
        repositorio.inserir(pedido)
    }
}

// Uso:
val repositorio = PedidoRepositorio()
val service = PedidoService(repositorio)

4. Relação entre Coesão e Acoplamento

Existe uma relação inversa natural: alta coesão geralmente leva a baixo acoplamento. Quando um módulo tem um propósito único e bem definido, ele naturalmente depende menos de outros módulos para realizar suas tarefas.

Refatoração de um sistema monolítico

Antes (baixa coesão e alto acoplamento):

class SistemaFinanceiro {
    fun processarPedido(pedido: Pedido) {
        // Valida estoque
        // Calcula imposto
        // Processa pagamento
        // Envia email
        // Gera nota fiscal
    }
}

Depois (alta coesão e baixo acoplamento):

class ProcessadorPedido(
    private val validadorEstoque: ValidadorEstoque,
    private val calculadoraImposto: CalculadoraImposto,
    private val processadorPagamento: ProcessadorPagamento,
    private val servicoEmail: ServicoEmail,
    private val geradorNotaFiscal: GeradorNotaFiscal
) {
    fun processar(pedido: Pedido) {
        validadorEstoque.validar(pedido)
        val imposto = calculadoraImposto.calcular(pedido)
        processadorPagamento.processar(pedido.total)
        servicoEmail.enviarConfirmacao(pedido)
        geradorNotaFiscal.gerar(pedido, imposto)
    }
}

5. Medindo Coesão e Acoplamento na Prática

Métricas quantitativas

LCOM (Lack of Cohesion of Methods): Mede quantos pares de métodos não compartilham atributos comuns. Valores altos indicam baixa coesão.

CBO (Coupling Between Objects): Conta o número de classes externas que uma classe depende. Valores altos indicam alto acoplamento.

Ferramentas de análise estática

  • SonarQube: Fornece métricas de coesão e acoplamento
  • JDepend (Java): Calcula CBO, LCOM e outras métricas
  • NDepend (.NET): Análise avançada de dependências
  • PyMetrics (Python): Métricas para código Python

Interpretação dos resultados

  • LCOM < 0.5: Alta coesão (aceitável)
  • LCOM > 0.8: Baixa coesão (alarmante)
  • CBO < 5: Baixo acoplamento (ideal)
  • CBO > 15: Alto acoplamento (preocupante)

6. Estratégias para Melhorar Coesão e Reduzir Acoplamento

Aplicação de princípios SOLID

  • Single Responsibility Principle (SRP): Cada classe deve ter apenas uma razão para mudar
  • Dependency Inversion Principle (DIP): Dependa de abstrações, não de implementações concretas

Uso de interfaces e abstrações

// Definição da abstração
interface RepositorioPedido {
    fun salvar(pedido: Pedido): Long
    fun buscarPorId(id: Long): Pedido?
}

// Implementação concreta
class RepositorioPedidoBanco : RepositorioPedido {
    override fun salvar(pedido: Pedido): Long { ... }
    override fun buscarPorId(id: Long): Pedido? { ... }
}

// Uso desacoplado
class PedidoService(private val repositorio: RepositorioPedido) { ... }

Refatoração de classes "faz-tudo"

Identifique classes que fazem muitas coisas diferentes e divida-as em módulos coesos. Use o padrão Facade para simplificar interfaces complexas.

7. Relação com Outros Princípios e Padrões

Design Patterns

  • Strategy: Permite variar algoritmos sem acoplamento rígido
  • Observer: Desacopla objetos que precisam ser notificados
  • Factory Method: Desacopla a criação de objetos
  • Dependency Injection: Reduz acoplamento ao injetar dependências

DRY, KISS e YAGNI

  • DRY (Don't Repeat Yourself): Código duplicado reduz coesão
  • KISS (Keep It Simple, Stupid): Simplicidade favorece alta coesão
  • YAGNI (You Ain't Gonna Need It): Evite acoplamento desnecessário

Lei de Demeter

"Fale apenas com seus amigos imediatos." Isso reduz acoplamento ao evitar encadeamento excessivo de chamadas:

// Ruim (viola Lei de Demeter)
pedido.cliente.endereco.cidade

// Melhor
pedido.obterCidadeCliente()

8. Conclusão e Boas Práticas

Coesão alta + acoplamento baixo = código sustentável. Essa combinação produz sistemas que são:

  • Fáceis de entender: Cada módulo tem um propósito claro
  • Fáceis de modificar: Alterações têm impacto localizado
  • Fáceis de testar: Módulos independentes são mais testáveis
  • Fáceis de reutilizar: Módulos coesos são mais portáteis

Checklist para revisão de arquitetura

  • [ ] Cada classe tem uma única responsabilidade?
  • [ ] As dependências são injetadas, não instanciadas?
  • [ ] Métodos que não usam atributos da classe foram movidos?
  • [ ] As interfaces são pequenas e focadas (Interface Segregation)?
  • [ ] O código viola a Lei de Demeter?

Dicas para times

  1. Revise o acoplamento em code reviews — pergunte "essa dependência é necessária?"
  2. Use análise estática contínua — configure ferramentas como SonarQube
  3. Refatore pequenas partes — não tente resolver tudo de uma vez
  4. Documente dependências — mantenha um mapa de dependências do sistema
  5. Eduque o time — compartilhe conhecimento sobre boas práticas

Lembre-se: coesão e acoplamento não são absolutos. O objetivo não é eliminá-los completamente, mas encontrar o equilíbrio certo para o contexto do seu sistema.

Referências