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