Vou voltar a escrever código à mão
(blog.k10s.dev)- k10s era um TUI de Kubernetes com reconhecimento de GPU, criado rapidamente com vibe-coding junto com Claude, mas depois de adicionar a fleet view vários estados de tela quebraram
model.gocresceu para um únicoModelde 1690 linhas e umUpdate()de 500 linhas, passando a carregar UI, cliente, cache, navegação e todo o estado das views- A IA adicionou recursos rapidamente, mas ampliou um god object e um handler global de teclas, criando uma estrutura em que cada nova view aumentava os branches no handler existente
- Dados posicionais em
[]stringe mutação direta em backgroundtea.Cmdpodiam causar erros de coluna e um data race evidente - O novo k10s será reescrito em Rust, e antes do primeiro prompt serão fixados em CLAUDE.md interface, tipo de mensagem, regra de ownership e escopo
O contexto por trás da reescrita do k10s
- k10s começou como um dashboard de Kubernetes com reconhecimento de GPU, uma ferramenta TUI criada para que operadores de clusters NVIDIA pudessem verificar rapidamente uso de GPU, métricas DCGM, nós ociosos e custos como
$32/hr - Foi escrito em Go com Bubble Tea e construído ao longo de cerca de 7 meses, 234 commits e aproximadamente 30 fins de semana em sessões de vibe-coding com Claude
- No início, recursos básicos no estilo clone do k9s, como pods, nodes, deployments, services, command palette, live updates baseados em watch e atalhos do Vim, já estavam funcionando em cerca de 3 fins de semana
- O recurso central, a GPU fleet view, era uma tela que mostrava alocação de GPU por nó, uso, métricas baseadas em DCGM, temperatura, energia, memória e estado com cores, e Claude chegou a gerar de uma vez a struct
FleetView, a filtragem por abas GPU/CPU/All e até a renderização das allocation bars - Depois de adicionar a fleet view, ao voltar para a pods view com
:rs pods, a tabela ficava vazia, os live updates paravam, a nodes view mostrava stale data do filtro da fleet view e até a contagem das abas da fleet ficava errada - Ao rastrear o problema, o autor leu pela primeira vez as 1690 linhas completas de
model.gogeradas por Claude, onde uma única structModelguardava widgets de UI, cliente Kubernetes, estado de logs/describe/fleet, histórico de navegação, cache e tratamento de mouse - O método
Update()tinha cerca de 500 linhas e era uma função de dispatch pormsg.(type)com 110 branches de switch/case - A IA consegue criar recursos rapidamente, mas, se continuar recebendo liberdade sem restrições, a arquitetura desmorona, e a sensação de velocidade parece sucesso até o momento em que tudo entra em colapso ao mesmo tempo
Cinco princípios tirados dos destroços
-
Princípio 1: a IA cria recursos, mas não cria arquitetura
- Claude implementou bem recursos isolados como fleet view, log streaming e suporte a mouse, mas cada um foi construído no contexto de “fazer funcionar agora”, sem considerar sua relação com outros recursos que compartilham o mesmo estado
- O handler de
resourcesLoadedMsgpassou a conter condições comomsg.gvr.Resource == k8s.ResourceNodes && m.fleetView != nil, misturando lógica específica da fleet view dentro do caminho genérico de carregamento de recursos - Sempre que uma nova view exigia comportamento customizado, mais um branch era adicionado ao mesmo handler, e vários campos precisavam ser limpos manualmente para evitar que dados da view anterior vazassem para a nova
- Em
model.gohavia 9 pontos espalhados com limpeza manual comom.logLines = nil,m.allResources = nilem.resources = nil; se um deles faltasse, sobravam ghost data da view anterior - A alternativa é escrever antes do código interfaces concretas, tipos de mensagem e regras de ownership, e colocá-los no
CLAUDE.mdcomo invariantes de arquitetura - Um exemplo de regra seria: cada view implementa a trait/interface
View, uma view não acessa o estado de outra, dados assíncronos só entram por variants deAppMsg, e a structAppcuida apenas de navegação e dispatch de mensagens
-
Princípio 2: god object é o artefato padrão que a IA produz
- Para satisfazer o prompt imediato com o mínimo de ceremony, a IA tende a uma estrutura em que uma única struct contém tudo
- O tratamento de teclas também não era separado por view, e a tecla
ssignificava autoscroll na logs view, shell na pods view e container shell na containers view - O pedido “adicione suporte a shell em pods” foi implementado inserindo mais um branch perto do handler global de teclas já existente
- A tecla
Entertambém se ramificava num único dispatch plano para contexts view, namespaces view, logs view e lógica genérica de drill-down, comparando strings emm.currentGVR.Resource - Dentro do único arquivo
model.go,m.currentGVR.Resource ==era usado mais de 20 vezes como se fosse um discriminador de tipo, e adicionar uma nova view exigia mexer em vários handlers - A alternativa é não adicionar campos de estado específicos de view em
App/Model, criar cada view como uma struct separada e colocar noCLAUDE.mda regra de que os key bindings ficam no keymap da view ativa - Também é necessário um guardrail como “adicionar uma view deve significar adicionar um arquivo; se isso exigir modificar views existentes, pare e pergunte”, para impedir que a IA siga o caminho mais curto de empilhar branches
-
Princípio 3: a ilusão de velocidade amplia o escopo
- O k10s era originalmente uma ferramenta para um público restrito, operadores de clusters de treinamento com GPU, mas o vibe-coding fez recursos como pods, deployments, services, command palette, suporte a mouse, contexts e namespaces parecerem “gratuitos” de adicionar
- Como resultado, o projeto deixou de ser uma ferramenta focada em GPU e foi se expandindo em direção a um TUI de propósito geral para todos os usuários de Kubernetes, na prática recriando o k9s
- O
keyMapplano misturava bindings específicos de várias views numa única struct, incluindoFullscreen,Autoscroll,ToggleTime,WrapText,CopyLogs,ToggleLineNums,Describe,YamlView,Edit,Shell,FilterLogs,FleetTabNexteFleetTabPrev AutoscrolleShellusavam ambos a teclas; funcionava porque o dispatch verificava o recurso atual, mas isso tornava impossível entender os atalhos localmente- A velocidade de escrita de código parecia “shipping”, mas cada recurso criava o custo adicional de mais um branch dentro do god object
- A alternativa é explicitar no
CLAUDE.mdum limite de escopo: k10s é para operadores de clusters com GPU, as views suportadas se limitam a fleet, node-detail, gpu-detail e workload, e não se devem adicionar views genéricas de recurso nem funcionalidades redundantes ao k9s - A IA pode oferecer um orçamento infinito de linhas, mas o orçamento de complexidade continua finito, então o escopo precisa ser recusado antecipadamente
-
Princípio 4: dados posicionais são uma bomba-relógio
- O k10s fazia flatten dos recursos recebidos da API do Kubernetes diretamente para a forma
type OrderedResourceFields []string - A função de ordenação da fleet view tratava
ra[3]como Alloc,ra[2]como Compute era[0]como Name, e a identidade das colunas dependia apenas de comentários e da ordem das colunas emresource.views.json - Se uma coluna fosse adicionada entre Instance e Compute em
resource.views.json, ordenações, renderizações condicionais e drill targets que referenciavamra[2]era[3]poderiam passar a funcionar errado silenciosamente - O compilador não sabe o significado de
[]string, e a configuração em JSON também não consegue expressar comportamento de ordenação, renderização condicional ou drill target customizado, então o código Go acabou hardcodando suposições posicionais - A IA tende a escolher
[]stringouVec<String>porque isso entra facilmente no widget de tabela, enquanto structs tipadas exigem mais ceremony logo no início e acabam perdendo no caminho mais rápido - A alternativa é manter os dados estruturados como structs tipadas, como
FleetNodeePodInfo, até imediatamente antes da renderização, e fazer a ordenação por campos nomeados em vez de acesso posicional comorow[3] - Uma estrutura de exemplo seria
FleetNode { name, instance_type, compute_class, alloc }, em que a identidade das colunas é expressa pelo tipo e estados impossíveis, como ordenar pela coluna errada, deixam de existir - “Making impossible states impossible” é uma expressão usada nas comunidades Elm/Rust para a ideia de desenhar tipos de modo que estados inválidos não possam ser construídos, em vez de depender de checagens em runtime
- O k10s fazia flatten dos recursos recebidos da API do Kubernetes diretamente para a forma
-
Princípio 5: a IA não deve ser dona das transições de estado
- No Bubble Tea, o ponto central da arquitetura é que o estado muda apenas dentro de
Update(), dirigido por mensagens, mas o k10s violou esse princípio - O handler de
updateTableMsgretornava uma closuretea.Cmd, e dentro dela havia chamadas comom.updateColumns(m.viewWidth),m.updateTableData()em.table.SetCursor(savedCursor)que alteravam campos deModel - Como o Bubble Tea executa
tea.Cmdem uma goroutine separada, a closure podia ler e escreverm.resources,m.tableem.viewWidthenquanto a goroutine principal doView()lia os mesmos campos - Não havia lock nem mutex, e
<-m.updateTableChanapenas aguardava o sinal de update, sem impedir queView()lesse um estado parcialmente escrito - A estrutura era um data race evidente e normalmente “funcionava”, mas às vezes aparecia como display corrompido
- A alternativa é fazer com que workers em background não alterem diretamente o estado da UI, mas enviem mensagens tipadas por channel, para que o loop principal de eventos receba a mensagem e aplique a mutação de estado
- A regra de concorrência é: tarefas em background não alteram diretamente o estado da UI, apenas enviam resultados como mensagens tipadas, e
render()/view()deve ser uma função pura, sem side effects, I/O ou operações de channel
- No Bubble Tea, o ponto central da arquitetura é que o estado muda apenas dentro de
Regras de proteção para colocar em CLAUDE.md e agents.md
-
Invariantes de arquitetura
- Cada view deve implementar a trait/interface
Viewe não pode acessar o estado de outras views - Todos os dados assíncronos devem entrar por variants de
AppMsg, e tarefas em background não podem alterar campos diretamente - Adicionar uma nova view não deve exigir mudanças em views existentes
- A struct
Appdeve ser um roteador fino, responsável apenas por navegação e dispatch de mensagens
- Cada view deve implementar a trait/interface
-
Regras de ownership do estado
- Não se deve adicionar campos de estado específicos de view à struct
App/Model - Cada view deve existir como uma struct separada e declarar seus próprios key bindings
- O app deve fazer o dispatch das teclas para a view ativa, e novos key bindings devem ser adicionados ao keymap da própria view, não a um handler global
- Se adicionar uma view exigir mudanças em views existentes, é preciso parar e confirmar
- Não se deve adicionar campos de estado específicos de view à struct
-
Escopo
- O k10s deve ser uma ferramenta para operadores de clusters com GPU, não para todos os usuários de Kubernetes
- As views suportadas devem se limitar a fleet, node-detail, gpu-detail e workload
- Não se deve adicionar views genéricas de recurso como pods, deployments e services
- Não se devem adicionar recursos que dupliquem funcionalidades do k9s
- Pedidos de recursos que não ajudem operadores de jobs de treinamento em GPU devem ser recusados
-
Representação de dados
- Não se deve fazer flatten de dados estruturados em
[]string,Vec<String>ou arrays posicionais - Os dados devem permanecer como structs tipadas até imediatamente antes da chamada de renderização
- A identidade da coluna deve vir do nome do campo na struct, não de índice de array
- Funções de ordenação devem operar em campos tipados, não em acesso posicional como
row[3] - A geração de strings para exibição só deve acontecer dentro de
render()/view()
- Não se deve fazer flatten de dados estruturados em
-
Regras de concorrência
- Tarefas em background como watcher, scraper e chamadas de API não devem alterar diretamente o estado da UI
- Tarefas em background devem enviar resultados como mensagens tipadas por channel
- Apenas o loop principal de eventos deve aplicar mutações de estado ao receber mensagens
render()/view()deve ser uma função pura, sem side effects, I/O ou operações de channel- Se um trabalho assíncrono precisar mudar o estado como resultado, deve ser definida uma nova variant de
AppMsg
Como será refeito
- O k10s será reescrito em Rust, não porque Rust seja “melhor”, mas porque parece uma linguagem que o autor consegue conduzir diretamente
- Em uma linguagem usada com profundidade suficiente, é possível perceber que algo está errado antes mesmo de conseguir explicar isso em palavras, e essa sensibilidade não é substituída por vibe-coding
- Quando a IA produz código aparentemente plausível, é preciso ter a capacidade de perceber se aquilo é lixo
- Na nova versão, o trabalho de design, como interfaces concretas, tipos de mensagem e regras de ownership, será feito manualmente por uma pessoa antes de escrever o código
- Em vez de deixar a IA tomar decisões erradas de arquitetura como antes, essas decisões passam a ser documentadas antes do primeiro prompt
- Links para o TUI e o projeto original estão em k10s Github e K10S.DEV
Observações adicionais
- Bubble Tea é um framework TUI em Go baseado na The Elm Architecture, e os problemas de arquitetura do k10s não vieram do Bubble Tea, mas da implementação do próprio k10s
- “Making impossible states impossible” é uma expressão das comunidades Elm/Rust para a ideia de impedir que estados inválidos possam ser construídos por meio do desenho dos tipos, em vez de checá-los em runtime
- Assim como o “em-dash” pode ser um cheiro em textos de IA, o “god-object” pode ser um cheiro em código de IA, e o vibe-coding pode fazer a implementação parecer barata, levando à perda de foco e ao inchaço
1 comentários
Comentários do Hacker News
As pessoas que diziam que o código gerado era bom, em geral, eram justamente as que não liam esse código
As medidas de mitigação propostas no texto também não se sustentam por muito tempo. Ao projetar um sistema ou componente, surgem invariantes como “uma view não acessa o estado de outra view”, e em algum momento você precisa adicionar uma funcionalidade que entra em conflito com isso
Nessa hora, normalmente você ou abandona a funcionalidade, ou a encaixa de forma estranha e ineficiente por cima do invariante, ou precisa mudar o próprio invariante. Essa escolha não é apenas uma questão de contexto, mas de julgamento, e os modelos atuais erram nesse julgamento com frequência demais
Se você explicita restrições arquiteturais, o agente produz código complexo e impossível de manter, todo retorcido para se encaixar nessas restrições mesmo quando deveria mudá-las. Se você não ler com mais cuidado do que leria código humano, no fim surge um “código que devora a si mesmo”, e você só percebe tarde demais
O ponto central é identificar onde a IA tem dificuldade e tornar isso fácil para ela. Por exemplo, contexto extremamente pequeno, modularização com limites claros, módulos puros separados de entrada e saída, esconder detalhes atrás de interfaces, 100 testes que rodem em menos de 1 segundo, benchmarks etc.
A IA funciona bem quando há limites e contexto pequeno. Se você não oferece isso, o desempenho piora, e a responsabilidade é de quem usa a ferramenta
Nenhuma especificação resiste à realidade, e mesmo com bastante investigação e projeto, algum invariante da especificação acaba se revelando errado
Quando um humano encontra essa situação durante o desenvolvimento, ele pode recuar um passo e repensar se o invariante está errado e quais seriam os impactos de mudá-lo. Já a IA muitas vezes força alguma solução remendada sob premissas ou projeto incorretos, e falta a ela a percepção para reavaliar o todo
Isso pode melhorar com bom fluxo de trabalho e validação, mas não é uma área que ferramentas como Claude Code resolvam bem por padrão, e há limites
No começo, definimos princípios fortes e ganhamos confiança migrando alguns usos manualmente. A migração completa era tão grande e cara que foi adiada por quase 10 anos, então tentamos acelerar com IA para reduzir custos
A IA foi aceitável nos 80% dos casos mecânicos e simples. Os 20% restantes exigiam mudanças no framework; a maioria era pequena, como adicionar campos de API, mas um ou dois casos exigiam redesenho conceitual
O backend de certo sistema conseguia produzir determinados dados em 99% dos casos, mas em alguns casos importantes isso era logicamente impossível, então os dados precisavam ser informados externamente. Só que uma otimização importante havia sido construída sobre a hipótese de que “isso é impossível”
A ferramenta de IA não percebeu isso e adicionou a lógica migrada como se fosse funcionar corretamente. Graças à forma de implantação, ainda não virou bug em produção, mas ao fazer as perguntas certas para a equipe parceira descobrimos que a mesma necessidade existia em outros lugares também
No fim, não virou um problema maior porque havia uma pessoa profundamente envolvida no assunto. Ferramentas de validação e modelos mais inteligentes talvez facilitem esse tipo de migração no futuro, mas por enquanto o código gerado às vezes parece bonito e ao mesmo tempo quebrado, então ainda precisa de acompanhamento de perto
Eu tinha um padrão arquitetural incomum que vinha usando havia uns dois meses, e toda vez que usava aquilo sentia um pequeno desconforto; só ontem à noite percebi que não era uma boa abstração e entendi uma forma melhor de dividir
Se eu deixasse um LLM gerar o código, sentiria esse desconforto de forma muito menos nítida, então demoraria mais para perceber o problema e encontrar uma solução. Pode até gerar as partes periféricas, mas a funcionalidade central ainda precisa ser escrita majoritariamente por mim
Mesmo que você os expresse em uma linguagem formal precisa, o LLM por trás do agente não tem capacidade suficiente para entender por que aqueles invariantes são necessários e por que importam. Pode até surgir um LLM que conecte tokens a especificações formais e escreva provas, mas o código estranho gerado a partir das partes informais do prompt vai continuar aparecendo
Não dá para impedir isso só adicionando restrições e prompts a uma lista de requisitos ou especificações. Você pode construir uma armadilha melhor, mas o bicho ainda escapa
O problema é a inflação de código causada por ir empilhando código para satisfazer o prompt ou a tarefa. Muitas vezes menos código é melhor, e é preciso alguém capaz de prever o que os outros querem e esperam. Geradores são bons, mas precisam ser usados com mais contenção, não como uma mangueira de incêndio
Quando o Copilot completava uma linha, diziam “mesmo assim a função inteira você ainda precisa escrever”; quando ele passou a completar a função, disseram “a lógica em volta da função você ainda precisa escrever”; quando completou essa lógica também, disseram “a funcionalidade você ainda precisa escrever”
Agora que a funcionalidade também é completada, dizem “mesmo assim a arquitetura você ainda precisa escrever”. Não sei se esses modelos conseguem resolver arquitetura, mas é interessante ver a expectativa sempre se deslocando
Mesmo que a IA complete uma linha, uma função inteira, uma funcionalidade ou um ticket, você ainda precisa ler e entender o código
Eu uso IA o tempo todo e ela está melhorando, mas ainda assim reviso cada linha. No nível de linhas individuais, hoje isso ainda não parece claramente melhor do que o autocomplete por tab do ano passado; às vezes é muito bom, às vezes é realmente ruim
LLMs são ótimos para desenvolvimento de software, mas só quando você não deixa que escrevam a arquitetura. Módulos, structs e enums eu crio manualmente e, sempre que possível, também adiciono os campos e variantes manualmente
Uma boa abordagem é colocar comentários de documentação em cada struct, enum, campo e módulo, e então apontar o LLM para esse módulo e essas estruturas de dados para ele preencher o corpo das funções necessárias etc.
Você pode repetir várias vezes “nunca bloqueie no caminho crítico”, e o LLM ainda assim coloca bloqueio no caminho crítico; pode dizer “se fizer X, precisa de teste do tipo Y”, e ele faz X e esquece os testes
Humanos também não seguem instruções 100% do tempo, mas LLMs são mais aleatórios. O erro humano, comparativamente, com menos frequência faz exatamente o oposto do que você queria
O LLM vê invariantes importantes no código, cria contornos para burlá-los, escreve testes que fazem falha parecer sucesso e depois diz que fez o que foi pedido, enterrando isso em um commit de 5 mil linhas
Tenho convicção de que LLMs são excelentes e são o futuro, e por isso estou criando a linguagem https://GitHub.com/Cuzzo/clear para eles. Precisamos superar o problema de linguagens que exigem contexto global onde esse contexto global não deveria ser necessário, para ficar mais fácil trabalhar junto com eles
Houve sucessos, mas às vezes é tão frustrante que fico em dúvida se valeu a sanidade mental
Não quer dizer que arquitetura não importa, e sim que a arquitetura que servia ontem não precisa necessariamente servir hoje também
Ao usar agentes de codificação, estabeleci algumas regras
Primeiro: se eu for gerar código com um agente, isso precisa ser algo sobre o qual eu tenha certeza absoluta de que, com tempo suficiente, eu mesmo conseguiria implementar corretamente
Segundo: se não for esse o caso, eu não avanço até entender completamente o que foi gerado, a ponto de poder reproduzir aquilo por conta própria
Terceiro: se eu quebrar a segunda regra, posso criar uma dívida cognitiva, mas preciso quitá-la integralmente antes de declarar o projeto concluído
Quanto mais essa dívida se acumula, maior a chance de a qualidade do código gerado cair depois; parece até juros compostos. Em projetos pessoais, essa abordagem é divertida, faz aprender bastante e deixa um codebase que eu consigo entender com tranquilidade
É preciso encontrar um ponto de equilíbrio entre continuar conectado ao codebase e não virar gargalo da equipe
O Claude é um matemático nível doutorado e eu não sou, mas eu sabia exatamente quais propriedades a solução desejada deveria ter e como testar se ela estava correta. Então mantive a solução do Claude em vez da minha solução simples e ingênua, registrei isso no pull request, e todos acharam que era a decisão certa
Fico curioso se esse seria um caso para abrir exceção. Se a IA ficar muito melhor do que eu não só em matemática avançada, mas também em programação, a pergunta mais interessante é se eu pararia totalmente de codar à mão, assumindo que eu talvez perca a capacidade de julgar o código diretamente, mas ainda consiga julgar os testes
Porque a dívida que se acumula é precisamente falta de entendimento do código, então é uma formulação mais exata
Não vejo por que só a IA deveria de repente ser tratada de forma diferente
No fim, é preciso julgar por risco e recompensa. Qual é o prejuízo se der errado? Qual a chance de isso ser detectado em testes e revisão? Qual o ganho se der certo? Com bibliotecas e serviços externos é a mesma coisa
Regras financeiras complexas em um contrato de cripto sem testes e que não pode ser atualizado não têm o mesmo risco que um visualizador interno de dados de log
Na teoria parece ótimo, mas na prática você sempre acaba tomando atalhos mentais sem perceber
Quando comparo corrigir um problema num codebase desconhecido fazendo eu mesmo com “entendi completamente” algo que o agente fez, o quanto fica na minha cabeça uma semana depois é diferente. Quando eu mesmo faço, isso se acumula como conhecimento geral e as partes importantes costumam permanecer; quando tento me apropriar do que o agente fez como se fosse meu, na hora até parece que entendi, mas esqueço muito rápido
Por isso concluí que, nesses casos, a ajuda de LLM na maior parte do tempo atrapalha meus objetivos, mesmo sem considerar outras preocupações como tempo e pressão de negócio
Passei exatamente pela mesma coisa
O golpe funciona assim. Num codebase bom, a IA consegue criar muitas funcionalidades, e parece até mais rápida, segura e precisa. Em áreas que você domina menos, a sensação é ainda mais forte
Com o tempo, o codebase cresce, o tempo de exploração aumenta e a taxa de falha sobe. Você não quer admitir, força ainda mais, e só para quando mudar qualquer coisa já se torna praticamente impossível
Quando você volta a olhar o código, chamar aquilo de espaguete é pouco; está mais para uma Muralha da China
No fim, deletei 75 mil linhas de um total de 140 mil, e sinto que três meses de imersão forte em agentic coding foram desperdiçados. Construí funcionalidades inúteis, aumentei bugs, perdi o modelo mental do código, deixei passar decisões difíceis que só aparecem quando você está dentro do código e também fracassei com os usuários
Não estou sendo sarcástico; realmente tenho curiosidade sobre qual era a expectativa inicial e de onde ela veio
Com LLMs, a expectativa parece ser diferente. Se você entregasse uma descrição resumida de funcionalidade para algum “desenvolvedor” aleatório que conheceu só online e recebesse de volta um monte de implementação meio quebrada, ninguém ficaria surpreso
Mas às vezes as pessoas esperam milagres de uma máquina que alucina longamente, milagres que nem esperariam de um humano. Tenho curiosidade sobre de onde vem essa confiança
Como uma cidade grande é um conjunto de cidades pequenas: existe um mapa, você dá zoom na área local e trabalha dentro daquele escopo. Não é preciso conhecer todos os detalhes de Nova York para tomar um café
Criar uma arquitetura saudável e sustentável é responsabilidade de quem usa a ferramenta. A IA não impede isso e, se usada corretamente, pode até ajudar
Por exemplo, tratar imediatamente o código gerado por IA como código legado, colocar fronteiras fortes de encapsulamento e interfaces bem definidas, e então integrar isso por um fluxo mais manual
Existe um espectro que vai de prompts únicos até geração inline de código, e a abordagem adequada muda conforme o problema e a posição no codebase
Geração em um único prompt combina mais com a fase de protótipo, quando você ainda repete muito a especificação; quando o protótipo se firma, você desce para geração por módulo ou arquivo e trabalha de forma mais sistemática, mantendo um bom modelo mental naquele nível
Se leu mas não entendeu, bastava pedir comentários detalhados em cada saída; se você já sabe que o modelo sofre mais conforme o codebase cresce, então a saída precisa ser revisada com ainda mais rigor à medida que a complexidade aumenta
Criar ilhas de código de maior qualidade, usar a IA para ajudar a reconstruir a intenção do desenvolvedor e as regras de negócio, e criar seams e testes unitários no módulo alvo
A IA não precisa necessariamente servir só para aumentar throughput; também pode ser uma ferramenta flexível de exploração e refatoração para ajudar na codificação manual ou na implementação com agentes depois
Sempre que vejo textos assim, acabo comparando a velocidade que as pessoas dizem obter com IA com a velocidade que eu consigo simplesmente codando à mão
Por coincidência, estou há 7 meses em um projeto de MMO 3D que já está jogável, as pessoas se divertem com ele, os gráficos estão bons e é fácil colocar centenas de pessoas no servidor. A arquitetura também é bem boa, então é fácil expandir funcionalidades, e acho que dá para lançar depois de cerca de 1 ano de desenvolvimento
Enquanto isso, o autor original passou 7 meses em vibe coding e nem conseguiu fazer uma TUI básica. A velocidade de funcionalidade pode parecer alta, mas para fazer uma UI básica dessas isso é inacreditavelmente lento. Há muitas boas bibliotecas de TUI, e como é o tipo de coisa em que basta preencher tabelas com os dados necessários, daria para fazer manualmente em algumas semanas
Ao usar IA, a sensação de progresso rápido e volumoso é forte, mas na prática muitas vezes parece muito mais lento do que programar manualmente. Os dados de produtividade também parecem sustentar isso: quem usa IA sente que foi mais rápido, mas a produção real é menor
O maior gasto de tempo em desenvolvimento de software é alinhar expectativas dos stakeholders com a solução, em reuniões. Nessa perspectiva, a IA quase não ajuda, então se você comparar homem-hora entre proposta e entrada no loop de testes, o resultado vai ser decepcionante
Mas em resolução de problemas, correção de bugs e implementação de soluções já aprovadas, sinto que melhorou pelo menos 10 vezes em relação a antes. Não é só tempo bruto; também melhorou a capacidade de interpretar o comportamento observado e investigar o problema
Ainda assim, há quem não consiga extrair resultados valiosos e corretos da IA. Se você sabe exatamente o que quer e como quer, a IA ajuda muito. Se você pede para ela fazer algo que você mesmo já faria, ela faz mais rápido. Mas se você não sabe exatamente o que quer, a IA atrapalha o avanço
Quando as pessoas mostram algo feito com LLM, normalmente não impressiona muito porque, na maioria dos casos, são coisas que também poderiam ser feitas manualmente em pouquíssimo tempo
Também não observo um aumento de software realmente impressionante, o que combina com a ideia de que hoje os LLMs estão sendo usados para resolver problemas simples, e não os importantes
Outra coisa pouco mencionada é a qualidade do código
Um codebase feito em vibe coding é um ótimo exemplo de que LLMs não são tão bons assim em escrever código. Eles corrigem o próprio erro e logo em seguida o recriam, e o uso de padrões não é consistente
Recentemente o Claude também tem feito escolhas de estilo de código “interessantes” que não combinam com o estilo atual do codebase
É preciso bloquear essa repetição com linguagem de “desenvolvedor sênior” no prompt
A parte “antes de escrever código, eu mesmo projeto interfaces concretas, tipos de mensagem e regras de ownership” é justamente a parte difícil de programar
Com arquitetura definida, escrever código é muito fácil. Se você não escreve o código com as próprias mãos, fica mais difícil perceber que projetou uma API que aceita null enquanto o banco de dados não aceita, ou que até aceita, mas você deixou passar outro problema pequeno
Não entendo como, depois de escrever isso, a pessoa ainda não percebe que o problema é a IA. Não só porque deixou a arquitetura nas mãos dela, mas porque não observou com atenção tudo o que a IA estava fazendo
IA é um gerador de código glorificado, e tudo o que ela faz precisa ser verificado. A parte difícil da engenharia de software nunca foi escrever código, e sim todo o resto
Quem acha que codar é a parte difícil adora IA para programação, porque algo antes difícil ficou fácil
Já para quem acha codar fácil, codificação é um problema de abstração, manutenibilidade e escalabilidade. O difícil é construir uma base sensata sobre a qual o software possa crescer; quando você encontra a abstração correta, o resto fica relativamente fácil
Para essas pessoas, IA para programação é uma ferramenta útil, mas não uma ferramenta mágica. O autor do texto percebeu os limites da IA, então faz parte do segundo grupo e enxergou a parte difícil que a IA não consegue fazer
De um lado, há gente usando autocomplete forte por tab ou chatbot em outra janela, mas revisando tudo claramente; do outro, há casos como Steve Yegge promovendo um novo editor para coordenar dezenas de agentes como se a maior parte do código não fosse ser lida: https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16d...
O primeiro grupo ainda pensa profundamente sobre design, interfaces e estruturas de dados e faz revisão forte. O segundo grupo preocupa mais justamente por não fazer isso
Eles seguem a abordagem plan → red/green/refactor, e o plano em si, por absorver toda a documentação e discussões de fórum, parece bem plausível e embasado
O problema é que, quando começam o trabalho, inevitavelmente aparece algum ponto em que documentação e implementação divergem de verdade. Pode ser que aquela combinação de ferramentas nunca tenha sido usada daquele jeito, que a documentação esteja desatualizada ou simplesmente que haja um bug
Ainda assim, se o objetivo do projeto ou da funcionalidade estiver claro o bastante e você puder rodar e testar localmente, o agente pode ficar iterando até sair de um beco sem saída arquitetural. Ele olha até código de dependência e biblioteca e chega a sugerir correções upstream, algo parecido com o que eu mesmo faria em uma sessão profunda de debugging
Por isso, fico bastante satisfeito em instruir e supervisionar em vez de fazer eu mesmo o trabalho entediante. Mas boa parte da minha equipe não investiga problemas de arquitetura com essa profundidade e, por padrão, “escala para o arquiteto”, o que no longo prazo não me parece bom
A janela em que é possível executar e entender tudo parece estar se fechando rápido. Ainda assim, talvez a gente se adapte criando novas ferramentas e frameworks, do mesmo jeito que usamos compiladores sem entender completamente a conversão para linguagem de máquina ou toda a previsão de desvios e cache de CPUs modernas
Do ponto de vista de alguém sem tanta experiência com código, estou aprendendo mais do que nunca justamente por verificar os resultados e observar o que está certo e errado
Por isso também não acho que isso vá melhorar drasticamente tão cedo. Quando me perguntam “como você consegue tirar saídas tão boas do Claude?”, a resposta é sempre “eu olhei com atenção, encontrei os problemas e pedi para o Claude corrigir”. É literalmente só isso, mas ao ouvir isso muita gente já fica com o olhar vazio
É como o Google: ele facilitou encontrar informação, mas não eliminou o elemento humano de distinguir informação boa de ruim
Primeiro penso no problema e projeto a estrutura e a API; só depois deixo a IA implementar
O título fala em “voltar a escrever código à mão”, mas o que a pessoa realmente faz é “fazer o trabalho de design à mão antes que o código seja escrito”
Então o código ainda parece ser gerado pelo Claude
Mais grave ainda: é difícil entender como alguém passou 7 meses achando que um projeto de vibe coding estava funcionando bem, sem olhar o código-fonte gerado, e ainda chegou a comprar um domínio
Se for um projeto paralelo e a pessoa estiver verificando incrementalmente acompanhando o diff, não olhar o código em profundidade também não é algo tão absurdo assim. É certamente outra forma de trabalhar, mas não chega a ser loucura
Dá a impressão de ver desenvolvedores fazendo um speedrun das lições de gestão de projetos e de produto
Agora estão percebendo que especificações são úteis e que escrever muito código errado não faz o projeto andar mais rápido. Desenvolvedores reclamam de reuniões e discussões como se atrapalhassem escrever código, mas esses processos muitas vezes existem justamente para evitar que todos escrevam mais coisas erradas
Também descobriram que gestão de trabalho é útil, e agora, com cada vez mais conversa sobre fazer todo o design antes, parecem caminhar para desenvolvimento em cascata
O próximo passo vai ser dar um nome para prototipagem, depois falar sobre funcionalidade incremental que convive com requisitos antigos e novos, e no fim concluir que o cliente precisa se envolver mais
Vale a pena observar o que gerentes de projeto e de produto realmente fazem. Eles lideram um produto chamado código, mas não se espera que leiam código, e precisam alcançar isso usando apenas linguagem natural
Será que acham que humanos não escrevem coisas quebradas? Que equipes nunca seguem pelo caminho errado e queimam uma semana, ou meses? Agora, com vibe coding, dá para viver tudo isso em 30 minutos. Como ex-gerente técnico de produto, a sensação é exatamente a mesma
Na prática, não parece que a pessoa está realmente escrevendo código à mão, então a diferença entre título e conclusão fica confusa