Quando o Revisor de Código com AI Erra com Total Confiança

Erik Treviño avatar
Erik Treviño
Cover for Quando o Revisor de Código com AI Erra com Total Confiança

Eu mergei 3 PRs na semana passada. O revisor de AI gerou 7 threads de revisão nos três.

5 eram melhorias válidas. Verificações de null safety que eu tinha deixado passar. Um padrão de assertion mais limpo. Formatação consistente em um utilitário de teste. Aceitei todas as 5 sem hesitar.

2 teriam quebrado todos os testes no CI. Rejeitei as duas.

A proporção — 5 boas, 2 perigosas — é a história completa da revisão de código com AI em infraestrutura de testes. A maioria das sugestões é válida. As que não são, são catastróficas. E a AI apresenta ambas com a mesma confiança.

O Padrão: Removendo Guards “Desnecessários”

As duas sugestões rejeitadas faziam a mesma coisa. Identificavam guards defensivos no código de teste e os sinalizavam como condicionais desnecessários que poderiam ser simplificados.

Aqui está uma versão sanitizada do que a AI viu:

// The original test code
test('verify user profile displays correctly', async ({ page }) => {
  await page.goto('/profile');

  // Check if the premium badge exists before asserting on it
  const premiumBadge = page.getByTestId('premium-badge');
  if (await premiumBadge.isVisible({ timeout: 3000 })) {
    await expect(premiumBadge).toHaveText(/Premium|Enterprise/);
  }

  // Always verify the core profile elements
  await expect(page.getByRole('heading', { name: /profile/i })).toBeVisible();
  await expect(page.getByTestId('user-email')).toBeVisible();
});

A sugestão da AI:

// AI suggestion: "Unnecessary conditional — the premium badge should
// always exist on the profile page. Remove the conditional check
// and assert directly."
test('verify user profile displays correctly', async ({ page }) => {
  await page.goto('/profile');

  // ❌ AI removed the guard — this breaks in staging and dev environments
  const premiumBadge = page.getByTestId('premium-badge');
  await expect(premiumBadge).toBeVisible();
  await expect(premiumBadge).toHaveText(/Premium|Enterprise/);

  await expect(page.getByRole('heading', { name: /profile/i })).toBeVisible();
  await expect(page.getByTestId('user-email')).toBeVisible();
});

A lógica da AI é limpa: se o teste navega para uma página de perfil, o badge premium deveria existir. O condicional é complexidade desnecessária. Reforce a assertion.

Correto — em produção. Completamente errado em staging, onde o badge premium está atrás de uma feature flag que só é habilitada no ambiente de produção. Completamente errado em dev, onde os dados de teste não incluem contas premium. Completamente errado durante janelas de deploy, onde o serviço de feature flag leva até 30 segundos para propagar após o deployment.

Por Que a AI Erra Nisso

A AI revisa código de forma isolada. Ela vê o arquivo de teste. Talvez veja o componente sendo testado. Ela não vê — e não consegue ver — o seguinte:

  1. Execução multi-ambiente. Esse teste roda em 3 ambientes (dev, staging, produção) com estados de feature flag diferentes, disponibilidade de dados diferente e timing de deployment diferente.

  2. Transientes de deploy. Durante um rolling deployment, o serviço de feature flag pode levar 30 segundos para sincronizar. Um teste que roda durante essa janela vê um estado diferente de um teste que roda 60 segundos depois.

  3. Variabilidade dos dados de teste. O ambiente de staging usa snapshots anonimizados de dados de produção que são atualizados semanalmente. Nem todo usuário no snapshot é um usuário premium.

  4. Design defensivo intencional. O guard não está lá porque o desenvolvedor foi preguiçoso. Está lá porque o teste precisa passar nos três ambientes. O guard é a funcionalidade.

A AI vê um condicional e pensa “code smell”. Um engenheiro que já foi acordado às 2 da manhã por uma falha de CI em staging vê um condicional e pensa “alguém aprendeu da maneira difícil”.

