Evitación de la refactorización.
Los asistentes de IA apilan código nuevo, casi duplicado, junto al que ya existe en lugar de reestructurarlo, de modo que la duplicación crece y la refactorización —el retrabajo que mantiene sana una base de código— desaparece sin que nadie lo note.
##Signs and Symptoms
Quien revisa ve un diff que es casi enteramente adiciones. El modelo resolvió el prompt añadiendo una función, rama o archivo nuevos en vez de editar la abstracción que debería haber absorbido el cambio. Señales reveladoras:
- Copiar-pegar-con-retoques: una nueva función es 90% idéntica a una existente, diferenciándose por un literal, un nombre de campo o un
ifde más. - Casi-duplicados en paralelo:
formatUserCsv,formatAdminCsv,formatGuestCsv, todos reimplementando a mano el mismo bucle en vez de una sola función parametrizada. - Proliferación de ramas en lugar de extracción: un método largo gana otro
else ifen vez de que el modelo saque una estrategia o tabla de búsqueda. - Reinvención: un
deepClone/debounce/parser de fechas escrito a mano cuando el repositorio ya importa lodash, date-fns, etc. (OX Security lo llama "Estilo vainilla"). - Sobreespecificación: una función auxiliar hiperespecífica de un solo uso donde ya existe una genérica dos archivos más allá.
- "Déjà-vu de fallos": el mismo arreglo hay que aplicarlo en tres copias porque el duplicado nunca se aplicó con DRY.
// Existente en el repositorio:
function priceWithTax(items: Item[]) {
const subtotal = items.reduce((s, i) => s + i.price * i.qty, 0);
return subtotal * 1.2; // 20% de IVA
}
// Lo que el modelo añade para el nuevo caso "con descuento":
// una segunda copia entera en vez de un parámetro:
function priceWithTaxDiscounted(items: Item[], discount: number) {
const subtotal = items.reduce((s, i) => s + i.price * i.qty, 0); // duplicado
return subtotal * (1 - discount) * 1.2; // lógica de IVA duplicada
}
El olor es estructural, así que es más visible en el cambio completo que en un solo fragmento: mucho verde, poco código movido, y un escáner de duplicación encendiéndose sobre las nuevas líneas.
##Reasons for the Problem
Por qué los modelos lo producen
- Sesgo aditivo del siguiente token. Un LLM completa el prompt que tiene delante. Emitir un bloque nuevo y autónomo es la continuación localmente más probable y de menor riesgo; editar una abstracción lejana exige mantener el módulo entero en la memoria de trabajo y predecir una edición globalmente coherente, lo cual es más difícil y no es lo que recompensa el prompt inmediato.
- Contexto del repositorio escaso. Los asistentes de programación rara vez cargan la base de código entera. Si el modelo no puede ver la función auxiliar, la biblioteca o la clase base existentes, no puede reutilizarlas, así que las reconstruye. El informe de OX Security enmarca esto como el efecto "Ejército de júniors": mucho código localmente funcional, sin memoria arquitectónica.
- Refactorizar es arriesgado y no se recompensa. Reestructurar toca código que al modelo no se le pidió cambiar y puede romper a quienes lo llaman y a los tests. Un asistente adulador, de "tú haz que funcione", minimiza el radio de impacto no tocando el código que ya funciona: justo el comportamiento que OX midió como "Evitación de refactorizaciones" en el 80-90% del código generado por IA, y "Sobreespecificación" (de un solo uso en vez de reutilizable) en otro 80-90%.
- Obsolescencia de los datos de entrenamiento. El modelo puede no saber que el repositorio adoptó un módulo de utilidades o actualizó una biblioteca después de su fecha de corte, así que reimplementa a mano lo que ya existe.
- Generar es barato, borrar da miedo. Producir 40 líneas nuevas no le cuesta nada al modelo; convencerlo de borrar y consolidar 40 líneas existentes choca con su instinto de preservar.
Por qué hace daño
- La duplicación se acumula. El análisis de GitClear de 2025 sobre 211M de líneas modificadas halló que las líneas copiadas/pegadas subieron de ~8,3% (2021) a 12,3% (2024) —el primer año en que la duplicación superó al código "movido" (refactorizado)— mientras que las líneas refactorizadas cayeron de ~25% a menos del 10%, una caída de aproximadamente el 60%. Los bloques de más de 5 líneas duplicadas se multiplicaron ~8× en 2024.
- Mantenibilidad y corrección. Cada clon es un lugar donde un arreglo futuro puede olvidarse: el "déjà-vu de fallos", donde el mismo defecto reaparece y hay que parchearlo N veces (OX: el 70-80% del código de IA viola así los principios de reutilización).
- Acumulación de deuda técnica. La refactorización es el retrabajo que mantiene baja la entropía; suprimirla significa que la deuda se crea pero nunca se paga. La base de código crece más rápido de lo que mejora.
- Carga de revisión. Quien revisa debe ahora comparar a ojo bloques casi idénticos para confirmar que son intencionadamente iguales: el desplazamiento de carga cognitiva que describe el debate sobre "olores de código para agentes de IA" de Stack Overflow; el trabajo pasa de escribir a revisar y consolidar.
##Treatment
Tácticas de prompts y revisión
- Apunta al modelo hacia lo que existe. "Antes de añadir código, busca en el repositorio una función auxiliar/util/clase base existente y reutilízala; si ninguna encaja, generaliza la más cercana." Pega el módulo relevante en el contexto para que pueda ver de verdad la abstracción.
- Restringe la forma del diff. "Prefiere editar funciones existentes a añadir nuevas. Si dos vías de código comparten lógica, extrae una función compartida (Extraer función) en lugar de duplicar."
- Haz que ejecute las herramientas. Exige al asistente que ejecute el escáner de duplicación (jscpd / PMD CPD) y el linter, y que resuelva cualquier nuevo hallazgo de bloque duplicado antes de declararlo terminado: esto convierte un olor invisible en una puerta de control que falla.
- Pide la refactorización de forma explícita como segundo paso. Generación y consolidación son tareas distintas; haz "que funcione" y luego, por separado, "ahora aplica DRY a esto y elimina la duplicación", algo que los modelos manejan mucho mejor cuando se les pide directamente.
- Añade una puerta de calidad en CI para que la duplicación no pueda escalar sin que nadie lo note (umbral de líneas duplicadas de Sonar, o
jscpd --threshold).
La refactorización real: nombra los movimientos clásicos: Extraer función, Parametrizar función y Subir / Reemplazar condicional por polimorfismo o una tabla de búsqueda para matar el Código duplicado.
// Antes: dos funciones casi idénticas (refactorización evitada)
function priceWithTax(items: Item[]) {
const subtotal = items.reduce((s, i) => s + i.price * i.qty, 0);
return subtotal * 1.2;
}
function priceWithTaxDiscounted(items: Item[], discount: number) {
const subtotal = items.reduce((s, i) => s + i.price * i.qty, 0);
return subtotal * (1 - discount) * 1.2;
}
// Después: Extraer función + Parametrizar función
const VAT = 1.2;
const subtotalOf = (items: Item[]) =>
items.reduce((s, i) => s + i.price * i.qty, 0);
function priceWithTax(items: Item[], discount = 0) {
return subtotalOf(items) * (1 - discount) * VAT;
}
Una única fuente de verdad para el subtotal y el IVA: un cambio futuro en la regla del impuesto es ahora una edición de una línea en lugar de una búsqueda por N sitios.
##Detected by
- jscpd duplication above --threshold / --min-tokens — Detección de copiar/pegar
- PMD Copy/Paste Detector (minimum-tokens duplication) — CPD
- SonarQube duplicated_lines_density / quality-gate duplicated-blocks condition — Duplicaciones
- ESLint (eslint-plugin-sonarjs) sonarjs/no-identical-functions — no-identical-functions
- ESLint (eslint-plugin-sonarjs) sonarjs/no-duplicate-string — no-duplicate-string