- Engenheiros de software eficazes criam e mantêm um modelo mental claro dos requisitos e do código, executando um loop de comparação e atualização de forma repetida
- LLMs conseguem escrever e modificar código, criar testes e depurar, mas têm dificuldade em manter um modelo mental preciso, o que leva à confusão em tarefas complexas
- Atualmente, os LLMs têm limitações para identificar com precisão as diferenças entre código e requisitos e corrigi-las adequadamente devido a problemas de omissão de contexto, viés de recência e alucinação
- Pessoas conseguem alternar o raciocínio com flexibilidade conforme a situação, como armazenar temporariamente o contexto completo ou ocultar detalhes por um momento para enxergar o panorama geral, mas os LLMs não conseguem implementar isso
- LLMs são úteis em tarefas com requisitos simples, mas no desenvolvimento de software complexo, no fim, o engenheiro de software precisa assumir diretamente a responsabilidade pela clareza dos requisitos e pelo comportamento do código, e o LLM atua como ferramenta de apoio
Loop de engenharia de software
- Engenheiros experientes trabalham repetindo as seguintes etapas
1. construir um modelo mental dos requisitos
2. escrever o código de acordo com esse modelo
3. entender o que o código escrito realmente faz
4. identificar as diferenças e corrigir o código ou os requisitos - O ponto central desse loop é a capacidade de manter um modelo mental preciso e sustentável
Limitações dos LLMs
- LLMs conseguem executar tarefas como escrever código, identificar problemas e corrigi-los, criar e executar testes, adicionar logs e usar depuradores
- No entanto, por não conseguirem manter um modelo mental, surgem problemas como os seguintes
- presumir que o código que eles próprios escreveram está funcionando bem
- quando um teste falha, depender de suposições para decidir se devem corrigir o código ou o teste
- quando entram em confusão, apagar todo o código e reescrevê-lo do zero
- Diferentemente dos humanos, quando um teste falha eles não têm flexibilidade para revisar o modelo e decidir a direção da correção, nem para destravar o problema por meio de conversa quando ficam frustrados
- Engenheiros de software executam testes durante o trabalho e, quando surge um problema, conseguem julgar com clareza que parte deve ser corrigida
- Às vezes, mesmo quando reiniciam todo o trabalho, isso acaba levando a uma compreensão mais profunda do problema
Possibilidades futuras
- Isso pode mudar no futuro à medida que os modelos evoluírem mais, mas engenharia de software exige mais do que simples geração de código
- Ao resolver problemas importantes, humanos conseguem trazer temporariamente à memória todo o contexto, focar em um ponto específico ou observar o quadro geral
- O importante não é continuar aumentando indefinidamente a quantidade de contexto, e sim lidar seletivamente com as informações necessárias
- Falta aos LLMs a capacidade de, como os humanos, armazenar e restaurar contexto temporariamente ou alternar o raciocínio entre o quadro geral e os detalhes
- Principais limitações atuais dos LLMs
- omissão de contexto (Context omission): não conseguem encontrar bem as partes em que falta informação necessária
- viés de recência (Recency bias): dão peso excessivo às informações mais recentes dentro da janela de contexto
- alucinação (Hallucination): inventam detalhes que não existem
- A adição de memória pode melhorar parte disso, mas, quando a complexidade ultrapassa certo ponto, eles ainda falham em entender o contexto e manter o modelo
- Também lhes falta a capacidade de manter dois modelos mentais semelhantes, analisar as diferenças entre eles e decidir onde corrigir os requisitos ou o código
Papel e uso no momento atual
- LLMs têm pontos fortes em geração rápida de código e integração de requisitos e documentação, então podem ser bastante úteis em tarefas simples e bem definidas
- No entanto, em problemas não triviais, é difícil manter contexto suficiente e promover melhorias iterativas
- Portanto, tarefas como clareza dos requisitos e validação do código continuam sendo responsabilidade do engenheiro de software
- Busca-se um ambiente em que humanos e agentes (LLMs) construam software juntos, mas, no momento, o engenheiro deve liderar, e o LLM deve ser usado como ferramenta
2 comentários
Por que os LLMs, "atualmente", na verdade não conseguem criar software...
Comentário do Hacker News
Nós não resolvemos problemas simplesmente adicionando mais palavras à janela de contexto; se fizéssemos isso, enlouqueceríamos
Quando surge um problema, também não o enxergamos apenas como texto
Se aparece um erro de autenticação no depurador, não pensamos "e se eu simplesmente remover a validação do token do código?" para resolver
Na prática, damos um passo atrás e olhamos a situação como um todo para identificar a causa raiz do problema
Por exemplo, se houve um erro de autenticação, revisamos todo o processo de validação do token e as permissões do usuário que está fazendo a chamada, e podemos perceber que o próprio teste estava errado
Nesse processo, em vez de apenas eliminar o erro, também acabamos descobrindo que é preciso distinguir com mais detalhe coisas como "o 401 é simplesmente falta de autenticação ou falta de permissão?"
Veja Grugbrain.dev
Eu vejo o trabalho do programador como traduzir regras de negócio para uma forma rígida que o computador consiga entender
Esse processo de tradução nem sempre é simples, porque você precisa entender ao mesmo tempo o que as regras significam e como o computador — ou o framework e as camadas de abstração que você está usando — realmente funciona
Principalmente quando um novo requisito quebra todas as suposições anteriores ou entra em contradição com elas, não tem jeito: é preciso revisar várias vezes
Até a tradução entre línguas humanas já é complexa por causa da ambiguidade; com computadores, que executam exatamente o que foi mandado, até um erro pequeno pode virar um problemão
Eu pessoalmente acho que uma abordagem realista é manter o humano envolvido de forma iterativa
Continuo usando esse jeito porque ele permite trabalhar mais rápido e com mais qualidade
Eu, pessoalmente, consigo manter uma grande quantidade de contexto inteira na cabeça
O texto do código em si é descartado quase imediatamente, e meu cérebro faz o parsing do código como uma estrutura parecida com uma AST (árvore sintática abstrata), ou até como um grafo espacial
Eu modelo logicamente o próprio programa e o percebo como uma estrutura totalmente separada do texto
Visto por esse ângulo, o LLM não entende a estrutura do software porque fica focado no texto e não consegue construir um modelo lógico do programa
Arquitetar sistemas grandes, algo que exige capacidade de pensamento abstrato, demanda um esforço mental enorme, e os LLMs não têm essa capacidade de abstração
Meu método é o seguinte
Quando um teste falha, primeiro identifico o componente em questão e depois analiso profundamente o propósito desse componente, seu fluxo interno de controle, mudanças de estado e até as suposições sobre o contexto ao redor, organizando tudo em Markdown (
<nome-do-componente>-mental-model.md)Depois, sempre que lido com problemas de teste, consulto esse modelo mental
Se eu colar essa análise num prompt do Claude, o LLM consegue produzir resultados melhores
Eu consigo até ler e corrigir diretamente o modelo mental construído pelo próprio LLM
A IA também pode sugerir usar 403 em vez de 401 quando o caso for falta de permissão
Parece que o autor do texto não entende muito bem a capacidade atual dos LLMs e das ferramentas de programação
A afirmação de que, quando um teste falha, o LLM só fica chutando se o código está certo ou se o teste está errado, e que quando se frustra apaga o código inteiro, não bate com a minha experiência real
Engenheiros de software sempre identificam de forma concreta a causa da falha de um teste com base no modelo mental que têm na cabeça
Eu desenvolvo em Rails com TDD usando Cline e Anthropic Sonnet 3.7, e sempre faço o LLM escrever os testes primeiro e só depois o código
Eu quebro o trabalho em partes pequenas para poder revisar cada trecho, e quando um teste falha ele costuma raciocinar razoavelmente bem sobre qual parte precisa ser corrigida e acerta
O LLM não é perfeito, mas muitas vezes entrega resultados no nível de um engenheiro júnior humano ou até melhores
Às vezes ele não consegue corrigir um bug, mas desenvolvedores humanos iniciantes também são assim
Os LLMs funcionam especialmente bem para tarefas CRUD dentro de frameworks consolidados como Rails
Já quando tentei criar um app nativo de Windows com Direct2D e Rust, foi um desastre total
Eu gostaria de ver avaliações mais abertas cobrindo casos variados
É um fenômeno muito conhecido que os modelos usem truques e gambiarras, como hardcode, para fazer testes com falha passarem
Na minha experiência, a variação depende muito da linguagem, da plataforma e do domínio
Ultimamente eu mesmo não tenho mexido com Ruby, então não experimentei com Rails recentemente, mas Rails tem uma cultura de programação tão consistente que imagino que os LLMs consigam se sair razoavelmente bem ali
Já em Python, como há uma mistura de estilos de código muito diferentes, várias vezes vi o LLM misturar padrões e deixar os testes instáveis
Foi preciso mudar o código repetidamente, e houve casos estranhos em que o erro real era "falta de ordenação no resultado da query", mas o LLM sugeria absurdamente remover SqlAlchemy e migrar para Django
Na linguagem R, só conseguir código que funcione direito de acordo com a especificação já é difícil
Se você limitar o LLM ao nível de um engenheiro iniciante, ele encontra e aplica soluções muito rápido, especialmente para problemas que já viu antes
Por outro lado, para problemas que ele nunca viu, precisa de mais explicação e orientação, então nesse caso meu papel acaba sendo mais o de mentor
Nosso time vem usando bastante o estilo “claude-code” para refatorações simples que estavam há muito tempo no backlog e para tarefas repetitivas e conhecidas, como sistemas de análise secundária
Pessoalmente, gosto muito de arrastar um bloco de código e pedir coisas como "explique como se eu tivesse 5 anos" ou "procure se há risco de race condition aqui"
Como o código gerado muitas vezes não bate com o estilo do código já existente, eu frequentemente preciso ajustar isso manualmente
Hoje em dia até se ouve falar em "escrever código legível para IA", mas, para mim, o ganho ainda não compensa muito a carga extra
Sobre a afirmação de que "o LLM às vezes fica no nível de um júnior ou até melhor", às vezes eu penso se esses casos não refletem mais o nível recente das contratações de desenvolvedores
Se eu contratasse um júnior pior que o Sonnet 3.7, eu ficaria realmente decepcionado
A maior parte das críticas aos LLMs pode até estar certa, mas anos investindo me ensinaram que vale a pena prestar atenção em tecnologias ou empresas que "são mais ou menos, mas continuam melhorando"
Havia muitas reclamações sobre a internet no começo e meados dos anos 90, mas as pessoas continuaram usando; o Twitter também caía com frequência, mas acabou se estabelecendo como plataforma de notícias
Carros elétricos, smartphones e outros exemplos também eram incômodos no início, mas continuaram melhorando porque eram valiosos
Os LLMs ainda não são perfeitos em várias tarefas, mas, em comparação com 2022, já melhoraram 10 vezes, e eu acredito que a maioria dos problemas citados aqui será resolvida nos próximos 5 anos
Mas em todos os casos citados antes também houve momentos em que a expectativa não bateu com a realidade
A internet ficou mais rápida, mas o metaverso nunca virou tendência dominante, e limites físicos como enjoo em VR continuam sem solução
Na época, não era como se houvesse um grande volume de reclamações porque o telefone era lento; o uso esperado era outro
Só porque uma tecnologia seguiu certo caminho de evolução no passado não dá para garantir que os LLMs vão necessariamente evoluir no mesmo padrão
Também precisamos considerar que novas tecnologias podem trazer soluções melhores
É verdade que no último ano o campo de aplicação se ampliou, mas ainda não houve um avanço que eu chamaria de revolucionário
Mesmo que os celulares antigos fossem lentos e tivessem câmeras ruins, eles já eram indispensáveis pelo uso principal da época: poder falar com alguém a qualquer hora e em qualquer lugar
As grandes melhorias foram um bônus; as pessoas não estavam esperando pensando "quando será que esse telefone vai ficar bom?"
Eu acredito que existe um viés de memória aí
Ao contrário da ideia de grandes reclamações populares sobre a internet nos anos 90, os usuários eram poucos e a adoção mainstream veio bem depois
Na prática, há pouca evidência de que existisse uma massa de usuários reclamando publicamente que a internet era lenta
Nós lembramos só dos poucos produtos que tiveram sucesso e evoluíram bem; a maioria foi rapidamente esquecida ou desapareceu sem melhorar
Em vez de partir da expectativa de que a tecnologia inevitavelmente vai evoluir, eu prefiro julgar com base no estado atual
É difícil concordar com a lógica simples de que o salto enorme dos últimos anos dos LLMs vai continuar do mesmo jeito daqui para frente
Eles podem chegar a um limite de crescimento e, principalmente, a incapacidade de descobrir novo conhecimento ou raciocinar sobre informação desconhecida me parece ser uma limitação decisiva dos LLMs
Não estou dizendo que são ferramentas inúteis, mas não vou embarcar em expectativas exageradas
Esperar que um LLM ouça só algumas frases e já saia codando até um protótipo é, por si só, irrealista
Se você colocasse uma equipe humana para trabalhar assim, também não sairia algo bom; então por que essa expectativa em relação aos LLMs?
Para melhorar bastante a qualidade dos artefatos de desenvolvimento de software com LLM, é preciso usar ativamente os processos e ferramentas que equipes de desenvolvimento já utilizam
artigo sobre autonomous software
Eu comecei um projeto chamado steadytext, feito de forma totalmente autônoma e mais na base do vibe coding, e o LLM acabou escrevendo sozinho até um projeto complexo com 7 mil linhas — biblioteca Python, CLI e extensão para Postgres — além de resolver por conta própria issues e pedidos de funcionalidade
Eu nem cheguei a ver diretamente 90% do código, e a cobertura de testes total, o CI passando e até o uso em produção estão todos sem problema
É indispensável ter no
CLAUDE.mdum plano minucioso, as issues e pedidos bem específicos, mas, com essa preparação, funciona bemNão é fácil fazer um agente de código gerenciar e escrever tudo com eficiência, mas minha experiência foi positiva
GitHub do steadytext
Eu aceito visões mais críticas, mas, no fim, resolver problemas ambíguos depende de o time inteiro compartilhar muito contexto
Até as soluções mais criativas surgem de restrições explícitas e implícitas
O LLM não consegue identificar essas restrições nem criar uma solução nova dentro de restrições que não estejam claramente definidas
Só depois que humanos definem o problema, delimitam o escopo e entendem as condições de contorno é que o LLM pode virar uma ferramenta que ajuda na implementação
Por enquanto, isso é apenas mais uma opção do tipo "qual ferramenta vamos usar para concluir o código?"
Acho irrealista transformar essa discussão numa solução única e absoluta, de tudo ou nada
Na verdade, há muitos engenheiros humanos que também trabalham razoavelmente bem nessas condições
Se dar instruções a um LLM não é assim tão fácil, fico me perguntando qual é exatamente o sentido da existência dele
O Kiro está aplicando essa abordagem; ainda está no começo e não é perfeito, mas, se você trabalhar da forma pretendida, ele até que se sai bem
Ao usar o claude code, tenho sentido cada vez mais frustração com a ideia de que "os LLMs não conseguem construir um modelo mental claro"
Não sei se LLMs baseados em texto realmente conseguem resolver esse problema de forma adequada
Isso me faz lembrar dos casos em que o Google Genie 3 perde o estado interno em cerca de 1 minuto
Tenho a intuição de que esse problema só vai ser resolvido quando surgir uma nova arquitetura além dos transformers, capaz de lidar com contexto de curto e longo prazo e ajuste do próprio peso interno, algo como uma imitação de aprendizado
Referência: discussão relacionada
Ultimamente venho pensando se uma estrutura hierárquica de agentes não seria a alternativa mais realista
Seria ótimo se um agente de nível mais alto mantivesse apenas o modelo mental global, enquanto agentes subordinados dividissem o trabalho entre si
Imagino que já dê para implementar algo parecido com a função de agentes da ferramenta Code; se alguém puder compartilhar estratégias relacionadas, eu gostaria muito
Já experimentei claude-code-requirements-builder, e melhorou um pouco, mas ainda não me deixou satisfeito
Sendo realista, no ambiente de trabalho um desenvolvedor júnior “mediano” também muitas vezes não é tão diferente disso
Acha que o código que escreveu está sempre certo, entra em pânico quando o teste falha e, se perde totalmente a direção, no pior caso apaga tudo e recomeça do zero
Copia e cola do StackOverflow, culpa o compilador e, em casos extremos, até fala em "radiação cósmica"
Quando uso LLMs, acabo percebendo por conta própria que preciso liderar o planejamento e o design
Gosto de poder delegar aos LLMs o trabalho repetitivo de baixo nível e os testes, liberando tempo para pensar no quadro maior
Dito isso, eu gostaria muito que a revisão dos resultados e as sugestões de mudanças do LLM fossem bem mais interativas
Acho que a direção das startups de IA é o ponto central do problema hoje
Em vez de uma interface de chat simples, precisamos de um workflow de IA naturalmente integrado à IDE
Essa parece ser a tendência, como já acontece no Visual Studio, IntelliJ e Android Studio
Quero uma ferramenta que se pareça de verdade com um programador: poder dar comandos por voz no meu idioma nativo, a IA entender todo o contexto do projeto e cobrir refatoração, análise estática e feedback de IA, além de coisas como criar UI a partir de esboços, programar a partir de escrita manual e gerar mensagens de commit a partir das mudanças no código
Concordo que os LLMs são bem úteis para tarefas de nível júnior
Ultimamente, tenho repensado aquela velha afirmação de que "velocidade de digitação não importa tanto"
Antes, como o mais importante era o design e a estruturação geral, o tempo gasto digitando código em si não representava uma parcela tão grande
Mas, usando o Claude, percebi que dá para fazer mudanças de código que antes eu evitava por serem trabalhosas, sem precisar de muito foco
Antes, ao adicionar um único valor a um enum, eu precisava revisar e ajustar manualmente todas as partes correspondentes; com LLM, isso pode ser corrigido automaticamente
Também não preciso mais perder tempo com aquele trabalho chato de corrigir um erro de compilação por vez; posso simplesmente pedir ao Claude para iterar nas correções
Como vários agentes podem mexer em partes diferentes do código ao mesmo tempo, eu posso usar esse tempo para pensar na estrutura maior ou até escrever no HN
Em outras palavras, como não preciso mais corrigir erros de compilação, consigo aplicar mudanças maiores com mais rapidez, e trabalhos que antes um júnior levaria o dia todo para fazer agora podem ser resolvidos de uma vez
Assim, dá para focar mais no desenho geral da arquitetura, além de finalmente resolver um monte de tarefas de código que estavam sendo adiadas, o que ajuda muito na motivação
Concordo com a ideia de que "mesmo digitando mais rápido, você não chega ao objetivo mais cedo porque o gargalo está no design"
Os LLMs são péssimos para criar um bom design e até funções mínimas quase sempre precisam de refatoração
Há ganho de produtividade na etapa de implementação em si, mas é mais no sentido de concretizar ideias que já estavam na minha cabeça ou em documentos
Para brainstorming, eles até servem
Se eu jogar todo o código e os testes e perguntar "tem algum edge case que eu deixei passar?", em uns 1 ou 2 casos entre 10 sai uma observação realmente útil
Corrigir algo para funcionar no curto prazo e alcançar excelência estrutural no longo prazo são problemas muito diferentes, então ainda é incerto se os LLMs vão conseguir acompanhar também nesse segundo aspecto
Sobre a ideia de que "há um monte de coisas que eu gostaria de fazer na codebase", na prática eu tenho sentido que o gargalo real não é mudar o código, e sim revisar
Se o fato de um LLM fazer rápido algo que um júnior levaria o dia inteiro para fazer acabar tirando dos iniciantes a chance de aprender e reduzindo contratações, me preocupo com quem vai formar essa próxima geração
Quanto ao fenômeno de "não conseguir decidir se deve corrigir o código ou corrigir o teste quando um teste falha"
Usar a linguagem de "Red-Green-Refactor" ajuda
Hoje eu deixo claro para o LLM o fluxo: etapa RED (o teste falhar é normal), etapa GREEN (fazer passar com o mínimo de código) e etapa REFACTOR (melhorar o código sem quebrar o teste)
Isso ajuda o LLM a entender que não se trata apenas de "consertar código quebrado", e sim de reconhecer o modelo mental do TDD
Acho claro que os LLMs ainda não chegam lá para um projeto novo inteiro no nível de "crie meu próprio Facebook"
Em compensação, já obtive bons resultados em tarefas mais específicas, como "adicione este modal e siga o estilo do código existente"
Quando o problema é quebrado em partes pequenas e passado uma de cada vez, o resultado costuma ser muito melhor
Copiar o código existente e modificá-lo do jeito que eu quero é algo que eu já consigo fazer sozinho
A área de transferência do meu sistema, ao contrário do LLM, sempre funciona de forma determinística e não cria infinitamente novos problemas inesperados
Fico curioso para ver como ferramentas novas como o v0 reagiriam a esse tipo de pedido
Sinto que o processo de 4 etapas do começo do texto é muito parecido com o de Deutsch em "The Beginning of Infinity"
Nossas teorias nascem de "conjecturas", e o conhecimento surge de um ciclo de "conjectura e crítica"
Escrever código é uma espécie de "conjectura", e criar testes é a "crítica" dessa conjectura
Ambos são tentativas de se aproximar de uma explicação que existe na mente, um ideal platônico