Complexidade Ciclomática: o que refatorar a seguir

Resumo

A complexidade ciclomática mede o número de caminhos independentes numa função. Acima de 10 por função, o risco de bugs aumenta de forma consistente e documentada. Ferramentas como SonarQube, CodeScene e Code Climate calculam e monitorizam esta métrica automaticamente. A regra prática: não refatores tudo ao mesmo tempo. Combina a complexidade com a frequência de modificação e a cobertura de testes para decidir onde agir primeiro. O impacto em produção é o critério decisivo.

Diagrama de fluxo de controlo ilustrando caminhos independentes e complexidade ciclomática em código

Complexidade Ciclomática: como saber o que refatorar (ou construir) a seguir

A complexidade ciclomática mede o número de caminhos independentes numa função ou módulo. Um valor superior a 10 é um sinal claro: mais bugs, mais testes necessários, mais dificuldade de manutenção. Se não sabes por onde começar a refatorar o teu projeto, começa pelos módulos com maior complexidade ciclomática. Não é a métrica perfeita, mas é uma das mais diretamente acionáveis que existem.

TL;DR: A complexidade ciclomática conta os ramos de decisão no código. Acima de 10 por função, o risco de bugs aumenta de forma consistente. Ferramentas como SonarQube, CodeScene e Code Climate identificam automaticamente os pontos críticos. Prioriza pelo impacto em produção, não pelo valor absoluto da métrica.

O que é exatamente a complexidade ciclomática

Thomas McCabe definiu esta métrica em 1976, num artigo publicado nas IEEE Transactions on Software Engineering. A base teórica vem da teoria dos grafos: cada função pode ser representada como um grafo de fluxo de controlo, onde os nós são blocos de código sequencial e as arestas são transições possíveis entre eles.

A fórmula completa é M = E - N + 2P, onde E é o número de arestas, N o número de nós, e P o número de componentes conexos (geralmente 1 para uma função isolada). Na prática, existe um atalho mais direto: conta o número de pontos de decisão na função e adiciona 1.

def processar_pedido(pedido):          # M começa em 1
    if pedido.urgente:                 # +1 -> M = 2
        notificar_equipa()
    for item in pedido.itens:          # +1 -> M = 3
        if item.stock == 0:            # +1 -> M = 4
            pedir_reposicao(item)
    return pedido

Esta função tem complexidade 4. Gerível. Uma função real de 300 linhas com vários if encadeados, múltiplos try/except, e lógica de negócio acumulada ao longo de anos pode chegar facilmente a 35 ou 50.

Cada if, elif, else if, for, while, case, catch, and, or e operador ternário conta como um ponto de decisão adicional. Em Python, o radon calcula isto automaticamente. Em JavaScript, o ESLint tem uma regra dedicada. Em Java, o Checkstyle e o PMD têm detetores nativos.

Os limiares que realmente importam no dia a dia

Os valores de referência mais usados pela indústria:

O limite de 10 não é uma lei da natureza. É um ponto de equilíbrio empiricamente identificado em múltiplos estudos sobre bases de código industriais. Em projetos legacy com décadas de histórico, uma função de complexidade 20 pode ser o melhor resultado possível após uma refatoração parcial -- e já é um ganho real.

O que importa mais do que o valor absoluto é a tendência: complexidade a aumentar sprint após sprint é um sinal de que a base de código está a degradar-se mais depressa do que está a ser mantida. Um dashboard de tendência temporal vale mais do que um snapshot único.

Visualização dos caminhos de execução possíveis numa função com alta complexidade ciclomática

Por que a complexidade ciclomática está associada a bugs

A relação não é teórica. Estudos sobre bases de código C++, Java e Python em contextos industriais mostram uma correlação consistente: módulos com complexidade acima de 20 apresentam entre 3x e 5x mais defeitos por linha de código do que módulos com complexidade abaixo de 10.

O mecanismo é direto. Mais caminhos de execução significa mais combinações de entradas a cobrir em testes. Cada caminho não coberto é um bug potencial que só se manifesta quando um utilizador ou processo específico aciona esse caminho em produção. Não é uma questão de qualidade do programador -- é física do código.