A Segunda Sugestão Rejeitada

A outra sugestão perigosa era o mesmo padrão aplicado a um valor de timeout:

// Original: defensive timeout for slow staging environment
await expect(page.getByTestId('dashboard-loaded')).toBeVisible({
  timeout: 15000, // Staging can take up to 12s after deploy
});
// AI suggestion: "Timeout of 15000ms is excessive. The default 5000ms
// should be sufficient for a visibility check. Reduce to default."
await expect(page.getByTestId('dashboard-loaded')).toBeVisible();

A AI está certa que 15 segundos é incomum para uma verificação de visibilidade. A AI não sabe que o ambiente de staging roda em uma infraestrutura menor, compartilha o banco de dados com o ambiente de QA e regularmente leva de 8 a 12 segundos para renderizar o dashboard após um deployment recente.

Reduzir o timeout para os 5 segundos padrão faria o teste falhar em staging em todo deploy. Passaria localmente. Passaria em produção. Falharia no único ambiente onde mais importa para capturar regressões antes que cheguem aos usuários.

A Árvore de Decisão: Aceitar / Validar / Rejeitar

Depois de encontrar esse padrão várias vezes, construí um framework de decisão para triar sugestões de revisão de código com AI em infraestrutura de testes:

Aceitar Imediatamente

Essas sugestões são quase sempre seguras para aceitar sem validação adicional:

  • Melhorias de null safety. Adicionar optional chaining, null checks ou type narrowing. Isso torna os testes mais robustos entre ambientes, não menos.
  • Melhorias de tipos. Refinar tipos, remover any, adicionar return types explícitos. Melhorias puras de corretude sem impacto comportamental.
  • Formatação e nomenclatura. Convenções de nomenclatura consistentes, ordenação de imports, clareza nas descrições dos testes.
  • Clareza nas assertions. Trocar de toBeTruthy() para toBeVisible(), usar matchers mais específicos. São assertions melhores que continuam passando em todos os ambientes.
// AI suggestion: use more specific matcher ✅ Accept
// Before
await expect(submitButton).toBeTruthy();
// After
await expect(submitButton).toBeEnabled();

Validar Antes de Aceitar

Essas sugestões podem estar corretas, mas precisam de verificação manual contra o comportamento multi-ambiente:

  • Refinamento de assertions. Trocar matchers flexíveis por matchers estritos. Correto na teoria, mas pode falhar em ambientes com dados diferentes.
  • Remoção de guards. Remover verificações condicionais, optional chaining em assertions ou caminhos de fallback. O guard pode existir por um motivo que a AI não consegue ver.
  • Mudanças de timeout. Reduzir timeouts, alterar intervalos de polling, remover waits explícitos. Esses são sensíveis ao ambiente por natureza.
  • Mudanças de locators. Trocar de getByTestId para getByRole. Melhor prática no geral, mas o locator baseado em role pode corresponder a elementos diferentes entre ambientes com estados de feature flag distintos.
// AI suggestion: tighten assertion ⚠️ Validate first
// Before: loose text matching
await expect(page.getByTestId('status')).toContainText('complete');
// After: exact text matching
await expect(page.getByTestId('status')).toHaveText('Complete');

// Is the text exactly "Complete" in all environments?
// Or does staging show "complete" (lowercase) or "COMPLETE" (uppercase)?
// Check before accepting.

Rejeitar

Essas sugestões são baseadas em premissas que não se sustentam em infraestrutura de testes multi-ambiente:

  • Qualquer coisa que assuma execução em ambiente único. Se a sugestão só funciona quando você imagina o teste rodando localmente, rejeite.
  • Remoção de lógica condicional sensível ao ambiente. Guards que verificam a existência de elementos antes de assertar em funcionalidades específicas do ambiente.
  • Remoção de lógica de retry para operações sensíveis ao deployment. Se um teste faz retry porque o deployment pode estar em andamento, esse retry é estrutural.
