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:
- Integrar métricas no pipeline de CI/CD com alertas, não bloqueios rígidos
- Definir metas contextuais: 80% de cobertura de branch para código de produção, 60% para protótipos
- Combinar métricas: cobertura + complexidade + acoplamento para visão holística
- Revisar tendências: mais importante que o número absoluto é a evolução ao longo do tempo
- 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
- JaCoCo - Java Code Coverage Library — Documentação oficial da ferramenta de cobertura para Java, com exemplos de configuração e interpretação de relatórios
- Istanbul - JavaScript Test Coverage Tool — Guia completo da ferramenta padrão para cobertura de código em projetos JavaScript/Node.js
- McCabe's Cyclomatic Complexity - IEEE Paper — Artigo seminal de Thomas McCabe (1976) definindo a métrica de complexidade ciclomática
- Coverage.py - Python Coverage Tool — Documentação oficial com exemplos de medição de cobertura de linha, branch e condição em Python
- SonarQube - Metric Definitions — Referência completa de métricas de qualidade, incluindo cobertura e complexidade, com thresholds recomendados
Referências
- JaCoCo - Java Code Coverage Library — Documentação oficial da ferramenta de cobertura para Java, com exemplos de configuração e interpretação de relatórios
- Istanbul - JavaScript Test Coverage Tool — Guia completo da ferramenta padrão para cobertura de código em projetos JavaScript/Node.js
- McCabe's Cyclomatic Complexity - IEEE Paper — Artigo seminal de Thomas McCabe (1976) definindo a métrica de complexidade ciclomática
- Coverage.py - Python Coverage Tool — Documentação oficial com exemplos de medição de cobertura de linha, branch e condição em Python
- SonarQube - Metric Definitions — Referência completa de métricas de qualidade, incluindo cobertura e complexidade, com thresholds recomendados