Em equipas, a complexidade elevada tem um custo adicional: o tempo de onboarding. Um dev que entra num projeto e encontra uma função de complexidade 40 vai demorar duas a três vezes mais a compreender o que acontece do que se a função tivesse complexidade 8. Durante esse período de compreensão incompleta, a probabilidade de introduzir um bug ao modificar a função é significativamente mais alta.

Num estudo sobre o código-fonte do OpenBSD publicado em 2009, módulos com complexidade ciclomática média acima de 15 tinham uma densidade de bugs 4.6x superior à dos módulos com complexidade abaixo de 6. Estes números variam por linguagem e domínio, mas a direção é consistente em projetos de dimensão real.

Como calcular a complexidade ciclomática no teu projeto hoje

Antes de refatorar seja o que for, tens de saber onde estás. Sem medição, a refatoração é baseada em intuição -- que costuma apontar para o código que o dev mais detesta, não para o código que mais beneficiaria de atenção.

Não precisas de fazer nada à mão. As ferramentas calculam automaticamente:

Python: radon cc src/ -a -s retorna a complexidade média por módulo com classificação de A (baixo risco) a F (alto risco). Instala com pip install radon.

JavaScript e TypeScript: configura a regra "complexity": ["error", 10] no teu .eslintrc. Qualquer função que ultrapasse 10 falha o lint automaticamente.

Java e Kotlin: o Checkstyle tem a regra CyclomaticComplexity. O PMD tem a mesma regra na categoria design. Ambas configuram-se via XML e integram com Maven e Gradle.

Go: o gocyclo é a ferramenta padrão. gocyclo -over 10 . lista todas as funções acima do limiar definido.

Para projetos multi-linguagem ou equipas que querem visibilidade agregada num dashboard unificado, o SonarQube é a escolha mais completa. Integra com GitHub, GitLab, Bitbucket e Azure DevOps. O tier Community é gratuito para projetos open-source.

Quando refatorar, quando monitorar e quando ignorar

A complexidade ciclomática elevada não obriga a uma refatoração imediata. A decisão racional passa por três critérios combinados:

Frequência de modificação. Um módulo com complexidade 30 que ninguém toca há dois anos tem risco operacional baixo. Um módulo com complexidade 15 que é modificado toda a semana é mais urgente. O historial de commits é um input crítico nesta decisão.

Cobertura de testes. Alta complexidade com 90% de cobertura é tolerável -- os caminhos estão verificados. Alta complexidade com 20% de cobertura é uma bomba de tempo: qualquer modificação pode quebrar um caminho não testado e o bug só aparece em produção.

Impacto em produção. Módulos no caminho crítico (autenticação, pagamentos, processamento de encomendas) têm prioridade máxima. Scripts de migração que correm uma vez por trimestre podem esperar.

O CodeScene automatiza esta análise combinada. Em vez de mostrar apenas "este módulo é complexo", mostra "este módulo é complexo, muda 3x por semana, e tem 25% de cobertura de testes". Isso é acionável.

O que construir à volta desta métrica

Se trabalhas numa equipa pequena e queres um projeto interno com impacto imediato:

Dashboard de tendência de complexidade. Um script que corre radon ou eslint em CI, persiste os valores numa base de dados simples (SQLite ou Postgres), e renderiza um gráfico de tendência semanal por módulo. O valor prático: ver se a complexidade está a aumentar ou a diminuir ao longo dos sprints. Custo de construção: menos de um fim de semana.

Bot de pull request. Um GitHub Action que comenta automaticamente em PRs que introduzem funções com complexidade acima de 15. Não bloqueia o merge -- informa o autor e os revisores antes da revisão de código. Implementação: cerca de 80 linhas de Python mais um ficheiro de workflow YAML.

Refactoring advisor. Um script que passa o AST de uma função de alta complexidade para a API de um modelo de linguagem e pede sugestões de decomposição. Util para contextos com devs junior a aprender a escrever funções mais simples. Não substitui a revisão humana -- complementa-a nos primeiros anos de carreira.

