- Al refactorizar por etapas una capa de acceso a datos en Rust de 17,155 líneas escrita por un agente, los tokens de entrada necesarios para el mismo cambio funcional bajaron de 159,564 a 27,360, una reducción del 83%
- Aunque el volumen total de código casi no cambió, al separar el código relacionado en archivos con alta cohesión, el agente pudo leer solo el conjunto mínimo de archivos necesario para el cambio
- Los tokens de entrada no se redujeron de forma significativa hasta que el archivo más grande se volvió lo suficientemente pequeño; al final, la capa de datos quedó dividida en 19 archivos Rust y el tamaño máximo de archivo bajó de 17,155 a 3,695 líneas
- Los tokens de salida y la cantidad de funcionalidad implementada casi no cambiaron, y Claude tampoco pudo elegir por sí mismo una refactorización adecuada ni ejecutarla de manera confiable, por lo que se necesitaron indicaciones humanas activas para planificar y ejecutar
- Con el precio de entrada de Sonnet 5 de $3/MTok, el ahorro por cada cambio fue de apenas unos $0.397, pero se confirmó la posibilidad de reducir costos de forma recurrente cada vez que se modifique la capa de acceso a datos en el futuro
Un archivo de 17,155 líneas creado por un agente
- La aplicación de soporte operativo cuenta con una UI web con actualización y consulta dinámicas, modales y guardado automático, integración con sistemas externos, machine learning y análisis de texto, trabajos en segundo plano y un entorno de despliegue automático
- De unas 150 mil líneas en total, alrededor de 120 mil son Rust y el resto es TypeScript y Terraform; la mayor parte fue escrita por agentes usando Claude Code y algo de Cursor
- Salvo revisiones ocasionales por curiosidad, el desarrollador no leyó ni revisó el código
- La capa de acceso a datos creció a más de 6,000 líneas al repetir la misma configuración de solicitudes HTTP y la codificación/decodificación JSON en todas las consultas de lectura y escritura, hasta que finalmente un único archivo Rust llegó a 17,155 líneas
- Este módulo no tenía eliminación de duplicación ni un lenguaje interno, tenía una extracción de funciones limitada y casi ninguna extracción de clases, pero sí interfaces que preservar y límites claros, por lo que era adecuado para un experimento de refactorización
Método de medición repitiendo el mismo cambio
- El objetivo era comprobar si invertir tokens en la refactorización actual podía reducir el consumo de tokens de futuros cambios funcionales
- Como el agente no aprende de tareas anteriores, en cada etapa se pidió exactamente el mismo cambio a un nuevo subagente para evitar que se mezclara el efecto de aprendizaje
- El experimento siguió este orden
- Escribir un plan completo siguiendo principios estrictos de refactorización
- Definir un cambio representativo con un solo prompt
- Hacer que un subagente realizara el cambio e informara el consumo de tokens para medir el valor base
- Descartar el resultado del cambio y aplicar una etapa de refactorización
- Repetir el mismo cambio y volver a descartar el resultado
- Registrar por etapa el costo en tokens, el tiempo de ejecución y las líneas de código
- Como Claude no podía entregar de forma confiable el recuento de tokens en tiempo real, se le pidió reportar la cantidad de caracteres enviados y recibidos, y se aproximaron los tokens dividiendo los caracteres entre 4 con tiktoken
Resultados de medición por etapa
- En el estado base, tanto la capa de acceso a datos como el archivo más grande tenían 17,155 líneas, el código Rust total tenía 50,359 líneas, y el cambio representativo requirió 159,564 tokens de entrada, 1,705 tokens de salida y 342 segundos
- Después de 15 etapas, la capa de acceso a datos tenía 16,608 líneas, el archivo más grande 3,695 líneas, el código Rust total 49,812 líneas, y se necesitaron 27,360 tokens de entrada, 2,113 tokens de salida y 454 segundos de ejecución
- En las etapas intermedias, a medida que se reducía el archivo más grande, también disminuían los tokens de entrada
- Tras extraer
queries.rsen la etapa 7, el archivo más grande bajó a 15,670 líneas y la entrada a 151,850 tokens - Tras extraer
traits.rsen la etapa 8, el archivo más grande quedó en 13,845 líneas y la entrada en 132,558 tokens - Tras separar
store/en la etapa 12, el archivo más grande quedó en 9,269 líneas y la entrada bajó a 104,080 tokens - Tras la última separación de
store/, el archivo más grande quedó en 3,695 líneas y la entrada cayó bruscamente a 27,360 tokens
- Tras extraer
- La capa final de acceso a datos quedó compuesta por 19 archivos Rust, y el archivo más grande pasó a ser una librería de pruebas
- En una refactorización adicional se podría aplicar el mismo método a ese archivo de pruebas
Por qué los tokens de entrada bajaron 83%
- Los tokens de entrada para la misma tarea bajaron de 159,564 a 27,360, lo que ahorró 132,204 tokens
- Como el volumen total de código de la capa de acceso a datos casi no cambió, no fue resultado de tener menos código que leer
- El agente identificó el conjunto mínimo de archivos necesario para la tarea y fue leyendo regiones de código cada vez más pequeñas; esto también se confirmó en la salida de razonamiento de Claude Code y en los resúmenes de lectura de archivos
- Si solo se dividen archivos arbitrariamente en partes pequeñas, el agente tendría que leer varios archivos para encontrar el código relacionado, por lo que es difícil obtener el mismo efecto
- La mayor reducción ocurrió en la última separación, pero fue posible gracias a que en etapas anteriores se extrajo duplicación y se creó una estructura central repetida
- Esta secuencia no se diseñó de antemano con el objetivo de reducir costos, sino que surgió de un proceso normal de refactorización: primero eliminar duplicación local, luego revelar el núcleo común y finalmente descomponer en archivos pequeños
Tokens de salida e impacto económico
- Los tokens de salida generados al escribir el cambio representativo casi no cambiaron, por lo que la refactorización no redujo el tamaño real del cambio
- Aunque el precio de los tokens de salida era 5 veces mayor que el de los tokens de entrada, su cantidad absoluta era mucho menor
- Calculado con el precio de entrada de Sonnet 5 de $3/MTok, el ahorro por cambio es de unos 39.7 centavos
- No se confirmó si el ahorro se acumula también en depuración, funcionalidades más complejas o refactorizaciones de todo el codebase, ni cuánto cuesta la refactorización en sí
- Tampoco está claro si es posible una refactorización que reduzca los tokens de salida; en el cambio representativo simple, el ruido de la generación de código no determinista ocultó las diferencias atribuibles a cambios estructurales
Refactorización realizada con Claude
- Claude no pudo observar el código y elegir por sí mismo qué refactorización aplicar; los resultados reales correspondieron a las tareas indicadas directamente en el prompt
- Aunque el arnés de desarrollo tenía una etapa explícita de refactorización, Claude no la usó para mejorar el archivo de 17,155 líneas
- Durante la planificación, Claude Code identificó la extracción de funciones como primer paso, mientras que Claude.ai llegó a identificar la extracción de toda una clase cliente
- Para cambios mecánicos se usaron scripts de Python con
grepysed, pero los scripts se confundían con frecuencia por la indentación - La separación de archivos de store, que fue la más valiosa, se omitió en el primer intento y se aplicó de nuevo en una etapa posterior; por eso el número de etapas en las mediciones no coincide con las etapas del plan del apéndice
- Todo el experimento tomó unas 8 horas y se realizó en su mayor parte sin supervisión
- Después de 6 horas y 40 minutos se detectó una etapa omitida y hubo una intervención
- La causa principal de la lentitud en la ejecución de pruebas no fue el Wi-Fi lento del hotel, sino una caché temporal de compilación de Cargo que había crecido demasiado
Prompt del cambio representativo
- Cada subagente recibió solo el codebase y la documentación de arquitectura, e implementó el mismo trait público asíncrono
ItemWatchStore - El trait incluye los siguientes tres métodos
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>>
- La información de watch se guarda en la colección
item_watchesde Firestore con los campositemId,userIdycreatedAt - Devuelve un
Vec<String>de ID de ítems sin una estructura Rust de registro separada - En
FakeStorese agrega un campo en memoriaVec<(String, String)>, y enFirestoreStorese implementa usando el patrón HTTP existente tal como está - Al final de la respuesta se le pidió que imprimiera en JSON los archivos leídos y la cantidad de caracteres, además de la cantidad de caracteres de la respuesta, y se le indicó que no hiciera commit del código
Plan de refactorización aplicado
-
Etapa 1 — Extraer la clase
FirestoreClient- Separar las responsabilidades de coordinación de consultas de dominio y transporte HTTP de Firestore
- Mover
reqwest::Client,project_id,MetadataAuth, el manejo de URLs y encabezados de autenticación a una nueva estructura - Según el plan, reducir unas 1,200 líneas de la implementación de
FirestoreStorey agregar unas 120 líneas al cliente
-
Etapa 2 — Extraer las funciones
extract_doc_idynew_link- Unificar la extracción de ID en 20 parsers de documentos y la creación de
Linkrepetida 62 veces - Se esperaba ahorrar unas 500 líneas
- Unificar la extracción de ID en 20 parsers de documentos y la creación de
-
Etapa 3 — Extraer funciones de pipeline para consultas de links
- Unificar el patrón de recolección de resultados de consultas en unos 15 lugares y el patrón de búsqueda de un ID de destino único en unos 8 lugares
- Se esperaba ahorrar unas 200 líneas
-
Etapa 4 — Extraer funciones de condición de links en
FakeStoreInner- Separar en dos métodos las variantes repetidas de
inner.links.iter()en unos 15 métodos - Se esperaba ahorrar unas 120 líneas
- Separar en dos métodos las variantes repetidas de
-
Etapa 5 — Introducir funciones de creación de valores de Firestore
- Reemplazar expresiones
json!repetidas más de 128 veces para strings, timestamps, etc., por llamadas a cuatro funciones - Se esperaba ahorrar unas 80 líneas al convertir macros de varias líneas en llamadas de una línea
- Reemplazar expresiones
-
Etapa 6 — Extraer
FieldsBuilder- Consolidar con un builder el patrón de creación de mapas de campos en unos 20 encoders
- Se esperaba reducir encoders de unas 40 líneas a unas 12, con un ahorro total de 500 a 600 líneas
-
Etapa 7 — Separar
queries.rs- Mover 32 constantes
LinkQueryy tipos relacionados a un módulo separado - Reducir unas 800 líneas de
mod.rssin cambiar los sitios de llamada existentes
- Mover 32 constantes
-
Etapa 8 — Separar
traits.rs- Mover 17 traits públicos y tipos de error relacionados, y volver a exportarlos
- Reducir unas 1,900 líneas de
mod.rs, aunque el nuevo archivo también tendría unas 1,900 líneas
-
Etapa 9 — Separar
traits/por dominio- Dividir los traits en
planning.rs,content.rs,people.rsysystem.rs - Limitar el tamaño por archivo a unas 300–650 líneas sin cambiar definiciones ni sitios de llamada
- Dividir los traits en
-
Etapa 10 — Separar
codec.rs- Mover encoders y decoders de documentos, parsers,
FieldsBuildery funciones de creación de valores - Después de la etapa 6, sería un módulo de unas 400–500 líneas y reduciría unas 500 líneas de
mod.rs
- Mover encoders y decoders de documentos, parsers,
-
Etapa 11 — Separar
fake_store.rs- Mover
FakeStore,FakeStoreInnery 18 implementaciones de traits - Reducir unas 4,700 líneas de
mod.rs
- Mover
-
Etapa 12 — Separar la implementación de
FirestoreStore- Dejar en
store/mod.rsla estructura, el constructor,FirestoreClientyMetadataAuth, y dividir las implementaciones de traits en archivos por dominio - Convertir un archivo de unas 10,000 líneas en 10 archivos de 120 a 650 líneas cada uno, y dejar
mod.rscomo un archivo de reexportación de unas 100 líneas
- Dejar en
-
Etapa 13 — Colocar las pruebas junto con los módulos objetivo
- Mover el código de pruebas debajo de cada archivo de implementación sin cambiarlo
- Reducir unas 2,000 líneas de
mod.rsy agregar pruebas relacionadas de 200 a 700 líneas a cada archivo
Limitaciones y experimentos posteriores
- No se contaron por separado los tokens usados para escribir y ejecutar el plan de refactorización, por lo que no se puede calcular con precisión el costo de inversión de la refactorización
- El límite superior, basado en el uso total durante ese periodo, es de 5 millones de tokens, pero incluye haber escrito el plan dos veces, el diseño del experimento y del cambio representativo, y otras tareas
- El objeto del experimento todavía está en etapa greenfield y es una sola aplicación grande construida y mantenida por un único desarrollador, por lo que los resultados no se pueden generalizar
- Como trabajo posterior, se necesita medir con precisión los tokens de refactorización, probar cambios más complejos, refactorizaciones de mayor alcance, refactorización continua y comparar el valor relativo de distintos enfoques
- Este experimento es un punto de partida para medir tanto el valor en tiempo y dinero que produce la refactorización como el costo de la refactorización en sí
1 comentarios
Opiniones en Hacker News
Es interesante ver cómo las mejores prácticas para desarrolladores, que la mayoría de las empresas de TI ignoraban, se reinventan como mejores prácticas para IA.
Antes, si decías que había que poner la documentación dentro del código, no limitarse a tirar tareas en Jira sino dar el contexto completo del proyecto, y refactorizar pensando en la productividad a largo plazo, lo consideraban aburrido.
Ahora, si dices lo mismo como que la documentación para IA debe estar en el código y en CLAUDE.md, que no hay que controlar todo minuciosamente con prompts, y que refactoricemos por la productividad de la IA, se recibe con interés.
Los humanos, aunque sepan cuál es la forma correcta, están ocupados o pierden la concentración; en cambio, los agentes no se cansan de las tareas tediosas, así que procedimientos cuya eficacia ya se había demostrado con humanos, pero que eran difíciles de aplicar de forma constante, se vuelven viables en la práctica.
Hicimos que agentes separados escribieran la implementación y las pruebas a partir de una especificación común, y que un agente auditor las verificara para evitar que los resultados se contaminaran entre sí; eso es aplicar de manera amplia y consistente con IA la ingeniería de sala limpia que IBM desarrolló para humanos en la década de 1980.
No se trata de empaquetar viejas prácticas como si fueran nuevas al ritmo de la moda de la IA, sino de mostrar, con fundamentos, que mejores prácticas de hace más de 20 años siguen siendo válidas.
Los agentes tienen que adquirir el contexto de nuevo en cada sesión, por lo que el valor de las mejores prácticas aumenta mucho y sus efectos se ven de inmediato.
Aun así, es bueno poder ejecutar una CLI 100 veces durante una hora para probar la usabilidad de una nueva bandera.
En cambio, si la IA no cuenta con esa base, se desempeña muy mal o directamente no funciona, así que las prácticas de ingeniería saludables dejan de ser una mejora a largo plazo y pasan a ser un prerrequisito indispensable.
Aunque el efecto neto real fuera nulo, incorporar IA al flujo de trabajo es útil en el sentido de que da una justificación para adoptar prácticas de desarrollo adecuadas.
Me gusta que este artículo critique las herramientas de IA de forma concreta y cuantitativa, basándose en cómo se usan realmente.
Un texto que muestra con métricas qué es lo que la IA no puede hacer resulta mucho más útil que uno que habla vagamente de riesgos sociales sin casos de uso reales.
Por la misma razón, también me pareció impactante este informe, que entrevistó a miembros de Boko Haram para investigar cómo se había usado la IA en el terrorismo.
Me encanta la refactorización hecha directamente, sin IA.
No hay cambios visibles, pero da satisfacción convertir un sitio web que quizá no muestre resultados ahora mismo en algo mucho más fácil de manejar en el futuro.
Es como un rompecabezas agradable encontrar código extraño del pasado que vuelve a resolver problemas ya solucionados con patrones establecidos, llevarlo hacia las mejores prácticas y hacerlo sin crear nueva deuda técnica.
Implementé de forma torpe todo por mi cuenta, incluso la autenticación, y aprendí los principios internos de la manera difícil; como resultado, también me quedó material de refactorización para disfrutar durante los próximos 10 años.
Llegué a entender de forma concreta la idea de que una base de código es un sistema, y empecé a verla en un nivel superior, como una organización continua o una red que se puede estirar y presionar.
El hecho de que la IA pueda eliminar este proceso de aprendizaje resalta el problema de los desarrolladores junior. Para adquirir intuición no queda otra que meterse a fondo directamente y, aunque Naur lo advirtió hace 40 años, seguimos olvidando esta lección.
Considero que cuando un agente hace refactorización, la participación humana es indispensable
Un modelo generativo puede concentrarse en el trabajo inicial y un modelo de revisión puede encontrar lo que se pasó por alto, pero queda la duda de si realmente puede entender el propósito de todo el proyecto y la forma en que el código se integra para distinguir duplicaciones o una estructura más elegante
Encargarle la refactorización a un agente de coding es parecido a pedirle a un cirujano de trauma que mejore el rendimiento deportivo; para hacerlo bien se necesita una visión holística
Dividir un archivo grande en varios archivos no pasa de ser una refactorización superficial. Si no hay una teoría sobre qué código debe estar junto y qué debe extraerse como funciones utilitarias, no es factorización, sino algo más cercano a partir un número grande en números pequeños y luego volver a sumarlos
Un agente puede crear sistemas que vuelven a guardar y calcular valores que ya se obtienen desde una API, pero un humano puede mirar todo el proyecto y encontrar con precisión que los datos necesarios ya están en una clave específica del JSON
functools.partialen lugar de clases de datosLa idea de que los límites entre archivos representan los límites de subsistemas lógicos y facilitan el razonamiento, y de que el contenido de otros archivos se trata por defecto como opaco, también abunda en los datos de entrenamiento
Creo que los beneficios de esta estructura no se deben solo a características accidentales de la cognición humana, sino que también tienen aspectos objetivos
Ayudó mi experiencia en refactorización, la forma en que había trabajado con bases de código ajenas y el hecho de que, como diseñador original, podía explicar las intenciones pasadas y presentes
Era un lenguaje de nicho, con pocas vulnerabilidades de dependencias y fácil de manejar para un LLM; después de eliminar el sesgo hacia JavaScript y Python, el ritmo de trabajo aumentó mucho
Era un proyecto JSR-223 que escribía scripts en varios lenguajes populares, pero los ejecutaba todos en la JVM según las necesidades del entorno: https://en.wikipedia.org/wiki/Scripting_for_the_Java_Platform
En este mercado laboral hay que usar agentes de coding basados en los modelos frontier más recientes y conocer con precisión sus capacidades y límites; dar una evaluación contraria en una entrevista puede ser motivo de descarte
Un contexto conciso no solo reduce el consumo de tokens, también mejora el razonamiento y permite meter más capas en un mismo contexto para manejarlas de forma inteligente
La refactorización hacia buenas abstracciones produce software que generaliza mejor, con más probabilidad de acertar no solo en los casos probados, sino también en casos interpolados y extrapolados
Hay teoría de la información y matemática bayesiana que respaldan esto, y parece una coincidencia elegante que el software más económico y eficiente energéticamente también sea más preciso
La clave es reducir la entropía del código
Los LLM ya infieren bastante bien el significado incluso con poco contexto
Me parece interesante que se hayan presentado datos, y coincide con mi impresión de que los LLM se benefician mucho del código bien separado, pero no son particularmente buenos para crear ese tipo de código por sí mismos
Probablemente la mayoría de los desarrolladores humanos sean similares
En general, los beneficios de la IA son grandes, pero también se necesita tiempo de limpieza, y sigo pensando que hay que leer cada línea
Para no bloquear el avance de otros equipos, postergué algunas revisiones y ahora estoy pagando una deuda técnica mayor de lo habitual, pero valió la pena destrabar primero el cuello de botella
La IA hizo más fácil tomar deuda técnica en todos los sentidos, y si se la guía bien también es bastante buena para saldarla. Eso sí, los resultados varían según la persona: https://news.ycombinator.com/item?id=49035455
Ayuda mucho mostrarle qué hacer y qué no hacer mediante ejemplos de código bien estructurado o repositorios open source
La mayor parte del beneficio económico de la refactorización no viene del ahorro de tokens, sino de mejorar la comprensión humana
Permite resolver más rápido una llamada por incidente a las 3 a. m., reducir los bugs que llegan a producción y lanzar más rápido que la competencia
Sobre todo, cuando se entiende el sistema, uno está más dispuesto a asumir responsabilidad y ownership, así que cuando surge un problema lo corrige más rápido y también se involucra activamente en las mejoras
El punto es que la refactorización reduce el consumo de tokens, y está bien que se haya cuantificado el efecto en lugar de hablar en abstracto
Pero Fowler dijo en 《Refactoring》 que el prerrequisito esencial para refactorizar son pruebas sólidas, y creo que ahí está el verdadero beneficio, independientemente de la IA
Las buenas pruebas evitan regresiones creadas por humanos o robots, y dejan como código una especificación legible para ambos: https://www.oreilly.com/library/view/refactoring-improving-the/9780134757681/
Si se calcula el precio de Sonnet 5 en 3 dólares por MTok, el ahorro en futuros cambios que toquen la capa de acceso a datos es de 39,7 centavos
Considerando las bajas de precios de OpenAI, los modelos abiertos y la caída a largo plazo del precio de los tokens, puede que no justifique el costo de que un desarrollador senior de unos 100 dólares por hora guíe la refactorización
El código creado por agentes se convirtió en un bloque enorme que solo los agentes pueden leer y entender, y en ese caso la realidad en sí importa más que si eso es una funcionalidad, un bug o una propiedad emergente.
Para manejar código generado con herramientas de IA, estamos volviendo a depender de herramientas de IA.
Dicho eso, los humanos ya veníamos creando archivos monstruosamente grandes y monorepos, y gracias a los LLM, editarlos y refactorizarlos por fin se volvió manejable.
En una situación en la que la base de código se volvió demasiado grande y caótica para que los humanos la entiendan, los LLM quizá puedan salvarnos; personalmente detesto los archivos gigantes, pero me resulta amargo pensar que los principios de ordenamiento al estilo Fowler tal vez ya no importen tanto.
Incluso a un empleado promedio, o que a veces hace bien las cosas, puede convertirlo en un mal empleado al permitirle ejecutar a velocidad relámpago tareas que antes no podía hacer por los procesos de la empresa.
Si un agente crea un archivo o una función enormes, basta con indicarle que no lo haga y obedece.
Considero que la refactorización es uno de los mejores indicios de un equipo de desarrollo saludable.
La refactorización tiene beneficios propios, pero su valor no suele verse bien para el responsable de producto ni en la tabla de trabajo de funcionalidades.
Si el equipo refactoriza por la salud del software en su conjunto, significa que los desarrolladores se sienten cómodos proponiendo mejoras para tener buen software y que esas propuestas se toman en serio.
La degradación del software se vuelve más grave cuando el equipo no tiene motivación ni autoridad para hacer realidad una visión de software de alta calidad; si el equipo puede seguir su propio criterio de excelencia, en general es una buena señal.
Claro que también hay excesos, como reescribir todo desde cero pasando por Ruby, Node y Rust para volver a una tecnología más amigable para agentes, pero en entornos empresariales es mucho más común encontrar equipos que sienten que no tienen permiso para mejorar.