Como medir a saúde de uma base de código com métricas objetivas
1. Por que métricas objetivas são essenciais para a saúde do código
A saúde de uma base de código frequentemente é avaliada por sensações subjetivas — "esse módulo parece confuso" ou "a manutenção está demorando mais". Sem dados objetivos, decisões técnicas tornam-se apostas. Métricas quantificáveis transformam intuições em fatos mensuráveis, permitindo detectar degradação silenciosa antes que ela se transforme em dívida técnica crítica.
Ignorar métricas objetivas pode levar a custos exponenciais: um sistema com alta complexidade ciclomática e baixa cobertura de testes pode exigir semanas para implementar uma simples correção. Estudos mostram que o custo de corrigir um bug em produção é até 100 vezes maior do que corrigi-lo durante o desenvolvimento. Métricas objetivas fornecem o radar necessário para evitar esse cenário, orientando priorizações baseadas em dados, não em achismos.
2. Métricas de complexidade e manutenibilidade
A complexidade ciclomática (CC) mede o número de caminhos independentes em uma função. Quanto maior a CC, mais difícil testar e compreender o código. Um valor abaixo de 10 é considerado saudável; acima de 20, sugere refatoração urgente.
# Exemplo: cálculo de complexidade ciclomática (Python com Radon)
# Função com CC = 5
def validar_pedido(pedido):
if not pedido:
return False
if pedido.valor < 0:
return False
if pedido.status == 'cancelado':
return False
if pedido.desconto > 0.5:
return False
return True
O Índice de Manutenibilidade (MI) combina volume (linhas de código), complexidade (CC) e legibilidade (comentários). Um MI acima de 65 indica código facilmente mantível; entre 20 e 65, moderado; abaixo de 20, crítico.
A profundidade de herança (DIT) e o acoplamento entre objetos (CBO) revelam riscos de modificação em cascata. Uma classe com DIT > 5 ou CBO > 14 frequentemente quebra princípios de encapsulamento, exigindo testes extensivos a cada alteração.
3. Métricas de cobertura de testes e qualidade de testes
Cobertura de linha informa quantas linhas são executadas durante os testes, mas esconde buracos lógicos. A cobertura de ramo (branch coverage) é mais rigorosa: garante que cada condicional (if/else, switch) seja testada em todos os cenários.
# Exemplo: diferença entre cobertura de linha e de ramo
def calcular_desconto(valor, cliente_vip):
if cliente_vip: # Ramo 1: True
return valor * 0.9 # Linha executada
else: # Ramo 2: False
return valor * 0.95 # Linha executada
# Cobertura de linha: 100% se ambos os caminhos forem testados
# Cobertura de ramo: 100% apenas se True e False forem testados
A taxa de sucesso de testes (percentual de testes verdes) deve ficar acima de 95% continuamente. O tempo médio de execução de testes unitários não deve ultrapassar 10 segundos por suíte; acima disso, o feedback loop prejudica a produtividade.
A razão entre código de produção e código de teste (test ratio) ideal varia entre 1:1 e 1:3 (código de teste : código de produção). Razões muito baixas indicam testes insuficientes; muito altas, testes redundantes ou frágeis.
4. Métricas de duplicação e dívida técnica
A densidade de duplicação de código mede o percentual de linhas duplicadas em relação ao total. Valores acima de 5% indicam risco de inconsistências: uma correção em um trecho duplicado pode não ser replicada nos outros, gerando bugs silenciosos.
A dívida técnica estimada calcula horas necessárias para corrigir violações identificadas. Ferramentas como SonarQube convertem cada violação em horas de esforço. Se a dívida ultrapassar 20% do esforço disponível da equipe em um sprint, priorize a redução.
A proporção de código morto (funções, classes ou variáveis não utilizadas) deve ser mantida abaixo de 1%. Código morto aumenta a complexidade sem agregar valor, confundindo novos desenvolvedores e aumentando o tempo de build.
5. Métricas de estabilidade e evolução do código
A frequência de alterações por arquivo (churn) identifica hotspots — arquivos que mudam constantemente. Arquivos com churn alto e alta complexidade são candidatos a refatoração prioritária.
# Exemplo: análise de churn (simplificada)
# Arquivo: src/processador_pedidos.py
# Commits nos últimos 30 dias: 12
# Linhas de código: 450
# Churn relativo: 12/450 = 0.027 (2.7% do arquivo alterado por commit)
# Se CC > 20 e churn > 0.02, classificar como hotspot
A taxa de defeitos por módulo (bugs por 1000 linhas de código) deve ficar abaixo de 0.5 para sistemas maduros. A densidade de bugs (bugs por funcionalidade) complementa essa métrica, focando em impacto no usuário.
A idade média dos commits revela o frescor da base. Commits com mais de 6 meses em módulos críticos indicam estagnação ou medo de alterar. Idealmente, 80% dos arquivos devem ter sido alterados nos últimos 3 meses.
6. Métricas de conformidade com padrões e boas práticas
Violações de regras de estilo e linting por arquivo devem ser zero. Ferramentas como ESLint (JavaScript) ou Pylint (Python) podem ser configuradas para bloquear commits que excedam limites.
A taxa de aprovação em revisões de código (code review) indica maturidade do processo. Taxas acima de 90% de aprovação na primeira revisão sugerem que o código está alinhado aos padrões do time.
O percentual de código que segue a arquitetura definida (ex.: camadas MVC, Clean Architecture) pode ser verificado com ferramentas de análise estática. Violações de dependência (ex.: uma camada de UI chamando diretamente o banco de dados) devem ser tratadas como blockers.
7. Como integrar e monitorar métricas continuamente
Ferramentas como SonarQube, CodeClimate e Radon (para Python) automatizam a coleta de métricas. Configure quality gates no pipeline CI/CD que bloqueiem deploys se limites forem excedidos:
# Exemplo de quality gate (YAML para SonarQube)
quality_gate:
conditions:
- metric: coverage
op: LT
threshold: 80%
- metric: duplicated_lines_density
op: GT
threshold: 5%
- metric: code_smells
op: GT
threshold: 50
Dashboards com tendências semanais/mensais permitem visualizar a evolução da saúde. Um aumento gradual na dívida técnica ou queda na cobertura de testes acende alertas antes que se tornem problemas críticos.
8. Armadilhas comuns e como evitar métricas enganosas
Otimizar para uma única métrica isolada é perigoso. Por exemplo, reduzir a complexidade ciclomática pode levar a funções excessivamente fragmentadas, aumentando o acoplamento. Sempre analise métricas em conjunto.
Métricas que não refletem o contexto do domínio ou do time podem enganar. Um sistema financeiro pode exigir cobertura de ramo de 95%, enquanto um protótipo interno pode aceitar 60%. Ajuste thresholds com base na criticidade do sistema.
Equilibre métricas com visão qualitativa: revisões humanas, entrevistas com desenvolvedores e análise de incidentes em produção complementam os números. Métricas são ferramentas, não substitutas para o discernimento técnico.
Referências
- SonarQube Documentation: Metric Definitions — Lista completa de métricas suportadas pelo SonarQube, incluindo complexidade ciclomática, duplicação e dívida técnica.
- Radon: Code Metrics for Python — Documentação oficial do Radon para cálculo de complexidade ciclomática, índice de manutenibilidade e outras métricas em Python.
- CodeClimate: Engineering Metrics Overview — Guia prático sobre métricas de qualidade de código, cobertura de testes e dívida técnica com exemplos de dashboards.
- Google Testing Blog: Code Health Metrics — Artigo do Google sobre como usar métricas objetivas para manter a saúde do código em larga escala.
- Martin Fowler: Metrics and Measurements — Reflexão de Martin Fowler sobre armadilhas de métricas e como usá-las com sabedoria, incluindo exemplos de churn e cobertura.