Os fundamentos que você ainda precisa ao programar com agentes
Revise código gerado por IA com confiança. Entenda os fundamentos web de HTTP, navegador, estado, segurança, depuração, Git e arquitetura.
Você precisa de fundamentos de programação suficientes para avaliar o código escrito por agentes, e não para digitá-lo, porque um código que funciona ainda pode ter um design ruim, ser inseguro ou lento.
A situação de sempre: o agente diz que terminou, a página carrega, os testes passam, e ainda assim você não conseguiria explicar o diff se um revisor perguntasse sobre ele. Essa dúvida é razoável. Geralmente ela aponta para uma lacuna específica no que você sabe.
Este artigo não trata de prompts nem de configuração de ferramentas. Para isso, veja como obter os melhores resultados com ferramentas de programação com IA e dicas e truques para programar com IA. Em vez disso, ele aborda sete fundamentos da web e associa cada um a um erro realista de agente e ao conhecimento necessário para identificá-lo.
Principais conclusões
- O
fetch()do navegador só rejeita quando a própria requisição falha, como em um erro de rede, então um código que nunca verificaresponse.oktrata um 404 ou 500 como sucesso. - Qualquer valor incluído no JavaScript do frontend é público, inclusive variáveis de ambiente que o framework expõe ao cliente.
- Esconder um botão não é autorização: o servidor precisa verificar as permissões em toda requisição.
git add -ppermite aceitar as alterações de um agente um hunk por vez, egit revertdesfaz um commit sem reescrever o histórico.
| Fundamento | Erro típico de agente | O que o identifica |
|---|---|---|
| HTTP | 200 com corpo de erro | Semântica dos status codes |
| Navegador | Listener sem cleanup | Ciclo de vida dos effects |
| Estado | Dados do servidor copiados para uma store | Fonte única da verdade |
| Segurança | Segredo no bundle do cliente | Saber o que vai para o navegador |
| Depuração | Erro engolido | Stack traces, DevTools |
| Git | Aceitar tudo de uma vez | Staging por hunk, revert |
| Arquitetura | Nova dependência para uma única linha | APIs da plataforma |
Por que status codes HTTP e cache ainda importam?
Status codes e cabeçalhos de cache são a forma como clientes, proxies e ferramentas de monitoramento sabem o que aconteceu com uma requisição. Um endpoint que retorna 200 OK com uma mensagem de erro no corpo informa a todos os clientes, caches e ferramentas de monitoramento que a requisição foi bem-sucedida. A RFC 9110 define 2xx como sucesso, então o rastreamento de erros e as novas tentativas (retries) não serão acionados. Mantenha a referência de status codes HTTP por perto ao revisar handlers.
O lado do cliente tem uma armadilha correspondente. O fetch() não rejeita em status de erro HTTP, apenas quando a própria requisição falha, então você precisa verificar Response.ok por conta própria:
const res = await fetch('/api/save', { method: 'POST', body });
if (!res.ok) throw new Error(`Save failed: ${res.status}`);
Agentes também costumam fazer polling de dados que raramente mudam. Para configurações ou listas de referência, Cache-Control (definido na RFC 9111) com revalidação via ETag (retornando 304 Not Modified, definido na RFC 9110) elimina as requisições redundantes. Dados que realmente mudam em tempo real são um problema à parte.
Como o navegador renderiza e trata eventos?
Event listeners e escritas no DOM têm ciclos de vida, e o navegador cobra o preço quando você os ignora. Um erro comum de agentes é adicionar um listener dentro de um effect sem cleanup e sem array de dependências. O React executa esse effect após cada renderização, então cada renderização adiciona mais um listener de resize. A correção retorna uma função de cleanup e declara suas dependências:
useEffect(() => {
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, [onResize]);
O cleanup retornado é executado antes da próxima execução do effect e na desmontagem do componente. O segundo padrão do navegador a identificar é o layout thrashing: um loop que escreve um estilo e depois lê offsetHeight, repetidamente. Cada leitura após uma escrita pode forçar um layout síncrono. A correção é fazer todas as leituras primeiro e as escritas depois.
Onde o estado deve ficar e como os dados devem fluir?
Cada dado deve ter um único dono. Copiar dados do servidor para uma store no cliente cria uma segunda fonte da verdade, e as duas cópias divergem assim que uma muda sem a outra. Uma versão típica de agente busca uma lista, coloca-a em useState ou em uma store do Zustand e a edita localmente enquanto um refetch atualiza outra cópia.
As orientações do React sobre estrutura de estado desaconselham duplicar estado. Para dados do servidor, uma camada de cache como TanStack Query ou SWR é dona dos dados e cuida da invalidação. Para qualquer coisa que você possa calcular a partir do estado existente, derive o valor durante a renderização.
Noções básicas de segurança: autenticação, XSS, segredos e entradas não confiáveis
A segurança no frontend depende de saber qual código roda no navegador e qual roda no servidor. Qualquer valor incluído no JavaScript do frontend é público. No Next.js, as variáveis de ambiente são exclusivas do servidor por padrão. Adicionar o prefixo NEXT_PUBLIC_ insere o valor da variável diretamente no bundle do cliente durante o build, então um agente que coloca esse prefixo em uma chave de API acabou de publicar a chave.
Esconder um botão na interface não é autorização. Qualquer pessoa pode chamar a API diretamente, então o servidor precisa verificar as permissões em toda requisição. Essa falha se enquadra no OWASP Top 10:2025, A01 Broken Access Control.
Atribuir uma entrada do usuário a innerHTML faz o navegador interpretá-la como markup, e isso inclui atributos como onerror, que executam JavaScript. textContent insere a mesma entrada como texto puro. O JSX escapa strings interpoladas por padrão, e dangerouslySetInnerHTML desativa essa proteção. O OWASP XSS Prevention Cheat Sheet cobre o restante.
Como depurar um código que você não escreveu?
Depurar significa encontrar a causa de um bug, e não fazer o sintoma desaparecer. Agentes frequentemente “corrigem” um erro envolvendo-o em um try/catch que o engole, ou adicionando optional chaining que esconde um valor undefined em vez de rastrear de onde ele veio. O erro some do console, mas o bug continua lá.
Antes de aceitar uma correção, leia o stack trace, reproduza o bug e consulte o painel Network do Chrome DevTools para ver o que a requisição e a resposta realmente continham. Alguns bugs só aparecem para usuários reais. O session replay mostra o que o código escrito pelo agente faz para esses usuários em produção, como um loop de retry martelando um endpoint ou um formulário que falha silenciosamente, e é aí que lacunas nesses fundamentos muitas vezes ficam visíveis.
Git o suficiente para revisar e desfazer as alterações de um agente
O Git é a forma de aceitar parte do trabalho de um agente e desfazer o resto. Agentes tendem a fazer edições abrangentes, e aceitá-las de uma só vez esconde alterações não relacionadas. Quatro comandos cobrem a maior parte do que você precisa:
git diff # read every change
git add -p # stage hunk by hunk
git restore -p src/x.ts # discard unwanted hunks
git revert <commit> # undo a committed change
git add -p pergunta sobre cada hunk antes de adicioná-lo ao stage, e git restore -p descarta hunks da working tree um de cada vez. git revert adiciona um novo commit que reverte um anterior. Isso o torna seguro em branches compartilhadas, enquanto o reset reescreve o histórico.
Arquitetura o suficiente para identificar uma solução superdimensionada
Uma boa arquitetura resolve o problema de hoje com o que o projeto já tem. Agentes frequentemente recorrem a uma nova dependência, ou a uma camada de factory ou strategy, para lidar com uma tarefa pontual. Instalar uma biblioteca de datas para formatar uma única data é um caso típico, já que Intl.DateTimeFormat faz isso nativamente. Quando um diff adiciona arquivos, pacotes ou abstrações, pergunte se o código existente poderia fazer o trabalho com menos arquivos. Se puder, rejeite a alteração ou peça uma menor.
Hábitos que mantêm você no controle
- Peça ao agente que explique suas escolhas. Se você não consegue avaliar a explicação, encontrou o próximo fundamento a estudar.
- Leia o diff antes de aceitar. Revise cada hunk, sempre.
- Faça pequenas alterações à mão. Digitar uma correção você mesmo mantém seu entendimento atualizado.
Os testes transformam o seu julgamento em verificações que rodam a cada alteração.
Conclusão
Agentes podem escrever o código, mas você é responsável pelo que ele faz. Aprenda a semântica do HTTP, o modelo de renderização do navegador, a posse do estado, as fronteiras de segurança, depuração, Git e a disciplina de manter as coisas pequenas, e você será capaz de rejeitar um diff ruim e explicar por quê. Da próxima vez que um agente entregar uma alteração, comece lendo o git diff e identifique uma decisão que você ainda não consegue explicar.
Perguntas frequentes
O axios tem o mesmo problema de erro HTTP que o fetch?
Não. Por padrão, o axios rejeita a promise para qualquer status de resposta fora do intervalo de 200 a 299, então um 404 ou 500 cai no bloco catch. A opção validateStatus controla esse comportamento e pode ser sobrescrita. Uma API que retorna 200 com uma mensagem de erro no corpo ainda é resolvida com sucesso tanto no axios quanto no fetch, então o servidor precisa enviar os status codes corretos.
É seguro colocar chaves de API em variáveis de ambiente do Vite?
Somente se a variável não tiver o prefixo VITE_. O Vite expõe variáveis com prefixo VITE_ ao código do cliente por meio de import.meta.env e as substitui estaticamente durante o build, então seus valores acabam no bundle do cliente. Variáveis sem prefixo não são expostas ao código do cliente. Assim como o NEXT_PUBLIC_ no Next.js, o prefixo opta pela publicação de um valor e não o protege, então segredos devem permanecer em um servidor.
Como renderizar com segurança HTML fornecido pelo usuário quando textContent não é suficiente?
Sanitize o HTML antes de inseri-lo. A OWASP recomenda o DOMPurify: passe a string por DOMPurify.sanitize e use o resultado com innerHTML ou dangerouslySetInnerHTML. Não modifique a saída sanitizada depois, porque alterações podem reintroduzir markup perigoso. Mantenha a biblioteca de sanitização atualizada, já que novas formas de contorná-la são descobertas regularmente. Editores de rich text e Markdown escrito por usuários são os casos típicos em que isso é necessário.
Por que meu effect do React roda duas vezes em desenvolvimento?
Em desenvolvimento, o Strict Mode do React monta os componentes uma vez a mais para expor effects sem cleanup. Isso não acontece em produção. Se a segunda execução criar listeners, requisições ou subscriptions duplicados, o effect precisa de uma função de cleanup que desfaça sua configuração. Remover o Strict Mode ou adicionar uma guarda com ref para pular a segunda execução apenas esconde o bug.