// AI suggestion: remove retry ❌ Reject
// Before: retry loop for post-deploy state
await expect(async () => {
  await page.reload();
  await expect(page.getByTestId('new-feature')).toBeVisible();
}).toPass({ intervals: [1000, 2000, 5000], timeout: 30000 });

// After (AI): "Unnecessary retry — just assert directly"
await expect(page.getByTestId('new-feature')).toBeVisible();

// The retry exists because this test runs against a deployment that
// takes up to 30 seconds to propagate. The AI has never seen a deploy.

A Regra dos 5 de 7

Aqui está a heurística prática: em um lote de sugestões de revisão de código com AI em infraestrutura de testes, aproximadamente 5 de 7 são melhorias válidas. As 2 restantes estão confidencialmente erradas sobre algo que a AI não consegue observar.

A proporção não é fixa. Varia conforme a complexidade do codebase e quantos ambientes seus testes cobrem. Mas o padrão é consistente: a maioria das sugestões é segura, e uma minoria quebraria seu pipeline de formas que só se manifestam fora do contexto observável da AI.

Essa proporção é o que torna a revisão de código com AI ao mesmo tempo valiosa e perigosa. Se a maioria das sugestões estivesse errada, você as ignoraria completamente. Se todas estivessem certas, você faria auto-merge. A divisão de 5 de 7 significa que você precisa avaliar cada uma individualmente — e o custo de aceitar as duas erradas é desproporcionalmente alto.

Um Checklist de Triagem para Revisão com AI

Quando um revisor de AI sugerir mudanças na sua infraestrutura de testes, passe por isto:

  1. Essa sugestão muda o comportamento entre ambientes? Se o teste roda em múltiplos ambientes com dados, flags ou estados de deployment diferentes, qualquer sugestão que reforce assertions ou remova guards precisa de validação manual.

  2. É uma mudança de timeout? Valores de timeout em infraestrutura de testes quase nunca são arbitrários. Eles codificam conhecimento sobre características de performance específicas do ambiente. Pergunte por que o valor atual existe antes de alterá-lo.

  3. Isso remove um condicional? Condicionais em código de teste que verificam a existência de elementos antes de assertar frequentemente são guards sensíveis ao ambiente, não code smells. Verifique se o elemento existe em todos os ambientes antes de remover a verificação.

  4. Essa sugestão sobreviveria a um deploy em staging? Mentalmente reproduza a sugestão contra seu ambiente mais lento e com menos recursos durante um rolling deployment. Se falharia, rejeite.

  5. Consigo verificar isso sem rodar em produção? Se a única forma de validar a sugestão é mergear e ver se o CI quebra em staging, esse é um forte sinal para rejeitar.

A Lição Mais Profunda

Revisão de código com AI é uma ferramenta. Uma boa ferramenta. As 5 sugestões que aceitei na semana passada trouxeram melhorias genuínas na qualidade do código — melhorias que eu poderia ter perdido em uma revisão manual. Não estou argumentando contra revisão de código com AI.

Estou argumentando contra tratar a revisão de código com AI como uma autoridade confiável sobre infraestrutura que ela nunca observou. A AI analisa código. Ela não analisa os ambientes em que esse código roda, os estados de deployment em que esses ambientes podem estar, ou o contexto histórico de por que um determinado guard existe.

O trabalho do revisor humano em um processo de revisão assistido por AI não é carimbar sugestões automaticamente. É ser a ponte de contexto — a pessoa que sabe que staging leva 12 segundos para renderizar, que a feature flag propaga lentamente, e que a verificação condicional na linha 47 existe porque alguém aprendeu da maneira difícil.

Não desligue a revisão de código com AI. Apenas pare de tratá-la como infalível. Cinco de sete. Aceite as cinco. Pegue as duas. Essa é a disciplina que mantém seu pipeline verde.