1 pontos por GN⁺ 4 시간 전 | 1 comentários | Compartilhar no WhatsApp
  • 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.go cresceu para um único Model de 1690 linhas e um Update() 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 []string e mutação direta em background tea.Cmd podiam 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.go geradas por Claude, onde uma única struct Model guardava 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 por msg.(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 resourcesLoadedMsg passou a conter condições como msg.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.go havia 9 pontos espalhados com limpeza manual como m.logLines = nil, m.allResources = nil e m.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.md como 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 de AppMsg, e a struct App cuida 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 s significava 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 Enter também se ramificava num único dispatch plano para contexts view, namespaces view, logs view e lógica genérica de drill-down, comparando strings em m.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 no CLAUDE.md a 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 keyMap plano misturava bindings específicos de várias views numa única struct, incluindo Fullscreen, Autoscroll, ToggleTime, WrapText, CopyLogs, ToggleLineNums, Describe, YamlView, Edit, Shell, FilterLogs, FleetTabNext e FleetTabPrev
    • Autoscroll e Shell usavam ambos a tecla s; 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.md um 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 e ra[0] como Name, e a identidade das colunas dependia apenas de comentários e da ordem das colunas em resource.views.json
    • Se uma coluna fosse adicionada entre Instance e Compute em resource.views.json, ordenações, renderizações condicionais e drill targets que referenciavam ra[2] e ra[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 []string ou Vec<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 FleetNode e PodInfo, até imediatamente antes da renderização, e fazer a ordenação por campos nomeados em vez de acesso posicional como row[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
  • 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 updateTableMsg retornava uma closure tea.Cmd, e dentro dela havia chamadas como m.updateColumns(m.viewWidth), m.updateTableData() e m.table.SetCursor(savedCursor) que alteravam campos de Model
    • Como o Bubble Tea executa tea.Cmd em uma goroutine separada, a closure podia ler e escrever m.resources, m.table e m.viewWidth enquanto a goroutine principal do View() lia os mesmos campos
    • Não havia lock nem mutex, e <-m.updateTableChan apenas aguardava o sinal de update, sem impedir que View() 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

Regras de proteção para colocar em CLAUDE.md e agents.md

  • Invariantes de arquitetura

    • Cada view deve implementar a trait/interface View e 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 App deve ser um roteador fino, responsável apenas por navegação e dispatch de mensagens
  • 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
  • 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()
  • 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

 
GN⁺ 4 시간 전
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

    • Se você sabe escrever bom código, é possível fazer a IA escrever bom código com várias técnicas, e isso é viável sim
      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
    • Vejo esse ponto de “em algum momento você vai adicionar uma funcionalidade que entra em conflito com um invariante” como um grande problema do desenvolvimento guiado por especificação
      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
    • Passei por algo parecido criando um novo framework interno na empresa e migrando usos do framework antigo
      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
    • Não basta ler a saída de código; pelo menos na minha experiência, é preciso escrever código com as próprias mãos
      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
    • Invariantes escritos de forma informal são difíceis de provar como preservados, mesmo com revisor humano, e linguagem natural não é precisa o suficiente para isso
      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

    • Essas “pessoas” hipotéticas estiveram erradas o tempo todo desde o começo
      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
    • Modelos até conseguem lidar com arquitetura, mas no momento geralmente fazem isso muito mal, a menos que sejam fortemente conduzidos
      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
    • Acho que a solução está nas entrelinhas do texto
      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.
    • Acho que as linguagens atuais não escalam porque o codebase é globalmente complexo e os invariantes desejados não ficam visíveis
      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
    • Eu chamo isso de arquitetura descartável
      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

    • São regras razoáveis para manter um modelo mental sólido da sanidade do código e do crescimento do codebase, mas é difícil segui-las no trabalho, onde as expectativas de velocidade de entrega mudaram muito depois da IA
      É preciso encontrar um ponto de equilíbrio entre continuar conectado ao codebase e não virar gargalo da equipe
    • Tentei seguir regras parecidas e encontrei um problema matemático difícil
      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
    • Gosto mais da expressão dívida de entendimento do que “dívida cognitiva”
      Porque a dívida que se acumula é precisamente falta de entendimento do código, então é uma formulação mais exata
    • Em projeto pessoal, faz sentido priorizar a forma mais divertida de trabalhar, mas no trabalho ninguém opera entendendo totalmente todas as camadas, desde dependências e trabalho dos colegas até serviços externos e o próprio silício
      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
    • Tive abordagem parecida, mas no fim acho irrealista cumprir bem a segunda regra
      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

    • O interessante é que esse resultado seja surpreendente
      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
    • Acho que codebases grandes deveriam ser um conjunto de codebases pequenos
      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
    • Acho que deve haver soluções de fluxo de trabalho além de simplesmente abandonar a IA
      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
    • Fico me perguntando se você nem leu o código gerado e só fez commit automático de tudo
      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
    • Não trabalhei com codebases grandes, mas imagino se não daria para aplicar um fluxo ao estilo de Working Effectively with Legacy Code
      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

    • Essa métrica depende muito de quem está usando IA e para quê
      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
    • Também cheguei recentemente à mesma conclusã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
    • É bem provável que quem sente o maior ganho com LLMs seja justamente quem já não sabia fazer software bom muito bem, ou não tinha tanta capacidade para isso
    • Também achei estranho o ponto dos 7 meses. Mesmo escrevendo em uma linguagem nova, não deveria levar tanto tempo assim
      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
    • A família GPT, por estrutura, existe para produzir texto — linguagem e código — e parece tender internamente a fazer tudo do zero
      É 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

    • Acho que existem dois tipos de desenvolvedor: os que acham que código é a parte difícil e os que não acham
      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
    • No momento há um problema de definições confusas
      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
    • Acho que agentes quase sempre falham entre o plano e a execução
      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
    • Muita gente parece perder de vista que tudo o que a IA faz precisa ser verificado
      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
    • Foi a única forma que encontrei de usar agentes sem passar a odiá-los completamente ou fracassar
      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

    • Em resumo, parece um título caça-cliques, e o objetivo do texto seria atrair atenção para o próprio projeto
    • Já comprei domínio de projeto poucos minutos depois de ter a ideia
      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

    • Verdade. Essas pessoas parecem nunca ter sido gerentes
      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

    • Acho que a intenção foi colocar um título sensacionalista para o HN morder e levar o texto à página principal
    • O texto também não parece ter sido escrito à mão. O que subiu ao topo do HN não foi o texto, foi o título