Todos os artigos

Quando ler o código da IA não é revê-lo

Ouvir o artigo (experimental) · 10:01

00:00 / 10:01

Um campo denso de linhas de código a escoar-se para a escuridão, com um único ponto de controlo iluminado onde algumas linhas são erguidas para a luz e lidas enquanto as restantes passam sem serem lidas

Consigo imaginar com nitidez um programador, porque uma versão dele está por todo o lado neste momento, com um fluxo de trabalho que parece responsável. Deixa o modelo escrever o código e lê o diff antes de o aceitar. Está a rever. Só que os diffs vão ficando mais longos, a leitura vai ficando mais rápida e, numa tarde qualquer, dá-se conta de que anda a aceitar código que já não conseguiria alterar se fosse preciso. Os projetos que a máquina construiu na maior parte são aqueles em que mal se orienta. Um projeto que escreveu à mão há três anos continua a parecer-lhe a sua própria casa. Algures pelo meio, ler deixou de ser rever, e nada o anunciou.

Às vezes ler é o trabalho todo

Eis onde a versão forte deste aviso se engana. Para uma boa fatia do que aceitas num dia, ler o diff é o trabalho inteiro, e corrê-lo tu próprio seria uma encenação. Um rename que o compilador já provou. Uma subida de versão de uma dependência. Um valor de configuração, uma alteração de texto, um comentário. Boilerplate que já escreveste cem vezes e reconheces pela forma. Um bloco de testes gerados, onde ler é de qualquer modo a passagem mais valiosa, porque é a leitura que apanha o teste que não afirma nada e mesmo assim fica verde.

Ler com atenção não é um instrumento fraco. Muito antes de tudo isto, a inspeção de código encontrava mais defeitos por hora do que os testes, e continua a encontrar. Portanto a questão não é "corre sempre o código". A questão é que ler e correr são cegos a bugs diferentes, e a maior parte da discussão que as pessoas têm é, no fundo, uma discussão sobre dentro de que cegueira estão a viver.

O verdadeiro teste é se consegues prevê-lo

A pergunta nunca foi ler contra correr. É se consegues prever o que o código faz sem o correr.

Para uma categoria de mudança consegues, e ler chega. Para outra, a correção não está no texto à tua frente. Vive nas entrelinhas, no entrelaçar de duas threads que o diff só te consegue mostrar uma de cada vez. Vive sob carga, na query que está bem em dez linhas e é fatal em dez milhões. Vive na fronteira, no off-by-one e no caminho de erro por onde o caso feliz passa a direito. Vive na ligação, na questão de saber se a nova peça está sequer ligada àquilo que a chama. Vive no espaço negativo, na verificação de autorização que não existe e na exceção que é silenciosamente engolida, ou seja, numa linha que nunca aparece no diff porque nunca foi escrita. Não há leitura que te leve a um bug que só existe em runtime.

A IA faz "parece certo" valer menos

A IA agudiza isto de uma maneira muito específica. Quem revê aprova código com base em sinais que fazem as vezes de correção. Lê-se como idiomático. Parece confiante. Assemelha-se a código que já funcionou antes. Um modelo de linguagem é uma máquina afinada para produzir exatamente esses sinais. É muito bom a parecer certo.

Por isso o atalho mais antigo da revisão, "isto parece bem", nunca foi prova tão fraca de que a coisa está bem. O polimento não é garantia. É o risco, porque aponta precisamente à parte de ti que ia deixar passar a mudança. Há investigação que mostra que pessoas a quem se deu um assistente capaz escreveram código menos seguro e sentiram-se mais certas de que era seguro. A confiança move-se no sentido oposto ao do perigo, e código gerado e limpo é a entrada mais desarmante que se pode pôr nas mãos de quem revê.

A máquina também consegue corrê-lo

Correr o código já não é apenas algo que fazes à mão. O agente pode corrê-lo por ti e devolver-te os logs, um screenshot, um parágrafo a explicar o que mudou e por que motivo está confiante. Isso é real, e para muitas mudanças é o trabalho todo: os testes ficaram verdes enquanto os viste correr, o screenshot mostra o botão no sítio certo, e acabaste mais cedo do que acabarias sozinho.

Mas repara no que aconteceu ali. Essa prova foi produzida pelo mesmo tipo de sistema que escreveu o código, apontada ao mesmo alvo, que é parecer certo. Um log de sucesso que não viste correr é uma afirmação, não um facto. Podes até pôr o agente de vigia, sobre os logs, a base de dados, as chamadas que saíram, e perguntar se está tudo a bater certo, e isso é uma alavanca real. É também aí que já apanhei um modelo a não olhar a sério, a responder com aquilo que esperava encontrar. Por isso entregar a execução ao agente não elimina a revisão. Desloca-a um nível acima, do diff para a prova. Quando te disser que os logs mostram X, abre os logs e encontra X. Trata o "funcionou, aqui está a prova" como uma afirmação a confirmar, do mesmo modo que confirmarias um número antes de o deixares ficar num parágrafo que um modelo escreveu. É a mesma verificação que um investigador salta quando uma citação inventada acaba num artigo publicado.

O que nos leva à parte que quase tem piada. Para muitas mudanças pequenas, a coisa mais rápida e mais fiável que podes fazer é corrê-la tu próprio. Ler uma parede de logs e um resumo confiante pode custar-te mais do que abrir a coisa e olhar com os teus próprios olhos. Correr não é a opção lenta e conscienciosa a que recorres quando ler falha. Metade das vezes é a rápida.

A falha em que a maioria bate é o volume

A falha em que a maioria realmente bate não tem nada de subtil. É o volume.

