← Voltar ao blog

Uncle Bob não revisa mais o código da IA. Ele mede.

Por que o autor do Clean Code parou de ler o código dos agentes — e o sistema de métricas que torna isso possível sem virar fé cega.

Em abril de 2026, Robert C. Martin — o Uncle Bob, autor do *Clean Code* — soltou uma frase no X que dividiu a comunidade. Respondendo a uma pergunta do podcast do Wookash, ele disse, sem rodeios, que **não revisa mais o código escrito por agentes de IA**. Em vez de ler linha a linha, ele mede: cobertura de testes, estrutura de dependências, complexidade ciclomática, tamanho de módulos, mutation testing. A partir dessas métricas, infere a qualidade. O código em si, ele deixa para a IA.

O argumento dele é direto: humanos são lentos para ler código. Se a IA gera volume numa velocidade que nenhum revisor humano acompanha, insistir no review tradicional é criar um gargalo. Para ganhar produtividade, diz Bob, precisamos nos desligar do código e gerenciar de um nível mais alto.

A reação foi imediata. Para uns, é o futuro inevitável. Para outros, é irresponsabilidade pura. Este artigo não vai ficar em cima do muro: vou mostrar exatamente **quais validações** sustentam essa ideia, com exemplos em JavaScript, e onde está o furo que a crítica corretamente aponta.

---

## A tese: spec como código-fonte

A proposta do Uncle Bob não é "confiar cegamente na IA". É mudar *o que* o humano autora e revisa com cuidado.

A analogia é do próprio Uncle Bob: o compilador. Você não lê o código de baixo nível que um compilador gera — confia nele porque a **entrada** é rigorosa (seu código-fonte) e a **saída** é checada (o programa roda e passa nos testes). O compilador cuida da tradução; você cuida da lógica.

Na era dos agentes, o artefato que o humano autora deixa de ser o código e passa a ser a **especificação**: critérios de aceitação em linguagem de domínio (o famoso Given/When/Then do ATDD), regras de negócio, arquitetura. O agente escreve os "toques de tecla". Como ele mesmo resume: as specs serão co-autoradas por humanos e IA, mas com aprovação final defendida ferozmente pelos humanos.

> **O que é ATDD?** *Acceptance Test-Driven Development* é a prática de definir os **critérios de aceitação** de uma funcionalidade — exemplos concretos do comportamento esperado — *antes* de escrever o código, e transformá-los em testes automatizados. Enquanto o TDD tradicional testa unidades pequenas por dentro ("construí certo?"), o ATDD testa o comportamento completo do jeito que o usuário percebe ("construí a coisa certa?"). Esses critérios costumam ser escritos no formato **Given/When/Then** (Dado/Quando/Então): o contexto inicial, a ação que dispara o comportamento e o resultado esperado — tudo em linguagem de negócio, sem citar nomes de classe, endpoint ou tabela. É justamente esse contrato de aceitação que serve de âncora para o agente: ele define, de forma objetiva, o que está "pronto" e o que o agente não pode furar.

Soa elegante. Mas tem um problema prático sério.

## O furo: testes que passam e não testam nada

A crítica mais afiada veio do Hacker News, e ela é justa. Agentes de IA adoram escrever testes **perfunctórios**: testes que existem só para bater meta de cobertura. Eles mockam tantas entradas, métodos e efeitos colaterais que, no fim, não verificam comportamento nenhum.

Veja o problema na prática:

```js
// código sob teste
function calcularDesconto(valor, cliente) {
if (cliente.vip) return valor * 0.8;
return valor;
}

// teste que o agente "adora" escrever
test('calcularDesconto retorna um número', () => {
const resultado = calcularDesconto(100, { vip: true });
expect(typeof resultado).toBe('number'); // passa, mas não verifica NADA útil
});
```

Esse teste dá **100% de cobertura** na primeira linha da função se você só testar o caminho VIP, e mesmo assim não garante que o desconto está certo. Se a IA trocar `0.8` por `0.9`, o teste continua verde.

É aqui que mora o perigo de "medir sem ler": se a métrica pode ser enganada por testes vazios, você está cego, não no controle. A resposta para esse furo não é voltar ao review linha a linha — é usar as métricas **certas**, em conjunto, onde uma cobre a fraqueza da outra.

Vamos a elas.

---

## As validações recomendadas

A regra de ouro: **nenhuma métrica isolada basta**. Elas só funcionam como sistema.

### 1. Cobertura de testes (test coverage)

É o ponto de partida e o mais fraco sozinho. Cobertura mede qual percentual do código foi **executado** durante os testes — não se ele foi **verificado**.

