Métricas de qualidade de código: cobertura e complexidade ciclomática

1. Introdução às métricas de qualidade de código

A qualidade de software é um conceito subjetivo quando baseado apenas em impressões. Para torná-la objetiva e mensurável, surgem as métricas de qualidade de código — indicadores numéricos que permitem avaliar aspectos como manutenibilidade, legibilidade e confiabilidade. Sem métricas, equipes ficam reféns de achismos e revisões subjetivas.

Duas das métricas mais consolidadas são a cobertura de código e a complexidade ciclomática. A primeira mede o quanto do código é exercitado por testes automatizados; a segunda quantifica a complexidade do fluxo de controle. Juntas, oferecem uma visão poderosa sobre a saúde do código e os riscos de manutenção.

2. Cobertura de código: conceitos fundamentais

Cobertura de código é a porcentagem de código-fonte que é executada durante a execução de uma suíte de testes. O cálculo básico é:

cobertura = (linhas executadas / total de linhas) × 100

Existem diferentes tipos de cobertura:

  • Cobertura de linha: cada linha executada ao menos uma vez
  • Cobertura de branch: cada ramificação condicional (if/else) é testada
  • Cobertura de função: cada função/método é invocado
  • Cobertura de condição: cada subcondição dentro de uma expressão booleana é avaliada como verdadeira e falsa

Exemplo prático de código e sua cobertura:

function calcularDesconto(valor, clienteVIP) {
    if (valor > 100 && clienteVIP) {     // branch 1
        return valor * 0.9;               // linha A
    } else if (valor > 100) {             // branch 2
        return valor * 0.95;              // linha B
    }
    return valor;                         // linha C
}

Testando apenas com calcularDesconto(200, true), a cobertura de linha seria 67% (2 de 3 linhas executadas), mas a cobertura de branch seria 50% (apenas 1 de 2 branches testados). A limitação é evidente: alta cobertura de linha não garante que todos os caminhos lógicos foram validados.

3. Ferramentas e práticas para medição de cobertura

Ferramentas populares incluem:

  • JaCoCo (Java): gera relatórios detalhados com cobertura de linha, branch e complexidade
  • Istanbul/nyc (JavaScript): padrão para projetos Node.js
  • Coverage.py (Python): integrado ao pytest, suporta branch coverage

Na integração contínua, é comum definir gates mínimos, como "cobertura de branch ≥ 80%". Porém, é crucial evitar a armadilha de perseguir números cegamente. Estratégias saudáveis incluem:

  • Priorizar testes em código de alta complexidade
  • Aceitar cobertura menor em código de boilerplate (getters/setters)
  • Usar cobertura como guia, não como meta absoluta

4. Complexidade ciclomática: entendendo a métrica

Proposta por Thomas McCabe em 1976, a complexidade ciclomática mede o número de caminhos linearmente independentes em um programa. A fórmula é:

M = E - N + 2P

Onde:
- E = número de arestas (fluxos de controle)
- N = número de nós (blocos de código)
- P = número de componentes conectados (geralmente 1)

Na prática, a complexidade equivale ao número de pontos de decisão mais 1:

function validarEmail(email) {
    if (!email) return false;           // 1 decisão
    if (!email.includes('@')) return false; // 2 decisões
    const partes = email.split('@');
    if (partes.length !== 2) return false; // 3 decisões
    if (partes[1].length < 3) return false; // 4 decisões
    return true;
}
// Complexidade ciclomática = 4 + 1 = 5

Valores de referência comuns:
- 1-10: baixa complexidade, código simples
- 11-20: complexidade moderada, requer atenção
- 21-50: complexidade alta, difícil de testar e manter
- >50: extremamente complexo, candidato forte a refatoração

5. Relação entre complexidade e testabilidade

Alta complexidade ciclomática implica em mais caminhos a testar. Para uma função com complexidade M, o número mínimo de testes necessários para cobertura de branch completa é M. Se M=15, são necessários no mínimo 15 casos de teste.

Estudos mostram correlação direta entre complexidade elevada e densidade de defeitos. Módulos com complexidade > 20 têm probabilidade 3x maior de conter bugs. A refatoração orientada por redução de complexidade geralmente envolve:

  • Extrair métodos (quebrar funções grandes)
  • Substituir condicionais aninhadas por polimorfismo
  • Usar tabelas de decisão ou early returns

6. Métricas combinadas na prática

O verdadeiro poder surge ao combinar cobertura e complexidade. Um heatmap de risco pode classificar módulos em quatro quadrantes:

Complexidade \ Cobertura Baixa cobertura Alta cobertura
Baixa complexidade Baixo risco Mínimo risco
Alta complexidade Alto risco Risco médio

Exemplo de análise combinada:

Módulo: processarPagamento()
Complexidade ciclomática: 18
Cobertura de branch: 92%

Interpretação: Alta complexidade (18 caminhos) com boa cobertura (92%).
Risco residual: 8% dos caminhos não testados podem conter bugs críticos.
Ação: Priorizar testes para os 2 caminhos não cobertos.

7. Limitações e armadilhas comuns

Cobertura falsa positiva: testar sem afirmar comportamento. Um teste que executa o código mas nunca verifica resultados gera cobertura sem qualidade.

// Teste falso positivo
test('processa dados', () => {
    processarDados(entrada);  // executa, mas não verifica nada
});

Complexidade ignorando acoplamento: uma função simples (M=3) pode ser altamente acoplada a 10 outros módulos, tornando a manutenção difícil apesar da baixa complexidade.

Gamificação inadequada: equipes que perseguem 100% de cobertura frequentemente escrevem testes triviais (getters, setters) que inflam o número sem agregar valor real.

8. Conclusão e boas práticas recomendadas

Métricas de qualidade de código são ferramentas, não fins em si mesmas. A abordagem recomendada inclui:

  1. Integrar métricas no pipeline de CI/CD com alertas, não bloqueios rígidos
  2. Definir metas contextuais: 80% de cobertura de branch para código de produção, 60% para protótipos
  3. Combinar métricas: cobertura + complexidade + acoplamento para visão holística
  4. Revisar tendências: mais importante que o número absoluto é a evolução ao longo do tempo
  5. Usar métricas para guiar refatorações, não para punir desenvolvedores

A qualidade de código é uma jornada contínua. Métricas como cobertura e complexidade ciclomática iluminam o caminho, mas a decisão de onde e como melhorar cabe às pessoas que constroem o software.

FIM

Referências

Referências