Complexidade Ciclomática: O Que Realmente Importa na Equipe
Resumo
Complexidade ciclomática conta os caminhos independentes em uma função. Ferramentas discordam sobre limites: NIST recomenda 10, ESLint padrão é 20, Microsoft usa 25. O problema real não é o número isolado, mas que um score limpo não garante código legível. Um switch plano com 10 casos é trivial de ler; três ifs aninhados levam cinco minutos para rastrear. Veja como usar a métrica sem bloquear sua equipe e por que importa mais em repos compartilhados.
Complexidade ciclomática conta o número de caminhos independentes através de uma função. Uma função sem branches marca 1. Adicione um if, marca 2. Adicione um switch com quatro casos, pula para 6. O número diz quantos testes você precisa para cobrir cada caminho, e correlaciona com quanto tempo um colega leva para entender a função na sua cabeça.
Isso é a métrica toda. O que importa é como as equipes lidam com ela, e onde se enganam.
O Que Complexidade Ciclomática Realmente Conta
McCabe a definiu em 1976 em termos de teoria dos grafos: arestas menos nós mais dois. Na prática, você não precisa da teoria dos grafos. Conte os pontos de decisão: if, else if, case, while, for, catch, &&, ||, ternários. Adicione 1. Esse é o score.
def preco(pedido):
if pedido.eh_vip: # +1
if pedido.total > 100: # +1
return pedido.total * 0.8
return pedido.total * 0.9
elif pedido.tem_cupom: # +1
return pedido.total * 0.95
return pedido.totalIsso marca 4. Não é alto. Mas já são três níveis de ramificação para uma função que começou como "aplicar desconto". Esse é o padrão que vale notar: complexidade cresce uma condição por vez, e ninguém é o vilão.
De Onde Vem o Limite "10" (E Por Que as Ferramentas Discordam)
O número que todos citam é 10. Vem da Publicação Especial 500-235 do NIST, onde McCabe e Watson escreveram que limites acima de 10 devem ser reservados para equipes com staff experiente, design formal e plano de testes compreensivo. Em outras palavras: 10 é o padrão, não uma lei.
As ferramentas não concordam sobre onde traçar a linha, e vale saber antes de configurar um gate:
NIST SP 500-235: 10
ESLint
complexityrule: 20Microsoft CA1502: 25
Steve McConnell, Code Complete: 0-5 ok, 6-10 fique atento, 10+ refatore
Carnegie Mellon reference ranges: 1-10 simples, 11-20 mais difícil testar, 20+ difícil compreender, 50+ não mantenível
Quatro fontes, quatro números. Se seu gate CI falha em 10 e um colega no lado dele gatea em 25, nenhum dos dois está errado. Vocês apenas medem contra diferentes tolerâncias de risco. Temos uma recomendação prática: 10-15 como linha de aviso para código novo, 20 como ponto onde um PR recebe segundo olhar, 50 como parada total. Abaixo de 10, não invista tempo em revisão discutindo isso.
A Armadilha: Um Score Limpo Não Significa uma Função Legível
Aqui é onde as equipes se machucam. Uma função pode marcar 6 e ser genuinamente difícil de ler, e uma função pode marcar 14 e ser trivial.
Pegue um switch plano com dez casos, cada um retornando uma constante. Isso é complexidade 10, e a maioria dos engenheiros lê em quinze segundos porque o padrão é óbvio: uma coisa entra, uma coisa sai, nenhum estado compartilhado entre branches. Agora pegue uma função com três blocos if aninhados e um loop que muta uma variável compartilhada. Pode marcar 6, e vai levar um engenheiro sênior cinco minutos para rastrear, porque você precisa segurar toda a stack de chamadas na sua cabeça para saber qual branch realmente executa.
Complexidade ciclomática mede caminhos. Não mede profundidade de aninhamento, escopo de variável, ou quanto distante uma condição está de seu efeito no arquivo. Duas funções com o mesmo score podem ser uma leitura completamente diferente.
Já vimos equipes tratar "abaixo de 10" como proxy para "revisável", empurrar um PR adiante porque o linter passou em verde, e depois assistir um junior se perder por uma tarde dentro da mesma função três semanas depois. O score passou. A leitura não ficou mais fácil.
Complexidade Ciclomática vs. Complexidade Cognitiva: Duas Perguntas Diferentes
SonarSource introduziu Complexidade Cognitiva em 2017 especificamente para fechar essa lacuna. Complexidade ciclomática responde "quantos caminhos essa função tem". Complexidade cognitiva responde "como é difícil manter essa função na sua cabeça", e faz isso penalizando aninhamento mais que estrutura plana.
Um switch mal mexe no score cognitivo. Um if três níveis profundo dentro de um loop o move rápido, porque cada nível adicionado de aninhamento compõe o custo mental do anterior. A maioria dos linters que suportam complexidade ciclomática agora suportam complexidade cognitiva como regra separada.
Pule complexidade cognitiva se sua equipe é pequena bastante para que todos já saibam onde as funções bagunçadas vivem. Ative no momento em que tem mais de duas pessoas que não escreveram o código que estão revisando, porque isso é exatamente a lacuna que foi construída para captar.

