# Complexidade Ciclomática: Delta de Teste e Risco de Rollout

URL: https://upstreamapi.com/pt/journal/complexidade-ciclomatica-risco-rollout
Type: blog
Locale: pt
Published: 2026-07-25
Updated: 2026-07-25

---

> Complexidade ciclomática delta prediz risco de rollout melhor que um score estático. Use-a para fazer gate de canary com base em complexidade, não só em taxa de erro.

Complexidade ciclomática conta o número de caminhos independentes através de uma função. O NIST coloca o limite seguro em 10 por função, e quase toda equipe força algo próximo disso no CI. O que esse número não te diz é qual desses caminhos vai realmente ser exercido pela canary que você está prestes a shipar para 5% do tráfego de produção, e é nessa lacuna que a maioria dos incidentes de rollout realmente começa. Trate complexidade ciclomática como um sinal de risco de rollout, não como uma regra de lint, e o número começa a valer a pena.

## O Que Complexidade Ciclomática Realmente Mede

A métrica de McCabe é uma contagem de grafo: nós, arestas, componentes conectadas. Todo `if`, `for`, `while`, `case`, e operador booleano adiciona um caminho. Uma função com complexidade 25 tem pelo menos 25 caminhos linearmente independentes. Não é opinião, é matemática de teste de caminho de base, e estabelece um piso sobre quantos casos de teste você precisaria para cobrir a função uma vez, do começo ao fim. O [detalhamento do Sourcegraph](https://sourcegraph.com/blog/cyclomatic-complexity-what-it-is-and-how-to-reduce-it) tem todos os limites: 1-10 risco baixo, 11-20 moderado, 21-50 alto, 50+ é o nível "precisamos conversar".

A maioria das equipes não atinge esse piso. Ferramentas de cobertura reportam cobertura de linha, não cobertura de caminho, então uma função pode mostrar 90% de cobertura enquanto metade de seus ramos nunca dispara no CI. Essa é a parte que ninguém coloca no template do PR, e é por isso que "testes passam" e "isso é seguro para shipar" são duas afirmações diferentes que acabam sendo tratadas como uma.

Revisamos post-mortems suficientes para notar o padrão: a revisão de incidente sempre tem uma linha para consumo do orçamento de erro e outra para tempo de detecção. Quase nunca tem uma linha para a complexidade da função alterada antes do diff chegar. Essa é uma lacuna no paperwork, não só na ferramenta.

![Close-up of hands reviewing a code diff with nested indentation on a laptop screen](https://fdzlnqpwsaniezitwiuw.supabase.co/storage/v1/object/public/cms-media/upstreamapi/2026-07/91e851-inline1.webp)

## Por Que um Número Flat de Complexidade Esconde o Verdadeiro Raio de Explosão

Aqui está a parte que todo dashboard de complexidade erra: duas funções podem postar a complexidade ciclomática idêntica e carregar riscos totalmente diferentes. Uma switch de 20 ramos despachando para handlers bem-testados não é o mesmo animal que uma função com quatro bolsões separados de condicionais aninhados espalhados por 80 linhas, cada um três níveis de profundidade. O CodeScene chama a segunda forma "bumpy road", e o nome é preciso. É a profundidade de aninhamento, não a contagem bruta de ramos, que consome a memória de trabalho e esconde o edge case que ninguém pensou em testar. [O artigo do CodeScene](https://codescene.com/blog/bumpy-road-code-complexity-in-context/) detalha a comparação e mapeia claramente para o que vemos nos dados de rollout.

Isso importa para nós especificamente porque uma canary não falha em complexidade média. Ela falha naquela função bumpy que foi tocada nesse diff, às 2 da manhã, sob carga que ninguém load-testou. Nos post-mortems que revisamos com plataforma teams, o padrão se repete: a função na causa raiz quase nunca tem a maior pontuação de complexidade no repo. Ela tem a maior complexidade *delta* no diff que foi shippado. Esse é um sinal diferente, e quase ninguém o instrumenta.

Um dashboard que diz que a complexidade do codebase está caindo não é o mesmo dashboard que diz que esse rollout, agora, tocou uma função que acabou de pegar três novos ramos aninhados. Um é um relatório de saúde que você lê uma vez por trimestre. O outro é um gate que você realmente quereria no pipeline de deploy, sentado ao lado do check de SLO, não enterrado em uma ferramenta de análise estática que ninguém abre durante um incidente.

Rollouts baseados em ring e baseados em porcentagem já assumem que alguns diffs são mais arriscados que outros; essa é a premissa inteira de uma canary. O que a maioria dos pipelines não faz é deixar a complexidade da forma do diff informar o quão rápido essa canary deveria se ampliar. É tratada como uma preocupação de revisão de código, depois esquecida no momento em que o PR faz merge.

## O Gate de CI Que Todo Mundo Define em 10, e Por Que Não Muda Nada

O conselho padrão, colocar complexidade ciclomática em 10 e falhar o build acima disso, é o passo "skip" que quase todo blog de engenharia recomenda e quase ninguém valida contra dados de incidentes reais. Não está errado, exatamente. Está incompleto de uma forma que deixa equipes acreditarem que trataram o risco quando na verdade apenas o movimentaram.

Dois modos de falha, ambos comuns em equipes com que conversamos:

- 
**Gaming o número.** Extract Method é o fix clássico, e funciona: complexidade ciclomática por função cai. Mas se a extração não reduz a contagem de decisão real, só a relocate por três funções em vez de uma, a complexidade do sistema está inalterada. O gate fica verde. O raio de explosão não encolhe, só fica mais difícil de ver em uma visão de diff única.

- 
**Ignorando a forma.** Um limiar flat trata um despachador de 12 ramos e um bumpy road de 12 caminhos como igualmente arriscados. Não são. O despachador provavelmente está bem; é roteamento mecânico. O bumpy road é onde seu próximo rollback vive, porque a pessoa revisando parou de rastrear estado três níveis de aninhamento para dentro.

Agentes de código de IA pioram isso antes de melhorar. Devin e agentes autônomos similares shippam PRs rápido, e "rápido" frequentemente significa adicionar um ramo em vez de refatorar o que já está lá. Esse é o caminho de menor resistência para um modelo otimizando para um test suite passando, não pela carga cognitiva de um revisor. Se sua equipe está fazendo merge de diffs autorais de IA em volume, complexidade delta por PR é uma métrica que você quer no dashboard antes de se tornar um achado de revisão de incidente, não depois.

![Two engineers reviewing a branching flowchart drawn on a glass whiteboard](https://fdzlnqpwsaniezitwiuw.supabase.co/storage/v1/object/public/cms-media/upstreamapi/2026-07/4479b1-inline2.webp)

## O Que a Complexidade Delta Realmente Te Diz Sobre a Carga de Teste

Esqueça o score absoluto por um segundo. O número que prediz risco de incidente é a mudança em complexidade introduzida por um único diff, referenciada cruzada contra se os testes tocando esse diff realmente cresceram para combinar.

Uma função que vai de complexidade 8 para 19 em um PR tem, pela matemática de teste de caminho de base acima, aproximadamente dobrado sua contagem mínima de testes necessários. Se o PR adicionou dois testes, você shippou um gap de cobertura disfarçado de run de CI passando. Esse gap não aparece até a canary atingir o slice de tráfego de 5% que exercita o ramo não-testado, e nesse ponto é um incidente, não um comentário de revisão de código perdido em uma thread de PR.

Essa é a lacuna de instrumentação que o AI Pilot da upstreamapi foi construído para fechar no lado do rollout: fazer gate da porcentagem de canary e hold time na complexidade delta do diff shippado, referenciada cruzada contra sua test delta, não só no SLO de taxa de erro downstream. Um SLO de taxa de erro te diz que algo já quebrou. Um gate de complexidade delta te diz que o diff era mais provável de quebrar algo antes de servir tráfego ao vivo, que é o único ponto onde essa informação ainda é acionável.

`// Gate de canary simplificado: ampliar lentamente em diffs baixo risco, hold em altos risco
function canaryStep(diff: DiffMetrics): CanaryDecision {
  const complexityRisk = diff.complexityDelta / Math.max(diff.testDelta, 1);

  if (complexityRisk > 3 && diff.slo.errorBudgetBurn > 0.1) {
    return { action: "hold", trafficPct: diff.currentTrafficPct };
  }
  if (complexityRisk > 3) {
    return { action: "extend_bake_time", bakeMinutes: 45 };
  }
  return { action: "advance", trafficPct: diff.currentTrafficPct + 10 };
}`
## A Pesquisa Realmente Concorda Sobre Isso?
Não, e vale a pena dizer claramente. Algumas pesquisas de praticantes contestam duramente complexidade ciclomática como preditor de defeito, argumentando que equipes que otimizam para um score menor frequentemente só relocate complexidade em algum lugar menos visível. [A crítica do GetDX](https://getdx.com/blog/cyclomatic-complexity/) faz esse caso e aponta equipes para métricas de experiência de desenvolvedor em vez disso. Não pensamos que esse argumento mata a métrica; pensamos que mata a métrica usada sozinha, como um score estático repo-wide, desconectado do diff e do rollout ao qual está anexado.

## Como Realmente Fazer Gate de Canary em Complexidade, Não Só Taxa de Erro

Três coisas, na ordem de quanto atrito elas adicionam a um PR:

- 
**Compute complexidade delta por diff, não por repo.** Médias repo-wide escondem a função que importa essa semana. Delta por PR é barato de computar na maioria das ferramentas de análise estática, e é o número que correlaciona com o que realmente quebra nas 48 horas seguintes.

- 
**Referência cruzada contra test delta, não contagem de testes.** Uma função com 40 testes e um jump de complexidade de 8 para 19 sem zero testes novos é um risco maior que uma função nova com complexidade 15 e cobertura matching desde o dia um.

- 
**Alimente o ratio em pacing de rollout, não só aprovação de merge.** Um diff de alta complexidade delta não precisa ser bloqueado em revisão; muita lógica de domínio legitimamente complexa (máquinas de estado, parsers de protocolo) sempre vai pontuar alto. Precisa de um canary mais lento e de uma coleira de orçamento de erro mais curta, que é uma decisão de rollout, não uma decisão de revisão de código.

Escreva o runbook antes do rollout, não depois da página. Se o engenheiro on-call abrindo o canal de incidente às 3 da manhã tiver que reverse-engineer por que um PR "limpo" acabou de queimar o orçamento de erro, a documentação falhou antes do código. Uma nota de uma linha no deploy log ("complexidade delta 11, test delta 1, held em 20%") custa nada de escrever e economiza os primeiros quinze minutos de toda revisão de incidente que se segue.

![Server room corridor at night with status LEDs and a lone engineer walking away holding a tablet](https://fdzlnqpwsaniezitwiuw.supabase.co/storage/v1/object/public/cms-media/upstreamapi/2026-07/a58df0-inline3.webp)

## Vale a Pena Rastrear, ou Só Outro Número de Dashboard?

Depende inteiramente de onde você a anexa. Complexidade ciclomática como uma linha de tendência de saúde repo é principalmente decoração: bom para um slide trimestral, inútil às 2 da manhã. Complexidade ciclomática como um delta por-diff, alimentado em pacing de canary e referência cruzada contra crescimento de teste, é um dos indicadores avançados mais baratos que encontramos para "esse rollout vai bip alguém".

O post-mortem vai perguntar como se parecia o orçamento SLO antes do rollback. Cada vez mais, o nosso também pergunta como se parecia a complexidade delta do diff que foi shippado. Vale a pena adicionar essa pergunta ao seu próprio runbook antes do incidente forçar a conversa, não durante a retro quando a resposta é um encolher de ombros e uma promessa de "adicionar testes melhores da próxima vez".

## FAQ

### Qual é a diferença entre complexidade ciclomática absoluta e delta?

Complexidade absoluta é o score da função atual. Delta é a mudança introduzida por um diff. Delta importa mais para risco de rollout porque prediz quais caminhos são novos, não testados, e provavelmente a serem exercidos sob carga de produção. Um dashboard repo-wide de complexidade é uma leitura de saúde; delta por PR é um sinal de risco acionável.

### Por que colocar um limite de CI em 10 não funciona?

Um limite estático trata todas as funções de alta complexidade como igualmente perigosas e permite gaming via Extract Method sem reduzir complexidade real do sistema. Além disso, ignora a forma: uma switch bem-estruturada de 15 branches é mais segura que um bumpy road de 10, mas o gate não diferencia. O que funciona é usar delta referenciado cruzado contra teste, anexado a decisões de rollout.

### Como isso se aplica a agentes de código de IA?

Devin e similares adicionam branches em vez de refatorar, aumentando delta de complexidade por PR mais rapidamente que diffs autorais humanos. Se você está mergeando diffs de IA em volume, rastrear complexidade delta por PR antes de se tornar um achado de incidente é crítico. Alimente esse sinal em pacing de canary mais lento.

### Qual é a matemática de teste de caminho de base?

Uma função com complexidade ciclomática N requer pelo menos N casos de teste para cobrir todos os caminhos independentes uma vez. A maioria das equipes só alcança cobertura de linha, então a função pode mostrar 90% de cobertura enquanto ramos inteiros nunca são exercidos no CI. Esse gap não aparece até produção.

### Como posso instrumentar complexidade delta no meu pipeline?

A maioria das ferramentas de análise estática (SonarQube, CodeScene, ESLint plugins) podem computar complexidade por função. Calcule a métrica antes e depois do diff, e alimente o delta em seu sistema de decision de canary. Passe o valor para seu orquestrador de rollout—não precisa bloquear no PR, precisa informar a pacing de canary.

### Qual é a relação entre bumpy road e risco de incidente?

Bumpy road—múltiplos bolsões de aninhamento espalhados por uma função—torna a revisão de código mais difícil e erros mais prováveis que um flat switch statement com a mesma complexidade. A profundidade de aninhamento, não o raw branch count, prediz bugs. Um diff que introduz dois novos níveis de profundidade é mais arriscado que um que apenas expande uma switch.