Nenhum destes projetos requer um produto SaaS. São ferramentas internas com impacto mensurável e escopo limitado -- o tipo de projeto que se constrói, usa durante seis meses, e já valeu a pena independentemente de qualquer outra coisa.

Dashboard de análise de complexidade ciclomática por módulo com evolução temporal

Code Climate como alternativa ao SonarQube

Para equipas que não querem gerir a infraestrutura do SonarQube -- que tem uma curva de setup não trivial, especialmente com autenticação e configuração de regras por linguagem -- o Code Climate Quality é uma alternativa gerida na cloud.

A integração com GitHub faz-se em menos de 10 minutos. Cada função recebe um rating de A a F com base na complexidade ciclomática e noutras métricas de duplicação e estabilidade. O dashboard agrega por repositório e mostra tendências temporais. A integração com PRs pode bloquear merges que pioram o rating global, configurável por threshold.

O limite do tier gratuito: repositórios públicos apenas. Para repos privados, o custo começa em torno de $25-50 por mês para uma equipa pequena. Razoável se substitui horas de análise manual e discussões recorrentes em code review sobre o que priorizar.

O próximo passo prático: escolhe uma ferramenta, corre-a no teu projeto hoje, e identifica as três funções com maior complexidade ciclomática. Depois decide, para cada uma, se a frequência de modificação e a cobertura de testes justificam refatoração imediata ou apenas monitorização. Provavelmente já tens a resposta antes de terminar a análise.

Perguntas frequentes

O que é a complexidade ciclomática e como se calcula?
A complexidade ciclomática é uma métrica que conta o número de caminhos independentes numa função. Calcula-se somando o número de pontos de decisão (if, for, while, case, etc.) e adicionando 1. Uma função sem ramos tem complexidade 1. Ferramentas como radon (Python) ou ESLint (JavaScript) fazem este cálculo automaticamente.
Qual é o valor máximo aceitável de complexidade ciclomática?
O consenso da indústria situa o limite em 10 por função. Acima de 10, a probabilidade de bugs aumenta de forma mensurável. Acima de 50, a função torna-se praticamente impossível de testar de forma exaustiva. Estes valores são guidelines, não regras absolutas: o contexto do projeto e a cobertura de testes influenciam a decisão.
A complexidade ciclomática elevada é sempre razão para refatorar?
Não necessariamente. Complexidade elevada num módulo que não muda há dois anos tem risco baixo. Complexidade elevada num módulo modificado frequentemente e com baixa cobertura de testes é urgente. Combina sempre a métrica com o historial de commits e a cobertura de testes antes de decidir.
O SonarQube calcula a complexidade ciclomática automaticamente?
Sim. O SonarQube calcula a complexidade ciclomática para todas as linguagens suportadas sem configuração adicional. O tier Community é gratuito para projetos open-source. Para projetos privados é necessário o tier Developer ou superior, com integração direta com GitHub e GitLab.
Como a complexidade ciclomática ajuda a decidir o que construir a seguir?
Se geres uma base de código existente, começa pelos módulos com maior complexidade e maior frequência de modificação. Se estás a construir algo novo, define um limiar máximo de 10 no linting de CI desde o primeiro commit. Evitas acumular dívida técnica antes de a base de código crescer.
O CodeScene é melhor do que o SonarQube para priorizar refatorações?
Para priorização, sim. O CodeScene combina complexidade ciclomática com análise de historial de commits e dados de equipa. Identifica automaticamente os pontos de maior risco real, não apenas os mais complexos em teoria. O SonarQube é mais completo como plataforma de qualidade geral; o CodeScene é mais específico para decisões de refatoração.
A complexidade ciclomática funciona em todas as linguagens de programação?
O conceito é universal, mas a implementação varia por linguagem. A maioria das ferramentas de análise estática suporta as linguagens mais comuns: Python, JavaScript, Java, Go, C#, Ruby. Em linguagens funcionais como Haskell ou Erlang, a métrica é menos relevante porque o estilo favorece naturalmente funções curtas sem estado partilhado.