Não gosto de falar com robôs
Eu tenho um telefone Android, mas desativei os comandos de voz. Nunca tive uma Alexa. Não tenho certeza do que o Siri faz, exatamente, porque consegui passar pela vida sem ele me encontrar uma playlist. Então, quando as ferramentas de codificação de IA se tornaram difíceis de ignorar, eu fui me convencendo lentamente. Por um tempo, fiz o que alguns chamam de “delegação única”: trabalhando como sempre trabalhei, ocasionalmente delegando uma parte do trabalho à IA e depois esperando para ver o que voltava. É uma abordagem muito legal para pessoas que gostam de apostar.
Para mim, parte do atrito era a interface de chat em si. A ideia de ter uma conversa com o modelo parecia tola. Eu construo coisas. Não negocio com caixas de texto.
Mas o desenvolvimento orientado por especificações não funciona assim. Você define o problema com precisão, diz ao modelo quais arquivos considerar, descreve o resultado desejado, vai e volta conversando sobre o plano antes que a IA gere uma linha de código. Os modelos de IA de hoje são rápidos em codificar, mas ainda perigosamente inconsistentes na tomada de decisões de alto nível. Quando você trabalha com IA, seu trabalho é transformar seu julgamento de design em uma especificação escrita, para que você possa dar à IA uma lista de passos de baixa decisão que correspondam ao que você faria, se fosse digitar o código você mesmo. Para acertar essa especificação, você tem que negociar.
Eu aprendi isso nos últimos seis meses. E me tornei bastante bom nisso. Raramente/nunca escrevo código manualmente, no entanto, estou entregando mais funcionalidades do que nunca entreguei antes. E como não estou delegando o design de software ao modelo, estou aplicando os mesmos padrões de qualidade que aprendi a manter em minha carreira de 25 anos construindo produtos digitais.
O que eu não esperava era o quão difícil seria ensinar isso.
Já sou Picasso?
Existe uma noção (entre a alta gestão?) de que as ferramentas de LLM aumentarão a produtividade de todos os engenheiros. Na prática, não tenho visto isso. O que vejo são engenheiros experientes e adaptáveis conseguindo muito mais do que produziam antes, enquanto outros produzem o mesmo ou menos.
Esta não é uma boa situação para quem está começando. As ferramentas de IA para codificação parecem simples porque suas interfaces são apenas caixas de texto. Mas você não se torna Picasso ao espalhar tinta arbitrariamente em uma tela em branco.
Esta não é uma boa situação para pessoas que estão começando. As ferramentas de codificação com IA parecem simples porque suas interfaces são apenas caixas de texto. Mas você não vira Picasso só por borrar tinta em uma tela em branco.
Síndrome da implementação paralela
Eu me importo com os engenheiros juniores por muitas razões, e não menos importante, o fato de que eles são minhas pessoas favoritas para trabalhar! A mentalidade aberta deles ainda não se atrofiou, e eles me ajudam a ver as coisas de forma diferente. Recentemente, eu embarquei em uma jornada de aprendizado em IA com um engenheiro jovem que é inteligente, entusiasmado com ferramentas de IA, ansioso para mergulhar, e de forma alguma hesitante em falar com o robô.
E, no entanto, de alguma forma não estava funcionando.
O primeiro sinal foi o sistema de autenticação. Ela precisava adicionar OAuth a um serviço existente, e ela instruiu o modelo a construir todo o fluxo de autenticação em uma especificação gigante. Login, renovação de token, gerenciamento de sessão, acesso baseado em funções, e o fluxo de logout. Tudo. O modelo prontamente atendeu. Ele produziu várias centenas de linhas de código que pareciam completas com 100% de cobertura de código e testes passando.
Mas quando eu revisei, os problemas eram estruturais. O modelo tinha criado um novo armazenamento de sessão em vez de usar o que já tínhamos. Ele introduziu um padrão de renovação de token que conflitava com nosso gateway de API. A lógica de acesso baseada em funções duplicava regras de negócio que já existiam em um middleware compartilhado. Tecnicamente, nada disso estava errado — funcionava. Arquitetonicamente, tudo estava errado.
Sentei-me com ela e fiz uma pergunta simples: 'Antes de você solicitar isso, você sabia como lidamos com as sessões hoje?' Ela hesitou. 'Não exatamente.' Essa era a lacuna. Ela não tinha mapeado o território antes de pedir ao modelo para construir sobre ele.
A próxima tentativa foi diferente, mas não o suficiente. Ela pediu ao modelo para modificar o fluxo de login para suportar um novo provedor. Desta vez, ela estava definindo escopo menor, o que foi bom. Mas ela descreveu a mudança em termos do que ela queria que a interface do usuário (UI) fizesse, não em termos do que o código existente já tratava. O modelo, não tendo razão para conhecer nossa abstração de provedor existente, escreveu uma implementação paralela do zero. Mesmo resultado: código funcional, arquitetura inadequada.
Conversamos sobre o que havia acontecido. Eu disse a ela para pensar no modelo da mesma forma que pensaria em um novo contratado temporário na equipe. Talentoso, rápido, sem experiência com nossa base de código. Você não entregaria um projeto a um contratado temporário e sairia. Você diria: aqui está nosso armazenamento de sessão, aqui está o middleware, aqui está o padrão para adicionar um novo provedor. Adapte seu trabalho a isso.
Ela entendeu o conceito. Mas as próximas rodadas me mostraram que o conceito era a parte fácil. Ela recebia um único plano do modelo e o aprovava sem insistir em alternativas. Ela via a disposição do modelo de fazer mudanças expansivas no código como sendo minuciosa; eu via isso como risco desnecessário. Ela ainda não tinha o instinto de dizer 'não, isso é área de superfície demais para esta mudança.'
Um engenheiro experiente teria lidado com essa mesma tarefa de OAuth em seis ou sete rodadas focadas, cada uma com escopo limitado a uma parte do sistema que ela já entendia.
Competências interpessoais são as novas competências técnicas
Isso pode parecer uma sobrecarga que eliminaria os ganhos de produtividade da codificação com IA. Mas essas não são habilidades novas. Engenheiros experientes vêm fazendo isso em suas cabeças o tempo todo. A diferença é que o desenvolvimento orientado por especificações externaliza o planejamento de design de software de todas as tarefas. A negociação que costumava acontecer internamente, entre você e seu próprio julgamento, agora acontece em uma janela de chat.
Antes de abrir o chat, você precisa saber o que quer. Não a implementação, mas o comportamento, as restrições, a forma da coisa. Você precisa conhecer bem a sua base de código para dizer ao modelo onde o trabalho dele tem que se encaixar. Quando o modelo voltar com uma resposta, provavelmente ela vai rodar. Isso não é suficiente. Você precisa saber se ele realmente se encaixa no sistema que você já tem.
Às vezes você reconhece que o modelo está certo e você está errado, e precisa de humildade para avaliar isso de forma justa.
Em todo o processo, o que separa as pessoas que usam bem essas ferramentas das que as usam rápido é paciência: a disposição de ir e vir cinco ou seis vezes em vez de aceitar algo medíocre na segunda rodada. Há também a habilidade de escrever com precisão sobre código sem escrever código, descrevendo fluxos de dados, casos extremos e modos de falha em linguagem simples. E há bom senso técnico, que é mais difícil de definir mas fácil de reconhecer: uma sensação de como um bom software se parece do ponto de vista da manutenção de longo prazo, não apenas se ele funciona.
E então há a parte desconfortável. A maioria dessas habilidades é reconhecimento de padrões construído a partir de anos de cometer erros. Você reconhece uma decisão arquitetural ruim porque já viveu com as consequências de uma. Você rejeita a resposta inicial do modelo porque já lançou sua primeira ideia antes e se arrependeu.
Engenheiros juniores estão sendo colocados em situações onde precisam realizar negociação de design em um alto nível, mas não parece haver um currículo para isso. É hora de mudar de “LeetCode” para negociação de design, e este será o assunto de posts futuros nesta série.
Adendo
Competências Interpessoais para Negociar uma Especificação com IA
Durante a Negociação
-
Fluência na leitura de código: Examine o que o modelo produz quanto à solidez estrutural, não apenas a sintaxe. Você está analisando para ver se se encaixa, não para encontrar bugs. Por que é importante: O modelo pode gerar código válido que não pertence ao seu sistema. Você precisa detectar isso rapidamente.
-
Bom senso arquitetural: Reconhecer quando uma solução é tecnicamente válida, mas errada para este sistema. Por que isso importa: O modelo não sabe o que "errado para nós" significa. Você é quem sabe.
-
Direcionamento criativo: Encontre enquadramentos alternativos quando o modelo estiver travado. Reformule o problema, ofereça uma analogia, restrinja-o de forma diferente. Por que é importante: O modelo responde à forma como você enquadra as coisas, e um enquadramento melhor produz um resultado melhor.
-
Saber quando tomar uma posição: O modelo apresenta opções de forma neutra. Você decide e articula por que uma abordagem é melhor para o seu contexto. Por que isso importa: Isso é julgamento de engenharia, e leva anos para desenvolver.
-
Ceticismo produtivo: Assuma que a primeira resposta do modelo é um rascunho, não uma solução. Não é cinismo, mas o hábito de teste de pressão antes de aceitar. Por que isso importa: As saídas de primeira passagem são sedutoras porque são fluentes. Fluência não é correção.
-
Saber o que você não sabe: Reconhecer quando o modelo pode estar certo e você pode estar errado. Por que isso importa: A negociação vai nos dois sentidos. Às vezes, ela traz à tona uma abordagem que você não havia considerado, e você precisa da humildade para avaliá-la de forma justa.
Ao Longo de Todo o Processo
-
Paciência: Voltar e revisitar cinco ou seis vezes, em vez de aceitar algo medíocre já na segunda rodada. Por que isso importa: Isso separa as pessoas que usam a ferramenta bem das que a usam rapidamente.
-
Comunicação técnica: Descrever com precisão o código sem escrever código. Descrever fluxos de dados, mudanças de estado, casos de borda e modos de falha em linguagem simples. Por que isso importa: Isso é mais difícil do que a maioria dos engenheiros pensa, e é a principal interface entre você e o modelo.
-
Manter a complexidade na mente: Acompanhe como a decisão atual interage com três outras partes do sistema que o modelo não conhece. Por que isso importa: O modelo não tem contexto arquitetural persistente. Você é a memória de trabalho.
-
Bom gosto: Uma percepção de como um bom software se sente a partir da perspectiva do usuário. Não apenas "funciona" mas "esta é a experiência certa, o comportamento certo, o nível certo de complexidade." Por que é importante: Sem isso, você aceitará soluções que são funcionais, mas erradas.
-
Saber quando parar: Reconhecer quando a especificação está boa o suficiente para passar para a execução, e quando você está fazendo refinamento excessivo. Por que importa: Os retornos decrescentes são reais. Em algum ponto, a negociação adicional custa mais do que melhora.
