¿La limpieza del código afecta a los agentes de programación? Un estudio controlado de pares mínimos
(arxiv.org)- La evaluación de agentes de programación autónomos se ha centrado principalmente en la tasa de éxito de tareas sobre codebases fijos, pero este estudio mide por separado si la limpieza del código cambia el costo de exploración y modificación
- Se construyeron 6 repositorios de pares mínimos y 33 tareas basadas en pruebas ocultas, donde la arquitectura, las dependencias y el comportamiento externo son iguales, y solo cambian las violaciones de reglas de SonarQube y la complejidad cognitiva
- Con Claude Code y Claude Sonnet 4.6, cada tarea se ejecutó 10 veces en ambos lados de cada par de repositorios para un total de 660 experimentos; el agente no sabía en cuál versión del código estaba trabajando
- La limpieza del código no cambió la tasa de aprobación, pero en el código más limpio las métricas equivalentes a tokens bajaron entre 7% y 8%, y las revisitas de archivos se redujeron en 34%
- Más allá del modelo o del prompt, el estado del codebase sigue siendo una variable práctica que determina el costo computacional y la eficiencia de exploración del agente
Pregunta de investigación y planteamiento del problema
- Los agentes de programación autónomos se están expandiendo rápidamente
- En un análisis de 128,018 proyectos de GitHub en 2026, se encontraron rastros de actividad de agentes en 22% a 29% de los proyectos menos de un año después del lanzamiento del primer agente práctico
- El costo de ejecutar agentes tampoco es menor
- En SWE-bench Verified, una sola tarea consume en promedio alrededor de 4 millones de tokens con LLMs de frontera
- En el uso total, los tokens de entrada representan la mayor parte
- Las evaluaciones existentes se han enfocado en si la tarea se resolvió en benchmarks como SWE-bench, y estudios recientes han empezado a medir también el uso de recursos junto con la tasa de aprobación
- La comparación habitual consistía en fijar el codebase y cambiar el agente o el scaffolding
- Este estudio hace lo contrario: fija el agente y la tarea, y compara cambiando solo la limpieza del codebase
Limpieza del código y construcción de pares mínimos
- La limpieza del código se trata como un conjunto de propiedades asociadas con código mantenible
- Facilidad de lectura
- Baja complejidad cognitiva
- Helpers bien separados
- Nombres claros
- Poco código muerto, lógica duplicada y acoplamiento accidental
- El estudio no fija la limpieza del código con una definición formal estricta, sino que usa la cantidad de violaciones a reglas de análisis estático de SonarQube como una métrica sustituta flexible
- La herramienta utilizada fue SonarQube Cloud Enterprise Edition
- El conjunto de reglas fue “default quality gate”
- Los repositorios de pares mínimos se construyeron para que solo difirieran en la limpieza interna, manteniendo estas condiciones
- Mismo lenguaje y framework
- Mismas dependencias
- Mismas pruebas o cobertura de pruebas equivalente
- Mismo comportamiento externo
- La equivalencia de comportamiento se consideró como igualdad en las salidas observables externamente y en las transiciones de estado ante la misma entrada
- La validación real se hizo pasando la misma suite de pruebas con la misma cobertura o pasando pares de pruebas ajustados para cambios no observables, como refactorizaciones
Pipeline de generación de pares mínimos: Slopify y Vibeclean
- Los pares mínimos se generan en ambas direcciones
- Slopify, que vuelve más desordenado un repositorio limpio
- Vibeclean, que ordena un repositorio desordenado
-
Slopify
- Convierte un codebase limpio en una versión que parece haber crecido sin revisión de código ni linting
- El objetivo no es código roto a propósito, sino una versión de historia alternativa de cómo habría quedado sin análisis estático
- Tres etapas, cada una ejecutada por un nuevo agente
- Build: compilar el repositorio y hacer que pasen las pruebas, luego fijar los comandos en
build instructions.md - Explore: recorrer el repositorio y escribir
summary.mdpara cada directorio objetivo de limpieza - Transform: introducir violaciones a reglas de SonarQube en los directorios indicados y volver a ejecutar las pruebas después de cada pasada para rechazar cambios rotos
- Build: compilar el repositorio y hacer que pasen las pruebas, luego fijar los comandos en
- Aumenta la complejidad cognitiva con acciones como inlinear helpers, duplicar lógica por rutas, agregar código muerto y fusionar algunos módulos en un solo archivo
-
Vibeclean
- Ordena codebases que de forma natural tienen muchas violaciones de reglas, manteniendo el comportamiento externo
- La lista de trabajo del agente es el conjunto de issues detectados por el analizador, y cada issue está ligado a un rango de código
- El alcance de las correcciones se limita a los problemas marcados por el analizador; no se busca un rediseño completo
- Funciona en dos etapas
- Build: verificar los comandos de compilación y pruebas y fijarlos en
build instructions.md - Clean: limpiar mecánicamente las violaciones de reglas del analizador por módulo y verificar la equivalencia de comportamiento con pruebas después de procesar cada módulo
- Build: verificar los comandos de compilación y pruebas y fijarlos en
- Realiza tareas como eliminar duplicación de literales de cadenas, borrar código comentado, reemplazar idioms heredados de colecciones y quitar ramas muertas
- Cuando el analizador señalaba estructuras realmente grandes, reemplazaba un
switchde despacho de más de 200 líneas por helpers con nombre, o extraía helpers de persistencia desde una clase de 2,800 líneas - Aun así, esa extracción puede redistribuir la complejidad en más métodos en lugar de eliminarla, y algunas de las estructuras gigantes más grandes quedaron como
wontfix
Repositorios benchmark y diseño de tareas
- El benchmark se construyó sobre Harbor framework v0.4.0
- Se usaron 6 repositorios de pares mínimos en total
- 3 centrados en Java y 3 centrados en Python
- Algunos repositorios incluyen pequeñas cantidades de código en otros lenguajes
- Se componen de 3 repositorios open source públicos y 3 codebases privados de SonarSource
- Los pares privados sirven para evitar memorización ante la posibilidad de que los LLM evaluados hayan sido entrenados con repositorios públicos
- Las principales métricas de los lados limpio y desordenado de cada repositorio son las siguientes
sonar-sca*: issues 94 / 2,825, densidad de issues 0.73 / 20.66, densidad de complejidad cognitiva 30.6 / 56.5sonar-caas-poc*: issues 16 / 855, densidad de issues 0.61 / 27.16, densidad de complejidad cognitiva 179.8 / 218.9sonarcloud-codedatalake*: issues 199 / 1,319, densidad de issues 4.36 / 34.39, densidad de complejidad cognitiva 34.0 / 216.5commons-bcel: issues 694 / 2,711, densidad de issues 12.60 / 49.46, densidad de complejidad cognitiva 102.8 / 108.3genie: issues 152 / 1,262, densidad de issues 1.28 / 10.81, densidad de complejidad cognitiva 22.2 / 23.5ckan: issues 1,006 / 3,632, densidad de issues 7.54 / 27.50, densidad de complejidad cognitiva 69.3 / 76.5
- El diseño de tareas sigue tres reglas
- Paso por hotspots: las tareas se colocan para que pasen por regiones de código donde la diferencia de densidad de issues y complejidad cognitiva sea grande en ambos lados del par
- Descripción externamente observable: solo se proporcionan entradas, salidas y escenarios de ejemplo, sin nombres de archivos, funciones o estructuras internas
- Pruebas de superficie pública: las pruebas ocultas se ejecutan a través de interfaces que la aplicación expone al llamador, como CLI, rutas HTTP o librerías/API
- La creación de tareas se repartió entre agentes y humanos
- Un agente comparó la variante limpia y la desordenada para construir un mapa de diferencias
- Otro agente escribió el esquema de la tarea y su posibilidad de prueba
- Una persona seleccionó, editó y curó los esquemas más plausibles e interesantes
- Un tercer agente creó las instrucciones reales, las pruebas ocultas sobre la superficie pública y una implementación de referencia para uso interno
- La implementación de referencia debía pasar las pruebas ocultas en ambos repositorios
- El repositorio antes de la modificación no debía pasar las pruebas ocultas
- Las tareas que no cumplieron las condiciones tras dos iteraciones fueron reescritas o eliminadas por personas
- El número final de tareas fue 33, divididas en tres tracks
- 13 tareas de hotspots cognitivos: atraviesan una región compleja de alta densidad dentro de un solo método o una sola clase
- 14 tareas multmódulo: requieren cambios a través de dos o más módulos
- 6 tareas de calibración: ambos lados realizan tareas simples en la misma región para verificar si hay cambios no relacionados con la limpieza
Configuración experimental y métricas
- Todos los experimentos se realizaron con Claude Code usando el conjunto de herramientas por defecto
- Las cifras reportadas provienen de ejecuciones con Claude Sonnet 4.6
- Claude Haiku 4.5 también se probó sobre el mismo conjunto de tareas, pero como su tasa de aprobación fue demasiado baja y dificultaba leer con claridad las diferencias de footprint, se excluyó de los resultados principales
- El agente solo leía la descripción de la tarea
- No recibía preparación adicional sobre limpieza de código
- No sabía en qué lado del par mínimo estaba trabajando
- Cada tarea se ejecutó 10 veces en ambos lados del par
- El total de experimentos fue 33 × 2 × 10 = 660
- Cada ejecución se realizó en un sandbox containerizado
- CPU, memoria, almacenamiento y tiempo de reloj estaban limitados
- Había acceso a registros públicos de paquetes
- La imagen base incluía toolchains, cachés de compilación y servicios por repositorio
- Dentro de cada par, solo cambiaba el árbol de código fuente montado en
/app
- Se registraron 10 métricas
- Tasa de aprobación: proporción de ejecuciones cuyo estado final pasó las pruebas ocultas
- Tokens de entrada: cantidad de tokens que el modelo leyó en todos los turnos, compuesta en su mayoría por contenidos de archivos y reenvío de diálogo previo
- Tokens de salida: toda la producción generada por el modelo y subagentes, incluyendo prosa, código, rastros de razonamiento y llamadas a herramientas
- Caracteres de razonamiento: como la API de Anthropic no expone por separado los tokens de razonamiento, se contó el texto plano de los bloques de contenido de razonamiento
- Número de turnos de conversación: total de intercambios agente-herramienta
- Turnos antes de la primera edición: cantidad de turnos hasta la primera modificación de archivo
- Caracteres antes de la primera edición: caracteres de conversación en ese mismo tramo
- Número de archivos leídos: archivos únicos abiertos durante la ejecución
- Revisitas de archivos: veces que se volvió a leer un archivo ya leído y modificado
- Líneas modificadas: líneas de código fuente cambiadas por el patch final
- Las revisitas de archivos aparecen en flujos como
leer → editar → hacer otra cosa → volver a leer- El estudio las interpreta como una señal de incertidumbre sobre ediciones previas más que como exploración amplia
- Como las métricas de footprint del agente pueden variar mucho aun repitiendo la misma tarea con temperatura fija, se aplicó un filtro de outliers
- Dentro de cada combinación
(tarea, lado), se eliminaron antes del promedio las ejecuciones que se desviaban más de 50% de la mediana de las 10 repeticiones - En la práctica, se eliminó 9.7% del total de ejecuciones
- Dentro de cada combinación
- Las cifras a nivel dataset se promediaron con micro-promedio sobre las 33 tareas
- Para cada métrica, se sumaron los promedios por tarea del lado limpio y del lado desordenado, y luego se calculó la diferencia relativa
- La tasa de aprobación, como excepción, se reportó como diferencia absoluta en puntos porcentuales entre el lado limpio y el lado desordenado
Resultados: más impacto en el costo de exploración que en la tasa de éxito
- La limpieza del código no produjo un cambio significativo en la tasa de aprobación del agente
- En código más limpio, las métricas equivalentes a tokens bajaron entre 7% y 8%
- Las revisitas de archivos disminuyeron 34%, lo que muestra que el código limpio puede reducir el costo de releer y volver sobre pasos previos incluso para la misma tarea
- Por lo tanto, la limpieza del código puede verse como un eje aparte que influye en el costo de ejecución del agente, junto con la elección del modelo, el harness y el prompt
1 comentarios
Opiniones de Hacker News
Es una pregunta interesante para investigar, pero soy muy escéptico respecto del diseño experimental.
En el experimento usaron Opus 4.6 para sintetizar bases de código “degradadas en calidad” o “limpias” y compararlas entre sí.
Peor aún, no controlaron si rompieron las pruebas de la aplicación.
“La tasa de aprobación califica el estado final del agente según pruebas ocultas que escribimos para cada tarea. No verifica si el agente rompió pruebas no relacionadas que ya existían en el repositorio, y aunque las soluciones del lado limpio y del lado desordenado pasen las pruebas ocultas, podrían diferir en pruebas no calificadas”.
Si no se controla la calidad del resultado final, las conclusiones sobre el consumo de tokens parecen tener muy poco sentido.
Para una comparación justa, habría que escribir un único conjunto de pruebas que funcione en ambos proyectos de cada par.
No digo que el estudio sea bueno, pero puedo entender esa decisión, porque pasar pruebas no necesariamente se correlaciona con la efectividad del agente.
Solo muestra una situación en la que funcionalmente parece estar bien, pero cuesta más tokens terminar la tarea.
La fatiga por la IA dejó de ser aburrida hace tiempo; ahora simplemente es dolorosa.
En mi experiencia, la diferencia de rendimiento de los agentes es bastante grande entre una base de código llena de código muerto, código duplicado, rutas alternativas inalcanzables, abstracciones con fugas y patrones de diseño inmaduros, y otra con flujo de datos claro, encapsulamiento y estructura limpios.
En código malo, todos los modelos de frontera tuvieron que pasar por varias rondas de revisión de código, verificación de calidad y correcciones; en código bueno, he visto que aciertan al primer o segundo intento.
Se puede hacer que el LLM ejecute scripts para verificar estos puntos, y también imponer los mismos scripts como hooks de pre-commit.
Configurar esto de forma estricta en todas las bases de código en las que trabajo ha sido lo que más efecto tuvo en la codificación con agentes.
Aquí hay un artículo donde detallo más los distintos linters que uso: https://www.balajeerc.info/Use-Deterministic-Guardrails-for-...
Creo que un agente inevitablemente se desempeña mejor en una base de código limpia que en una enorme base de código hecha un desastre.
Es lo mismo que cuando rinde mejor si tiene acceso a una buena especificación y documentación.
En algún momento, esos patrones horribles empiezan a pegárseme también a mí.
Aquí hay un paper que intenta responder la pregunta, y los testimonios anecdóticos solo sesgan al lector, sin aportar valor para llegar a una conclusión objetiva sobre el problema.
La discusión más útil sería que todos leyeran el paper y criticaran su metodología o resultados.
Aun así, la gente que se queja de que los LLM no son tan buenos suele parecer del tipo que tiene bases de código desordenadas.
Un truco que he visto funcionar bien, si es Python, es pedir una refactorización así:
“Refactoriza el código Python para que sea más Pythonic. Por ejemplo, reduce clases y singletons, especialmente si eso mejora la velocidad. El código Python debe seguir los estándares de organización de código que se esperarían en paquetes Python open source populares, sin regresiones de rendimiento en los benchmarks”.
Para código Rust usé una variante como esta:
“La base de código Rust en
/srcse ha inflado con varios archivos de más de 1.000 líneas. Refactoriza la base de código Rust para que se ajuste a los estándares de organización de código esperados en proyectos Rust open source populares, sin regresiones de rendimiento en los benchmarks”.Estos prompts parecen mejorar el rendimiento del agente porque a) reorganizan el código de forma lógica y b) los nombres de archivo dan pistas semánticas sobre dónde está el código relevante.
En un archivo inflado de 5.000 líneas, el agente tiene que leer varios bloques para encontrar el código relevante, lo que es ineficiente.
El rendimiento en benchmarks normalmente también mejora después de la refactorización; especialmente en Rust compilado, quizá sea casualidad, pero no me voy a quejar.
Básicamente, las herramientas de codificación con agentes tienden a resistirse a borrar código. Incluso si les dices que lo borren, hacen todo tipo de esfuerzos por dejar el código viejo o agregar complejidad para que ese código todavía pueda ser llamado.
Si solo estás prototipando, es realmente molesto, porque termina acumulándose mucho código muerto y después genera confusión cuando quieres agregar más funcionalidades.
Una vez que sabes esto, simplemente puedes pedirles que eliminen lo legacy.
Mantener la base de código limpia incentiva a la IA a hacer lo correcto. Si hay muchas pruebas, agrega más al crear nuevas funcionalidades; si hay documentación, la actualiza sin que se lo digas.
A medida que mejoren los arneses de código, estas cosas vendrán cada vez más incorporadas, y será más fácil que incluso personas con poca experiencia en prompts obtengan resultados decentes.
Normalmente primero le pido que revise y genere una lista de elementos a revisar, y luego voy viendo cada punto con él para decidir sí/no o proponer cambios adicionales.
Especialmente si no tienes pruebas end-to-end cuidadosas para todo el sistema.
De todos modos, las reglas de estilo de código ya las tengo en CLAUDE.md.
El enfoque de “un pipeline de agentes que limpia repositorios desordenados” se ve terrible y, por sí solo, basta para invalidar todo el estudio.
Parece que en este trabajo la mitad de los pares mínimos se crearon de esa manera.
No confiaría en absoluto en una conclusión que exige asumir que un repositorio “limpiado” por IA representa realmente una buena base de código.
Aquí, “limpio” no significó pedirle al agente que simplemente escribiera mejor código.
Le dimos una lista de 50 a 100 violaciones de reglas de analizadores estáticos y la cantidad de líneas de código, y le pedimos que las eliminara.
Luego verificamos si las violaciones de reglas se habían resuelto.
Reescribir código con un LLM para eliminar este tipo de violaciones es una práctica bastante aceptada.
El enfoque existente de Sonar basado en LLM de una sola pasada [1] lleva más de un año en operación, y su enfoque agéntico reciente [2] también hace bastante bien la misma tarea.
[1] https://www.sonarsource.com/solutions/ai/ai-codefix/
[2] https://www.sonarsource.com/products/sonarqube/remediation-a...
Creo que, por supuesto, no puede sino influir.
Ningún modelo puede meter una base de código real completa en el contexto, y tiene que revisar el código como lo haría una persona.
Es decir, buscar y leer archivos.
Si los archivos están donde se espera y tienen nombres que el modelo o una persona buscarían primero, los encuentra al primer intento; si no, necesita búsquedas profundas y varios intentos.
Le basta con ver todas las rutas y pasar a la siguiente ignorando las zonas que ya trató.
Es bastante parecido a cómo trabaja un desarrollador.
Vimos resultados similares en un trabajo parecido que está en curso en NJIT. A esto lo llamamos contagio contextual de calidad.
Lo interesante aquí son las situaciones reales comunes en la industria: bases de código con calidad mezclada, bases donde conviven patrones de código legacy y patrones “buenos” más nuevos, lo que hace que el agente confunda las convenciones.
El diseño de pares mínimos es, de hecho, una de las fortalezas, porque intenta aislar la limpieza de otros factores como estructura, dependencias y pruebas, en lugar de comparar repositorios entre sí.
Dicho eso, usar código “ensuciado” generado por un LLM es algo cuestionable, porque no es un enfoque mecánico ni guiado por humanos.
La crítica más importante, como otros señalaron correctamente, es la decisión de no verificar todo el conjunto de pruebas. La afirmación de “equivalencia de comportamiento” solo es tan válida como las pruebas y la cobertura.
La hipótesis resulta convincente por dos motivos: 1) los LLM imitan lo que ven en la base de código, así que tiene sentido lo de basura entra, basura sale; 2) coincide con lo que muchos ingenieros han sentido intuitivamente al usar estos modelos durante el último año o dos.
Greenfield casi siempre es más fácil que incorporarse a una base de código ocupada, y el desorden aparece en integraciones complejas y en mantener sistemas con propósitos legacy.
Aunque el agente aprenda a abrirse paso entre los stubs y el código WET que dejó, ¿de verdad queremos una base de código donde una persona no pueda seguir lo que está pasando?
Por eso, en lo personal, al menos quiero que el código cuente con código lo que hace.
Aunque dos funciones pequeñas tengan lógica que podría extraerse a un helper compartido, a veces un programador humano no lo hace porque sabe que esa abstracción quedaría sucia y se rompería si se intenta cambiar apenas una de las dos.
Es interesante verlo cuantificado.
Una estructura limpia parece reducir la carga cognitiva tanto de las personas como de los agentes, y eso explica por qué los nombres y la modularización son más importantes de lo que parece.
Es difícil de cuantificar, pero eso es lo que todos los indicadores de calidad de código intentan capturar al final.
Visto con ese criterio, si las métricas de calidad de código usadas son razonables, la conclusión no es tan sorprendente.
Si una métrica de calidad es buena en el contexto de agentes de programación, este es precisamente el resultado que se esperaría.
Muchos tokens se gastan en explorar el código: encontrarlo o seguir los puntos de llamada, creando suficiente contexto para realizar la tarea.
Si al agente se le da acceso a algún tipo de LSP y, en un monorepo, se le ofrecen guías jerárquicas con archivos como AGENTS.md, se puede reducir mucho el uso de tokens dedicado a la exploración.
Pero una base de código dispersa acaba necesitando exploración de una forma u otra para resolver cada tarea.
Y esta exploración no es solo consumo de tokens. En cada paso se repite la latencia de ida y vuelta: espera del LLM, prefill, decodificación, salida, parseo del agente, llamada a herramientas, respuesta de herramientas y vuelta al LLM.
Algunas partes se pueden paralelizar, pero en la práctica la mayoría son secuenciales, así que el trabajo se vuelve bastante lento.
Para usar agentes de forma eficiente, la localidad y la estructura son clave. La ventana de contexto siempre es limitada, y la atención dentro de ella tampoco es consistente.
Según mi experiencia, todo lo que afecta a un ingeniero también afecta a un agente.
Buenas abstracciones, métodos de tamaño adecuado, buenos nombres, estructuras con principios dentro de los servicios y entre servicios, pruebas unitarias, todo eso entra ahí.
Históricamente, estas cosas eran trabajo de los ingenieros y servían para facilitar que otras personas contribuyeran al código.
Ahora facilitan que no solo otras personas, sino también otros agentes, contribuyan al código.