Passadas umas centenas de linhas, qualquer revisão humana degrada-se num relance, e um agente dá-te umas centenas de linhas sempre que pedires. O trabalho só de revisão é a forma mais cansativa que o ofício assume, porque estás a reconstruir a intenção de outra pessoa, num estilo que não é o teu, a um ritmo que não definiste. Por isso desmorona-se, em silêncio, em aceitação. Ninguém decide parar de rever. Começas a ver o código em vez de o compreender, e por dentro os dois são idênticos. A restrição que importa não é quanto o modelo consegue escrever. É quanto consegues realmente segurar. Deixa-o escrever para lá disso e não estás a rever, seja lá o que o ecrã diga que estás a fazer.

Ajusta a profundidade ao alcance do impacto

Isso transforma a disciplina em algo mais útil do que "ler com mais atenção". Ajusta a profundidade da revisão ao alcance do impacto, e ao quão reversível é a mudança.

Uma mudança pequena, contida e fácil de reverter merece uma leitura rápida e um merge. Uma migração, uma interface pública, uma eliminação, tudo o que toca em dinheiro ou em acessos, ganha o tipo lento de revisão: corre-a, segue-a passo a passo, tenta parti-la, quer sejas tu a fazê-lo, quer ponhas o agente a mostrar o seu trabalho e depois verifiques o que ele fez de facto, não importa quem ou o que a escreveu. Ler chega exatamente quando as formas de a mudança falhar são as formas que os teus outros gates já apanham: o compilador, os tipos, os testes que correm no CI, os corras ou não à mão. Deixa de chegar no momento em que a falha que te iria mesmo magoar é uma que só correr a coisa revelaria. Aplicar o mesmo escrutínio a cada diff não é diligência. É uma recusa de decidir onde está o risco.

Aquilo de que abdicas

Há um custo mais silencioso por baixo do custo técnico. Aceitar código que não compreendeste desloca-te, um clique de cada vez, de autor do teu produto para seu consumidor. Ficas com o crédito e ficas com a responsabilidade, o pager continua a tocar para ti, mas abriste mão da compreensão em que ambos assentam. Há quem descreva construir uma app numa tarde e sentir-se estranhamente vazio, e depois passar duas semanas a pô-la mesmo a funcionar e voltar a sentir-se ele próprio. Isto não é nostalgia. É a diferença entre ser dono da coisa que leva o teu nome e segurar uma cópia que não consegues abrir.

Construo ferramentas que apostam totalmente na geração. Uma transforma uma frase num plano e abre os seus próprios pull requests. Outra governa frotas inteiras de agentes. Não estou aqui para dizer a ninguém que escreva menos código com IA. Mas cada uma dessas ferramentas pressupõe, lá no fim, uma pessoa que revê à profundidade que a mudança merece, e um gate por onde só espreitas não é um gate. A máquina consegue produzir o diff. Não consegue produzir a parte em que o compreendeste.

Por isso revê a olho à vontade, para as mudanças em que o olho é tudo o que há. Só tens de manter uma linha clara entre o diff que compreendeste e o diff que apenas viste. O primeiro é teu. O segundo passa a ser teu no instante em que se parte, e não vai querer saber que parecia estar bem.

Perguntas a que este artigo responde

  • Ler o diff chega para rever código gerado por IA?

    Para uma boa fatia do que aceitas num dia chega, e corrê-lo seria uma encenação: um rename que o compilador já provou, uma subida de versão de uma dependência, um valor de configuração, uma alteração de texto, boilerplate que reconheces pela forma. Os testes gerados são o caso mais claro, porque é a leitura que apanha o teste que não afirma nada e mesmo assim fica verde. Deixa de chegar quando a correção não está no texto à tua frente: o entrelaçar de duas threads, a query que está bem em dez linhas e é fatal em dez milhões, o caminho de erro por onde o caso feliz passa a direito, ou a verificação de autorização que nunca aparece no diff porque nunca foi escrita.

  • O agente correu os testes e deu-me logs verdes e um screenshot, porque hei de verificar mais alguma coisa?

    Porque essa prova foi produzida pelo mesmo tipo de sistema que escreveu o código, apontada ao mesmo alvo, que é parecer certo. Um log de sucesso que não viste correr é uma afirmação, não um facto. Entregar a execução ao agente não elimina a revisão, desloca-a um nível acima, do diff para a prova: quando te disser que os logs mostram alguma coisa, abre os logs e encontra-a. Já apanhei um modelo a não olhar a sério e a responder com aquilo que esperava encontrar, e é a mesma verificação que um investigador salta quando uma citação inventada acaba num artigo publicado.

  • Quanto código de IA consigo mesmo rever antes de deixar de ser revisão?

    Menos do que um agente te vai entregar, porque ele dá-te umas centenas de linhas sempre que pedires e, passado esse ponto, qualquer revisão humana degrada-se num relance. O trabalho só de revisão é a forma mais cansativa que o ofício assume, porque estás a reconstruir a intenção de outra pessoa, num estilo que não é o teu, a um ritmo que não definiste. Ninguém decide parar de rever. Começas a ver o código em vez de o compreender, e por dentro os dois são idênticos.

  • Não é mais seguro rever tudo com a mesma profundidade?

    Não. Aplicar o mesmo escrutínio a cada diff não é diligência, é uma recusa de decidir onde está o risco. Ajusta a profundidade ao alcance do impacto e ao quão reversível é a mudança: uma mudança pequena, contida e fácil de reverter merece uma leitura rápida e um merge, enquanto uma migração, uma interface pública, uma eliminação ou tudo o que toca em dinheiro ou em acessos ganha o tipo lento, em que a corres, a segues passo a passo e tentas parti-la. Corrê-la tu próprio também não é a opção lenta e conscienciosa, porque abrir a coisa e olhar é muitas vezes mais rápido do que ler uma parede de logs e um resumo confiante.