```js
function classificarIdade(idade) {
if (idade < 18) return 'menor';
if (idade < 60) return 'adulto';
return 'idoso';
}

test('cobre o caminho adulto', () => {
classificarIdade(30); // executa 2 das 3 ramificações
// SEM nenhum expect! Cobertura sobe, verificação = zero
});
```

Ferramentas como o Istanbul (embutido no Jest) reportam cobertura por linha, por branch e por função. O detalhe crucial: o exemplo acima **aumenta a cobertura** mesmo sem um único `expect`. Por isso o próprio Bob trata 100% de cobertura como uma meta alcançável em coisas pequenas, mas péssima como critério de release.

**Posição no sistema:** condição necessária, nunca suficiente. Sirva-a sempre acompanhada da próxima métrica.

### 2. Mutation testing (o herói da história)

Esta é a métrica que fecha o furo da crítica. A ideia é genial pela simplicidade: a ferramenta introduz pequenas alterações no seu código — os **mutantes** — e roda a sua suíte de testes contra cada versão alterada.

- Se algum teste **falha** com o mutante, ótimo: o mutante foi "morto". Seus testes pegaram a mudança.
- Se **nenhum** teste falha, o mutante "sobreviveu": significa que aquele teste não verifica de verdade aquele comportamento.

O **mutation score** mede a *eficácia* da suíte, não a mera presença de testes.

Voltando ao exemplo do desconto. O Stryker lê o seu código-fonte, encontra pontos mutáveis e gera variações automaticamente. Um dos mutadores padrão é o de **operador aritmético**: ele troca o `*` por `/`. Então, a partir desta linha:

```js
function calcularDesconto(valor, cliente) {
if (cliente.vip) return valor * 0.8;
return valor;
}
```

O Stryker gera um mutante onde `valor * 0.8` vira `valor / 0.8` e roda a sua suíte contra ele. Para um cliente VIP comprando R$ 100:

- **Código original:** `100 * 0.8 = 80`
- **Mutante:** `100 / 0.8 = 125`

Agora repare no teste fraco que vimos lá em cima:

```js
test('calcularDesconto retorna um número', () => {
const resultado = calcularDesconto(100, { vip: true });
expect(typeof resultado).toBe('number');
});
```

Tanto `80` quanto `125` são números. O teste passa **nas duas versões** — então o mutante **sobreviveu**, e o Stryker te entrega a prova de que esse teste é decorativo: ele nunca verificou o valor do desconto, só o tipo do retorno. Um teste de verdade (`expect(resultado).toBe(80)`) **mataria** o mutante, porque falharia ao ver `125`.

Não é preciso referenciar o `0.8` manualmente em lugar nenhum: o Stryker descobre os pontos mutáveis sozinho, varrendo a árvore sintática do código. Outros mutadores padrão fariam, por exemplo, a condição `cliente.vip` virar sempre `true` ou sempre `false`, expondo testes que não cobrem os dois caminhos.

No ecossistema JS, a ferramenta padrão é o **Stryker**:

```bash
npx stryker run
```

```js
// stryker.conf.js
export default {
mutate: ['src/**/*.js', '!src/**/*.test.js'],
testRunner: 'jest',
thresholds: { high: 80, low: 60, break: 50 }, // CI quebra abaixo de 50%
};
```

Uma otimização importante para não tornar o CI lento: **differential mutation testing** — mutar apenas o código que mudou no commit, em vez do projeto inteiro. É exatamente o tipo de truque que torna "medir em vez de ler" viável em escala.

**Posição no sistema:** é o que separa teste real de teste de fachada. Sem mutation testing, cobertura é teatro.

### 3. Complexidade ciclomática (cyclomatic complexity)

Conta o número de caminhos independentes através de uma função. Na prática, comece em 1 e some 1 para cada ponto de decisão (`if`, `else if`, `&&`, `||`, `for`, `while`, `case`, `catch`, `?:`).

```js
// complexidade ciclomática = 1 (base) + 4 decisões = 5
function precoFrete(peso, regiao, cliente) {
let preco = 0;
if (peso > 10) preco += 20; // +1
if (regiao === 'norte') preco += 15; // +1
if (cliente.vip) preco *= 0.5; // +1
return preco > 0 ? preco : 10; // +1 (ternário)
}
```

Quanto maior o número, mais caminhos precisam de teste e mais difícil é manter. É um sinal direto de que o agente "encheu" uma função de lógica. A regra prática mais usada: acima de 10, acenda o alerta amarelo; acima de 15, exija refatoração.

No JS você impõe isso direto no ESLint, sem ferramenta extra:

```js
// eslint.config.js
rules: {
'complexity': ['error', 10], // falha o build se passar de 10
}
```