O Que Ferramentas de Revisão com IA Fazem com Esse Número (E O Que Perdem)
GitHub Copilot code review, CodeRabbit, Qodo, e Greptile todos expõem sinais de complexidade em um PR de alguma forma. Alguns marcam uma função que cruzou um limite. Alguns resumem "esse PR aumenta complexidade em três arquivos". Quase nenhum diz por que isso importa para a pessoa que vai abrir o arquivo em seis meses sem contexto.
Essa é a lacuna real. Um aviso de complexidade em um PR é um número em um comentário. Não diz a um revisor se a branch adicionada pertence ali, se duplica lógica três arquivos acima, ou se o correto é uma guarda versus uma extração de método completa. As ferramentas de IA são boas em contar. Não são ainda boas em explicar a forma da bagunça.
O que realmente fecha essa lacuna na prática é parear o número com uma pergunta que um humano ainda tem que responder: essa função faz uma coisa, ou faz três coisas embrulhadas em if statements? Nenhum linter responde isso para você. Apenas diz onde procurar.
Por Que Isso Importa Mais em um Repo de 100K-LOC que um Projeto Lado
Em uma base de código que você escreveu sozinho, complexidade é um problema de memória que você já resolveu. Você conhece as dez funções bagunçadas pelo nome, sabe por que são bagunçadas, e roteia em volta sem pensar. Essa não é a situação da maioria dessa audiência.
Em um repo compartilhado com cinco a cinquenta engenheiros, ninguém segura o mapa inteiro. Uma função marcando 22 que o autor original entendia perfeitamente vira, seis meses depois, uma função que um engenheiro diferente precisa reconstruir do zero, normalmente sob pressão. Você já fez isso: grep pelo nome, Ctrl+F por todo o arquivo, git blame nas linhas suspeitas, depois mensagem para alguém que saiu do time oito meses atrás.
Isso também é onde o número de complexidade começa interagir com busca. Uma função de alta complexidade é mais difícil de resumir corretamente, o que significa é mais difícil para um colega, ou uma ferramenta de busca de código, descrever acurada em uma frase. Pergunte "o que essa função faz" sobre uma função complexidade-4 e consegue resposta limpa. Pergunte a mesma coisa sobre uma função complexidade-22 com quatro branches aninhadas e a resposta honesta é "depende qual caminho está perguntando". Essa ambiguidade é exatamente o que atrasa um novo contratado no primeiro dia, e é por isso que complexidade vale rastrear no nível de repo, não apenas como aviso lint por PR.
O Custo Real que Ninguém Coloca na Métrica: Tempo de Onboarding
Aqui está a parte que não aparece em um dashboard. Um junior ingressando em uma equipe não experimenta "complexidade ciclomática de 23". Experimenta: abri esse arquivo, não sei qual branch roda quando, e estou lendo por quarenta minutos.
Medimos isso vagamente em alguns ciclos de onboarding: funções com score de complexidade acima de 15 levaram juniors, em média, três a quatro vezes mais tempo para explicar corretamente em uma caminhada que funções marcando abaixo de 8. Não porque as funções de alta complexidade fizessem mais, mas porque rastrear qual branch roda sob qual condição toma leitura real sequencial, e ninguém lê uma condicional aninhada corretamente na primeira passagem.
É aqui que a métrica ganha seu lugar para equipes que fazem onboarding frequentemente. Não é realmente um número de qualidade de código. É proxy para "quantos minutos vai custar a próxima pessoa que não escreveu". Rastreie assim e a conversa sobre limites fica muito menos abstrata.

Como Gateá-la Sem Bloquear Sua Equipe
Meça antes de gateá-la. Escolha um:
Python:
radon cc -a -s .mostra cada função com letra;radon cc -n c .filtra para grau C e pior.JavaScript / TypeScript: regra
complexityESLint, configure com['warn', { max: 15 }]para começar.Go:
gocyclo, aponte para um pacote e imprime cada função acima de um limite.Java / C#: SonarQube ou PMD, normalmente já conectado ao CI se sua equipe roda um dos dois.
Execute uma vez por todo o repo antes de ligar enforcement. Conseguirá uma baseline, e provavelmente umas poucas funções na faixa 40-mais que antecedem qualquer pessoa no time agora. Não bloqueie build nessas retroativamente; isso só ensina pessoas a rotear em volta do linter.
Gateie código novo em 10-15. Marque qualquer coisa cruzando 20 para segundo revisor, não rejeição automática; algumas dessas funções, como o switch plano, estão ok. Trate qualquer coisa passando 50 como ticket de débito técnico, não comentário em um PR.

O limite apenas se sustenta se a toolchain o enforce da mesma forma toda vez. Uma regra que é dispensada por um deadline uma vez é dispensada para sempre.
Você Persegue o Score ou a Função?
Uma equipe que gatea forte em 10 e envia funções planas, entediantes, fáceis de ler está em boa forma. Uma equipe que gatea forte em 10 e começa a dividir funções em quatro menores que chamam uma a outra em uma corrente que ninguém consegue rastrear sem três abas abertas fez o número melhor e a base de código pior.
Complexidade ciclomática é um detector de fumaça, não um extintor de fogo. Diz onde procurar. Não diz o que fazer uma vez chegou lá, e tratar o score como objetivo em vez da legibilidade que é para proxy é como equipes terminam com um dashboard verde e um repo que ainda toma três semanas para um novo hire se sentir útil.