- Ao refatorar gradualmente uma camada de acesso a dados em Rust com 17.155 linhas escrita por agentes, os tokens de entrada necessários para a mesma mudança funcional caíram de 159.564 para 27.360, uma redução de 83%
- O volume total de código permaneceu quase igual, mas ao separar o código relacionado em arquivos mais coesos, o agente passou a conseguir ler apenas o conjunto mínimo de arquivos necessário para fazer mudanças
- Os tokens de entrada não diminuíram muito até que o maior arquivo ficasse pequeno o suficiente; no fim, a camada de dados foi dividida em 19 arquivos Rust e o maior arquivo caiu de 17.155 para 3.695 linhas
- Os tokens de saída e o volume de implementação funcional quase não mudaram, e o Claude também não conseguiu escolher sozinho uma refatoração adequada nem executá-la com estabilidade, exigindo orientação humana ativa no planejamento e na execução
- Com o preço de entrada do Sonnet 5 em $3/MTok, a economia por mudança é de apenas cerca de $0,397, mas isso indica potencial de redução recorrente de custos sempre que a camada de acesso a dados for modificada no futuro
O arquivo de 17.155 linhas criado pelo agente
- O aplicativo de suporte ao trabalho inclui uma UI web com atualização e consulta dinâmicas, modais e salvamento automático, integração com sistemas externos, machine learning e análise de texto, tarefas em segundo plano e ambiente de deploy automático
- Do total de cerca de 150 mil linhas, aproximadamente 120 mil são em Rust e o restante em TypeScript e Terraform; quase tudo foi escrito por agentes usando Claude Code e, em parte, Cursor
- O desenvolvedor não leu nem revisou o código, exceto em olhadas ocasionais por curiosidade
- A camada de acesso a dados cresceu para mais de 6 mil linhas repetindo a mesma configuração de requisição HTTP e codificação/decodificação JSON em todas as consultas de leitura e escrita, até que um único arquivo Rust chegou a 17.155 linhas
- Esse módulo não tinha deduplicação, nem linguagem interna, havia extração limitada de funções e quase nenhuma extração de classes, mas tinha interfaces a preservar e limites claros, o que o tornava adequado para um experimento de refatoração
Como a medição repetiu a mesma mudança
- O objetivo era verificar se investir tokens na refatoração atual poderia reduzir o consumo de tokens em futuras mudanças funcionais
- Como o agente não aprende com trabalhos anteriores, em cada etapa foi pedido exatamente a mesma mudança a um novo subagente, para evitar que efeitos de aprendizado contaminassem o experimento
- O experimento foi conduzido na seguinte ordem
- elaborar um plano completo seguindo princípios rígidos de refatoração
- definir uma mudança representativa em um único prompt
- fazer o subagente executar a mudança e reportar o consumo de tokens para medir a linha de base
- descartar o resultado da mudança e aplicar uma etapa da refatoração
- repetir o processo de executar a mesma mudança e descartar o resultado
- registrar custo de tokens, tempo de execução e linhas de código em cada etapa
- Como o Claude não fornecia contagem de tokens em tempo real de forma confiável, foi pedido que reportasse o número de caracteres enviados e recebidos, e os tokens foram aproximados dividindo esse número por 4 com tiktoken
Resultados das medições por etapa
- No estado inicial, tanto a camada de acesso a dados quanto o maior arquivo tinham 17.155 linhas, o código Rust total tinha 50.359 linhas, e a mudança representativa exigia 159.564 tokens de entrada, 1.705 de saída e 342 segundos
- Após a etapa 15, a camada de acesso a dados tinha 16.608 linhas, o maior arquivo 3.695 linhas, o código Rust total 49.812 linhas, com 27.360 tokens de entrada, 2.113 de saída e tempo de execução de 454 segundos
- Nas etapas intermediárias, conforme o maior arquivo diminuía, os tokens de entrada também caíam
- após a etapa 7, com a extração de
queries.rs, o maior arquivo caiu para 15.670 linhas e a entrada para 151.850 tokens - após a etapa 8, com a extração de
traits.rs, o maior arquivo passou a 13.845 linhas e a entrada a 132.558 tokens - após a etapa 12, com a separação de
store/, o maior arquivo caiu para 9.269 linhas e a entrada para 104.080 tokens - após a separação final de
store/, o maior arquivo despencou para 3.695 linhas e a entrada para 27.360 tokens
- após a etapa 7, com a extração de
- A camada de acesso a dados final ficou composta por 19 arquivos Rust, e o maior arquivo passou a ser a biblioteca de testes
- Em refatorações adicionais, o mesmo método pode ser aplicado a esse arquivo de testes
Por que os tokens de entrada caíram 83%
- Os tokens de entrada para a mesma tarefa caíram de 159.564 para 27.360, economizando 132.204 tokens
- Como o volume total de código da camada de acesso a dados quase não mudou, isso não foi resultado de haver menos código para ler
- O agente passou a identificar o conjunto mínimo de arquivos necessários para a tarefa e a ler áreas cada vez menores do código; isso também apareceu na saída de raciocínio e nos resumos de leitura de arquivos do Claude Code
- Se os arquivos fossem apenas divididos arbitrariamente em partes pequenas, seria preciso ler vários arquivos para encontrar o código relacionado, então seria difícil obter o mesmo efeito
- A maior redução ocorreu na separação final, mas ela só foi possível porque etapas anteriores extraíram duplicações e criaram estruturas centrais repetidas
- Essa sequência não foi planejada antecipadamente com foco em redução de custo; ela surgiu de um processo normal de refatoração, em que primeiro se remove duplicação local, depois emerge um núcleo comum e só então o código é decomposto em arquivos menores
Tokens de saída e efeito financeiro
- Os tokens de saída gerados ao implementar a mudança representativa quase não mudaram, então a refatoração não conseguiu reduzir o tamanho real da mudança
- O preço dos tokens de saída era 5 vezes maior que o dos tokens de entrada, mas o volume absoluto era muito menor
- Calculando o preço de entrada do Sonnet 5 em $3/MTok, a economia por mudança é de cerca de 39,7 centavos
- Não foi verificado se a economia também se acumula em debugging, em funcionalidades mais complexas ou em refatorações de todo o codebase, nem qual foi o custo da própria refatoração
- Também não está claro se é possível fazer refatorações que reduzam os tokens de saída; em uma mudança representativa simples, o ruído da geração de código não determinística mascarou diferenças decorrentes de mudanças estruturais
A refatoração conduzida com Claude
- O Claude não conseguiu olhar o código e escolher sozinho qual refatoração aplicar; os resultados reais corresponderam ao que o prompt instruía diretamente
- Havia etapas explícitas de refatoração no harness de desenvolvimento, mas o Claude não as usou para melhorar o arquivo de 17.155 linhas
- No processo de planejamento, o Claude Code encontrou a extração de funções como primeira etapa, enquanto o Claude.ai chegou a identificar até a extração da classe cliente inteira
- Para mudanças mecânicas, foram usados scripts Python com
grepesed, mas os scripts se confundiam com frequência por causa da indentação - A separação dos arquivos de store, que teve maior valor, foi omitida na primeira tentativa e reaplicada em etapas posteriores; por isso, o número de etapas nos resultados não bate com o plano de etapas do apêndice
- O experimento inteiro levou cerca de 8 horas e foi em grande parte não supervisionado
- após 6 horas e 40 minutos, uma etapa omitida foi descoberta e houve uma intervenção
- mais do que o Wi‑Fi lento do hotel, o grande responsável pela lentidão nos testes foi um cache temporário de build do Cargo que havia crescido demais
Prompt da mudança representativa
- Cada subagente recebeu apenas o codebase e a documentação de arquitetura e implementou o mesmo trait público assíncrono
ItemWatchStore - O trait inclui os três métodos a seguir
watch_item(&self, item_id: &str, user_id: &str) -> Result<()>unwatch_item(&self, item_id: &str, user_id: &str) -> Result<()>watched_items_for_user(&self, user_id: &str) -> Result<Vec<String>>
- As informações de watch são armazenadas na coleção
item_watchesdo Firestore com os campositemId,userIdecreatedAt - Em vez de uma struct de registro Rust separada, é retornado um
Vec<String>com os IDs dos itens - Ao
FakeStorefoi adicionado um campo em memóriaVec<(String, String)>, e noFirestoreStorea implementação foi feita reutilizando o padrão HTTP existente - No fim da resposta, foi pedido que o agente imprimisse em JSON os arquivos lidos, a quantidade de caracteres e a quantidade de caracteres da resposta, além de não fazer commit do código
Plano de refatoração aplicado
-
Etapa 1 — extração da classe
FirestoreClient- separar a responsabilidade de coordenar consultas de domínio da responsabilidade de enviar requisições HTTP ao Firestore
- mover
reqwest::Client,project_id,MetadataAuthe o tratamento de URL e cabeçalhos de autenticação para uma nova struct - no plano, isso reduziria cerca de 1.200 linhas da implementação de
FirestoreStoree adicionaria cerca de 120 linhas ao cliente
-
Etapa 2 — extração das funções
extract_doc_idenew_link- unificar a extração de ID em 20 parsers de documento e a criação repetida de
Link, que aparecia 62 vezes - previa-se uma redução de cerca de 500 linhas
- unificar a extração de ID em 20 parsers de documento e a criação repetida de
-
Etapa 3 — extração de funções do pipeline de consulta de links
- unificar o padrão de coleta de resultados de consulta em cerca de 15 pontos e o padrão de busca de ID único em cerca de 8 pontos
- previa-se uma redução de cerca de 200 linhas
-
Etapa 4 — extração de funções de condição de link em
FakeStoreInner- separar em dois métodos as variações repetidas de
inner.links.iter()presentes em cerca de 15 métodos - previa-se uma redução de cerca de 120 linhas
- separar em dois métodos as variações repetidas de
-
Etapa 5 — introdução de funções de criação de valores do Firestore
- substituir expressões
json!repetidas mais de 128 vezes, como strings e timestamps, por quatro chamadas de função - ao trocar macros de múltiplas linhas por chamadas em uma linha, previa-se economizar cerca de 80 linhas
- substituir expressões
-
Etapa 6 — extração de
FieldsBuilder- consolidar em um builder o padrão de criação de mapa de campos usado em cerca de 20 encoders
- previa-se reduzir encoders de cerca de 40 linhas para cerca de 12, com economia total estimada entre 500 e 600 linhas
-
Etapa 7 — separação de
queries.rs- mover 32 constantes
LinkQuerye tipos relacionados para um módulo separado - sem alterar os pontos de chamada existentes, reduzir
mod.rsem cerca de 800 linhas
- mover 32 constantes
-
Etapa 8 — separação de
traits.rs- mover 17 traits públicos e tipos de erro relacionados, e reexportá-los
- reduzir
mod.rsem cerca de 1.900 linhas, embora o novo arquivo também tenha cerca de 1.900 linhas
-
Etapa 9 — separação de
traits/por domínio- dividir os traits em
planning.rs,content.rs,people.rsesystem.rs - sem mudar definições nem pontos de chamada, limitar o tamanho dos arquivos a cerca de 300 a 650 linhas cada
- dividir os traits em
-
Etapa 10 — separação de
codec.rs- mover encoders/decoders de documento, parsers,
FieldsBuildere funções de criação de valores - após a etapa 6, isso se tornaria um módulo de cerca de 400 a 500 linhas e reduziria
mod.rsem cerca de 500 linhas
- mover encoders/decoders de documento, parsers,
-
Etapa 11 — separação de
fake_store.rs- mover
FakeStore,FakeStoreInnere 18 implementações de traits - reduzir
mod.rsem cerca de 4.700 linhas
- mover
-
Etapa 12 — separação da implementação de
FirestoreStore- em
store/mod.rs, manter a struct, o construtor,FirestoreClienteMetadataAuth, e dividir as implementações de traits em arquivos por domínio - transformar um arquivo de cerca de 10 mil linhas em 10 arquivos de 120 a 650 linhas cada, deixando
mod.rscomo um arquivo de reexportação com cerca de 100 linhas
- em
-
Etapa 13 — colocação dos testes junto dos módulos de destino
- mover o código de teste para baixo de cada arquivo de implementação, sem alterar os testes
- reduzir
mod.rsem cerca de 2.000 linhas e adicionar a cada arquivo entre 200 e 700 linhas de testes relacionados
Limitações e experimentos futuros
- Os tokens gastos para elaborar e executar o plano de refatoração não foram contados separadamente, então não é possível calcular com precisão o custo do investimento em refatoração
- Um limite superior com base no uso total daquele período é de 5 milhões de tokens, mas isso inclui duas rodadas de elaboração do plano, o experimento e o desenho da mudança representativa, além de outros trabalhos
- O experimento foi feito em um único grande aplicativo ainda em fase greenfield, construído e mantido por um único desenvolvedor, então os resultados não podem ser generalizados
- Como trabalhos futuros, são necessários medição precisa dos tokens de refatoração, mudanças mais complexas, refatorações de escopo mais amplo, refatoração contínua e comparação do valor relativo entre abordagens
- Este experimento serve como ponto de partida para medir ao mesmo tempo o valor temporal e financeiro obtido com a refatoração e o custo da própria refatoração
1 comentários
Comentários do Hacker News
É curioso ver boas práticas de desenvolvimento que a maioria das empresas de TI ignorava sendo reinventadas como boas práticas de IA
Antes, quando se dizia para manter a documentação no código, não apenas jogar tarefas do Jira, mas fornecer o contexto completo do projeto, e refatorar pensando na produtividade de longo prazo, isso era visto como algo tedioso
Agora, quando se diz a mesma coisa — manter a documentação de IA no código e em
CLAUDE.md, não tentar controlar tudo nos mínimos detalhes via prompt, e refatorar para a produtividade da IA — isso é recebido com interesseHumanos, mesmo sabendo o jeito certo, ficam ocupados ou perdem o foco, mas agentes não se cansam de tarefas entediantes, então procedimentos que já provaram funcionar com pessoas, mas eram difíceis de aplicar de forma constante, passam a ser viáveis na prática
Com base em uma especificação comum, agentes separados escreveram a implementação e os testes, e um agente de auditoria fez a verificação para evitar contaminação entre os resultados; isso é uma aplicação ampla e consistente, com IA, da engenharia cleanroom que a IBM desenvolveu para humanos nos anos 1980
Não se trata de embalar práticas antigas como se fossem novidade por causa da moda da IA, mas de mostrar, com evidências, que boas práticas de mais de 20 anos continuam válidas
Agentes precisam reaprender o contexto a cada sessão, então o valor das boas práticas aumenta muito e seus efeitos aparecem imediatamente
Ainda assim, é bom poder executar a CLI 100 vezes em uma hora para testar a usabilidade de uma nova flag
Já a IA, sem essa base, tem desempenho muito ruim ou simplesmente não funciona, então práticas de engenharia saudáveis deixam de ser uma melhoria de longo prazo e viram pré-condição obrigatória
Mesmo que o efeito líquido real seja zero, colocar IA no fluxo de trabalho já é útil por criar uma justificativa para adotar práticas de desenvolvimento adequadas
Gosto deste texto porque ele critica de forma concreta e quantitativa, com base em como ferramentas de IA são realmente usadas
Em vez de textos que discutem riscos sociais de forma vaga sem casos reais de uso, é muito mais útil ver textos que mostram com medições o que a IA não consegue fazer
Pelo mesmo motivo, também foi marcante um relatório que entrevistou membros do Boko Haram para investigar como a IA foi usada no terrorismo
Eu realmente gosto de refatorar diretamente, sem IA
Não há mudança visível, mas é satisfatório tornar, para o futuro, muito mais fácil de lidar um site cujo resultado não aparece imediatamente agora
É divertido como um quebra-cabeça encontrar casos em que um problema já resolvido por um padrão estabelecido está sendo resolvido de novo por algum desvio bizarro do passado, e mover isso para o lado das boas práticas sem criar nova dívida técnica
Implementei tudo de forma improvisada, até autenticação, e assim aprendi o funcionamento interno do jeito difícil; como resultado, também ganhei material de refatoração para aproveitar pelos próximos 10 anos
Passei a entender concretamente a frase a base de código é um sistema, e comecei a enxergar o código em um nível mais alto, como uma organização contínua ou uma malha que dá para puxar e pressionar
O fato de a IA poder eliminar esse processo de aprendizado destaca o problema dos desenvolvedores juniores. Para ganhar intuição, não há alternativa além de mergulhar fundo por conta própria, e, embora Naur já tenha alertado sobre isso há 40 anos, essa lição continua sendo esquecida
Acho que, quando um agente faz refatoração, a participação humana é indispensável
Um modelo gerador pode focar no trabalho inicial e um modelo de revisão pode encontrar partes que ele deixou passar, mas é questionável se ele realmente entende o propósito do projeto como um todo e como o código se conecta, a ponto de identificar redundâncias ou uma estrutura mais elegante
Deixar a refatoração nas mãos de um agente de código é como pedir a um cirurgião de trauma para melhorar a capacidade atlética; para fazer direito, é necessária uma visão holística
Dividir um arquivo grande em vários arquivos, por si só, fica em uma refatoração superficial. Sem uma teoria sobre que código deve ficar junto e o que deve ser extraído como função utilitária, isso se parece menos com fatoração e mais com quebrar um número grande em números menores para depois somá-los de novo
Às vezes os agentes constroem sistemas que armazenam e calculam de novo valores já obtidos pela API, enquanto um humano consegue olhar o projeto inteiro e localizar com precisão que os dados necessários já estão em uma chave específica do JSON
functools.partialem vez de classes de dadosO conceito de que as fronteiras entre arquivos representam fronteiras de subsistemas lógicos, facilitando o raciocínio, e de que o conteúdo de outros arquivos é tratado basicamente como opaco, também é abundante nos dados de treinamento
Acho que a vantagem dessa estrutura não vem só de características acidentais da cognição humana, mas também de aspectos objetivos
Ajudaram minha experiência com refatoração, minha forma de lidar com bases de código de outras pessoas e o fato de, como designer original, eu poder explicar as intenções passadas e presentes
Era uma linguagem pouco popular, com poucas fragilidades de dependência e fácil para LLMs lidarem; depois de remover o viés para JavaScript e Python, a velocidade do trabalho aumentou muito
Era um projeto JSR-223 em que se escreviam scripts em várias linguagens populares, mas todos executavam na JVM conforme as exigências do ambiente: https://en.wikipedia.org/wiki/Scripting_for_the_Java_Platform
Neste mercado de trabalho, é preciso usar agentes de código baseados em modelos de ponta de última geração e conhecer com precisão suas capacidades e limitações; apresentar a avaliação contrária em uma entrevista pode ser motivo de eliminação
Contexto conciso não só reduz o consumo de tokens, mas melhora o raciocínio e permite colocar mais camadas em um mesmo contexto para tratá-las de forma inteligente
A refatoração em direção a boas abstrações cria software que generaliza melhor, com maior chance de funcionar não só nos casos testados, mas também em casos interpolados e extrapolados
Há matemática de teoria da informação e bayesiana que sustenta isso, e parece uma coincidência elegante que software economicamente e energeticamente eficiente também se torne mais preciso
O ponto central é reduzir a entropia do código
Os LLMs já inferem significado muito bem mesmo com pouco contexto
É interessante ver dados apresentados, e isso bate com minha percepção de que LLMs ganham muito com código bem separado, mas não são tão bons assim em criar esse tipo de código por conta própria
A maioria dos desenvolvedores humanos provavelmente também é assim
No geral, o ganho com IA é grande, mas também é preciso tempo de organização, e ainda acho que é necessário ler cada linha
Adiei algumas revisões para não bloquear o andamento de outros times e agora estou pagando uma dívida técnica maior do que o normal, mas valeu mais a pena destravar os gargalos primeiro
A IA tornou mais fácil contrair dívida técnica em todos os sentidos e, se bem orientada, também resolve essa dívida de forma bastante competente. Ainda assim, os resultados variam de pessoa para pessoa: https://news.ycombinator.com/item?id=49035455
Mostrar exemplos de código bem estruturado ou repositórios open source para indicar o que fazer e o que evitar ajuda muito
A maior parte do ganho econômico da refatoração vem não da economia de tokens, mas da melhor compreensão humana
Isso permite resolver mais rápido um chamado de incidente às 3 da manhã, reduzir bugs que chegam ao ambiente de produção e lançar mais rápido que a concorrência
Acima de tudo, entender o sistema faz com que as pessoas assumam responsabilidade e ownership com mais disposição, corrigindo problemas mais rápido e se envolvendo ativamente nas melhorias
O ponto principal é que a refatoração reduz o consumo de tokens, e é bom que isso tenha sido quantificado em vez de discutido apenas de forma abstrata
Mas Fowler disse em Refactoring que a pré-condição essencial para refatoração são testes sólidos, e acho que, independentemente de IA, é aí que está o benefício real
Bons testes evitam regressões feitas por humanos ou robôs e deixam em código uma especificação legível por ambos: https://www.oreilly.com/library/view/refactoring-improving-the/9780134757681/
Calculando o preço do Sonnet 5 a 3 dólares por MTok, o valor economizado em mudanças futuras que mexam na camada de acesso a dados é de 39,7 centavos
Considerando a redução de preços da OpenAI, os modelos abertos e a queda de longo prazo no preço dos tokens, isso pode não bater com o custo de um desenvolvedor sênior de cerca de 100 dólares por hora orientando a refatoração
O código criado por agentes virou um bloco tão gigantesco que só os próprios agentes conseguem ler e entender, e nisso a realidade em si importa mais do que se é funcionalidade, bug ou propriedade emergente
Para lidar com código gerado por ferramentas de IA, estamos nos tornando novamente dependentes de ferramentas de IA
Ainda assim, humanos também já criaram arquivos assustadoramente grandes e monorepos, e graças aos LLMs finalmente ficou viável editar e refatorar isso
Numa situação em que a base de código ficou grande e bagunçada demais para humanos entenderem, os LLMs podem nos salvar; pessoalmente odeio arquivos gigantes, mas é amargo pensar que os princípios de organização ao estilo Fowler talvez já não sejam tão importantes
Ela pode fazer até funcionários medianos ou que às vezes vão bem executarem num piscar de olhos coisas que antes não conseguiam por causa dos processos da empresa, e assim acabar transformando-os em maus funcionários
Se um agente cria arquivos ou funções gigantes, basta instruir para não fazer isso e ele obedece
Refatoração é um dos melhores sinais de uma equipe de desenvolvimento saudável
A própria refatoração tem seus benefícios, mas seu valor não aparece bem para o responsável pelo produto ou no quadro de tarefas de funcionalidades
Se a equipe refatora em prol da saúde geral do software, isso significa que os desenvolvedores conseguem propor com liberdade melhorias para um bom software, e que essas propostas são levadas a sério
A deterioração do software é mais grave quando a equipe não tem motivação ou autonomia para realizar a visão de um software de alta qualidade, e em geral é um bom sinal quando a equipe pode seguir seu próprio julgamento sobre excelência
Claro, também existe o exagero de reescrever tudo passando por Ruby, Node e Rust para depois voltar de novo para uma tecnologia amigável a agentes, mas em ambiente corporativo é muito mais comum haver equipes que sentem que não têm permissão para melhorar