**Posição no sistema:** alarme de "aqui a IA exagerou". Funções complexas são onde bugs se escondem e onde a próxima métrica vai gritar.

### 4. A métrica CRAP (Change Risk Anti-Patterns)

Esta é a mais elegante, porque **combina** complexidade e cobertura numa fórmula só. A intuição: um trecho de código só é realmente perigoso quando é **complexo E mal testado** ao mesmo tempo.

A fórmula:

```
CRAP(m) = complexidade(m)² × (1 − cobertura(m))³ + complexidade(m)
```

Onde a cobertura é um valor de 0 a 1. Veja como ela se comporta:

```js
function crap(complexidade, cobertura) {
return (
complexidade ** 2 * (1 - cobertura) ** 3 + complexidade
);
}

crap(10, 0.0); // 10² × 1³ + 10 = 110 -> PERIGOSO (complexo e sem teste)
crap(10, 1.0); // 10² × 0³ + 10 = 10 -> OK (complexo, mas 100% coberto)
crap(10, 0.8); // 100 × 0.008 + 10 ≈ 10.8 -> aceitável
crap(5, 0.0); // 5² × 1³ + 5 = 30 -> no limite (simples, mas sem teste)
```

Repare na beleza disso:

- Código complexo, mas **muito bem testado**, tem CRAP baixo. Está sob controle.
- Código simples, mesmo sem teste, tem CRAP moderado. O risco é pequeno.
- Só o que é complexo **e** mal coberto explode o número.

O limiar mais citado é **CRAP > 30**: acima disso, o método entra na fila de refatoração. Em vez de revisar tudo, você revisa exatamente onde o risco está concentrado. É a métrica que transforma "validar sem ler" de fé cega em priorização inteligente.

### 5. Estrutura de dependências (dependency structure)

Aqui validamos se as setas de dependência apontam na direção certa. Numa Clean Architecture, a Regra da Dependência diz que tudo aponta para dentro: o domínio nunca conhece a infraestrutura.

O problema é que um agente, sem supervisão, importa o que for mais conveniente — e isso fura a arquitetura silenciosamente. A solução são **testes de arquitetura**: regras automatizadas que falham o build se alguém importar o que não devia.

No JS, o **dependency-cruiser** faz isso muito bem:

```js
// .dependency-cruiser.js
module.exports = {
forbidden: [
{
name: 'dominio-nao-conhece-infra',
severity: 'error',
from: { path: '^src/domain' },
to: { path: '^src/infra' }, // domínio importando infra = build quebra
},
],
};
```

```bash
npx depcruise src --config .dependency-cruiser.js
```

**Posição no sistema:** protege as decisões arquiteturais que você, como humano, defende ferozmente. O agente escreve dentro das cercas; o teste de arquitetura é a cerca.

### 6. Detecção de ciclos e de duplicação

Dois cheiros clássicos que agentes introduzem sem perceber.

**Ciclos de dependência:** o módulo A importa B, que importa A. Quebra a testabilidade e o entendimento. O mesmo dependency-cruiser detecta:

```js
{
name: 'sem-ciclos',
severity: 'error',
from: {},
to: { circular: true },
}
```

**Duplicação:** a IA tende a recriar lógica em vez de reusar a existente. O **jscpd** (Copy/Paste Detector) acha blocos repetidos:

```bash
npx jscpd src --threshold 5 # falha se duplicação passar de 5%
```

**Posição no sistema:** gates automáticos baratos que pegam dois dos vícios mais comuns de código gerado.

### 7. Tamanho de módulos e funções

A métrica mais simples, e surpreendentemente eficaz. Funções e arquivos pequenos são um proxy de coesão e contêm a tendência do agente de "plopar" código em qualquer lugar.

```js
// eslint.config.js
rules: {
'max-lines-per-function': ['error', 40],
'max-lines': ['error', 300],
'max-params': ['error', 4],
}
```

**Posição no sistema:** força a IA a decompor. Função grande é onde complexidade e duplicação se acumulam — cortar pela raiz é mais barato que corrigir depois.

---

## Montando o pipeline na prática

Isoladas, essas métricas são relatórios. Juntas e automatizadas, viram um **portão de qualidade** que roda sozinho. O fluxo tem quatro passos, cada um com um responsável claro:

1. **Humano — escreve a spec.** Critérios de aceitação em linguagem de domínio (Given/When/Then), sem citar classe, endpoint ou tabela.
2. **Agente — gera testes e código** a partir da spec.
3. **CI — roda os gates**, do mais barato ao mais decisivo:
- cobertura mínima (necessária, não suficiente)
- **mutation score** acima do limiar (a verificação de verdade)
- complexidade ciclomática e CRAP dentro do limite
- testes de arquitetura (sem importação proibida, sem ciclos)
- duplicação e tamanho sob controle
4. **Humano — entra só onde a métrica aponta.** Gate vermelho barra o merge; você lê apenas o trecho sinalizado, não o diff inteiro.

Vale notar que o próprio Bob está colocando isso em prática com ferramentas próprias — ele mantém repositórios públicos com um mutation tester e um analisador de CRAP para seus projetos em Java, além de skills para Claude Code. Não é teoria de palco; é o workflow que ele usa.

Num setup com subagents e Git worktrees, dá para ir além: um subagent dedicado a rodar e interpretar as métricas, devolvendo ao agente executor um relatório do que precisa melhorar antes mesmo de o código chegar a você.

---

## Então, ele está certo?

Em parte. A proposta brilha onde o código é **bem especificado** e o **volume é alto**: CRUD, integrações, refatorações mecânicas. Nesses casos, review linha a linha é desperdício de um recurso caro (atenção humana sênior), e o conjunto de métricas dá uma rede de segurança mais consistente do que o olho cansado de um revisor às cinco da tarde.

Mas o "medir em vez de ler" tem limites honestos. Arquitetura nova, requisitos ambíguos, decisões de produto com implicação de compliance — nada disso aparece num mutation score. Uma suíte de métricas perfeita ainda pode estar validando, com excelência, a solução **errada** para o problema.

A síntese, então, não é "pare de ler código". É: **suba o nível em que você aplica seu cuidado.** O humano autora e defende a spec e a arquitetura; as métricas, em conjunto, garantem que o agente respeitou os dois. Mutation testing cobre o furo da cobertura. CRAP prioriza onde olhar. Os testes de arquitetura protegem o desenho. É isso que o Uncle Bob chama de camada de disciplina para a era da spec-como-código-fonte.

E talvez seja essa a habilidade que define o engenheiro sênior em 2026: não escrever o código mais rápido que a máquina — isso é impossível — mas construir o sistema de validação que faz o código da máquina ser confiável.

---

## Referências e fontes de estudo

**A fala que originou o debate**

- Post do Uncle Bob no X (abr/2026), respondendo ao podcast do Wookash, onde ele afirma não revisar código de agentes e medir métricas no lugar: <https://x.com/unclebobmartin/status/2044114698451476492>
- Post do Uncle Bob sobre a ferramenta de mutation testing que ele construiu com o Claude (roda cobertura, muta cada operador coberto e avalia os mutantes sobreviventes): <https://x.com/unclebobmartin/status/2026015551009460727>

**O trabalho do Uncle Bob sobre disciplina com agentes**

- `empire-2025` — o projeto onde ele desenvolveu e refinou a aplicação de ATDD contra agentes de IA: <https://github.com/unclebob/empire-2025>
- Repositórios públicos do Uncle Bob, incluindo o mutation tester e o analisador de CRAP para os projetos Java dele: <https://github.com/unclebob?tab=repositories>
- `swarm-forge` — ferramenta dele para coordenar múltiplos agentes em Git worktrees, com o fluxo specifier → coder → refactorer → architect: <https://github.com/unclebob/swarm-forge>
- *Clean AI: Agentic Discipline* — série em vídeo no Clean Coders sobre as disciplinas de teste e refatoração usando agentes como o Claude Code: <https://cleancoders.com/episode/agentic-discipline-1>

**Fundamentos sobre as métricas**

- "Mutation Testing" — artigo clássico do Uncle Bob no Clean Code Blog, explicando o conceito com o pitest (Java): <https://blog.cleancoder.com/uncle-bob/2016/06/10/MutationTesting.html>
- Stryker Mutator — ferramenta de mutation testing para JavaScript/TypeScript: <https://stryker-mutator.io/>
- dependency-cruiser — validação de regras de dependência e detecção de ciclos em JS/TS: <https://github.com/sverweij/dependency-cruiser>
- jscpd — detector de código duplicado: <https://github.com/kucherenko/jscpd>

**O outro lado do debate**

- "Uncle Bob: It's Over" — discussão no Hacker News, incluindo a crítica de que agentes escrevem testes perfunctórios que mockam tudo e não testam nada: <https://news.ycombinator.com/item?id=47998601>

**Disciplina derivada da abordagem (comunidade)**

- `disciplined-agentic-engineering` (DAE) — plugins para Claude Code inspirados na abordagem do Uncle Bob a partir do empire-2025 (dois fluxos de teste, regra anti-vazamento de spec, differential mutation): <https://github.com/swingerman/disciplined-agentic-engineering>

// COMENTÁRIOS

Deixe seu comentário

0/40
0/2000

Comentários (0)

Nenhum comentário ainda. Seja o primeiro.