Logotipo de ConstructiCat
CodeBust.
Explorar la sección ▾

Deriva de convenciones.

Código generado por IA que ignora en silencio las convenciones establecidas de un repositorio —reinventa ayudantes, elige la biblioteca equivocada y usa nombres y manejo de errores ajenos al estilo de la casa— de modo que la base de código deriva hacia un estilo genérico, el promedio de internet.

##Signos y síntomas

Un revisor reconoce la deriva de convenciones cuando un cambio de la IA funciona pero no parece pertenecer a la base de código. Recurre al patrón promedio global en lugar del local:

  • Se escribe un ayudante nuevo en línea aunque ya exista una utilidad probada en batalla (utils/date.ts, lib/apiClient, un tipo Result compartido).
  • Aparece una biblioteca o API distinta del estándar del repositorio (axios en una app que estandarizó un envoltorio de fetch; moment donde el repositorio usa date-fns).
  • Los nombres, la distribución de archivos y el estilo de manejo de errores divergen: camelCase donde el módulo es snake_case, un throw new Error("...") en crudo donde todo lo demás devuelve un error tipado, un try/catch a medida con logging en lugar del logger compartido.
  • Cada nuevo requisito recibe su propio bloque recién acuñado y ajustado a su caso en lugar de reutilizar una abstracción (la "sobreespecificación" de OX Security, vista en el 80–90 % del código de IA).
  • Firma sintomática: los bloques duplicados se multiplican. GitClear midió un salto de 8× en bloques de clones de 5+ líneas en 2024, con las líneas copiadas/pegadas superando por primera vez a las movidas (refactorizadas).
// Estilo de la casa (ya en el repositorio)
import { apiClient } from "@/lib/apiClient";   // envuelve auth, reintentos, URL base
import { formatDate } from "@/utils/date";      // de toda la app, sensible a la configuración regional

// Cambio generado por IA — se desvía de ambos
import axios from "axios";                       // no es un patrón de dependencias del proyecto
async function getUser(id: string) {
  try {
    const res = await axios.get(`https://api.example.com/users/${id}`); // URL base codificada
    return { ...res.data, joined: new Date(res.data.joined).toLocaleDateString() }; // reinventa formatDate
  } catch (e) {
    console.log("error", e);                     // no es el logger compartido; traga el error
  }
}

La pista es la consistencia, no la corrección: tres revisores encuentran cada uno una elección distinta "errónea pero funcional", y ninguna coincide con el archivo de al lado.

##Causas del problema

Por qué los modelos lo producen

  • Regresión a la media del entrenamiento. Los LLM se entrenan con un corpus enorme de código que es el promedio de internet, no con tu repositorio. Sin instrucciones, emiten la expresión idiomática estadísticamente más común (axios, moment, console.log), no la de tu casa: tus convenciones son una señal diminuta y fuera de distribución frente al promedio global.
  • Autoconsistencia del siguiente token frente al alcance del repositorio. Localmente es más fácil completar un bloque autónomo (insertar en línea un formateador de fechas) que "saber" que @/utils/date existe e importar un identificador que el modelo nunca vio. La reutilización requiere contexto del repositorio que el modelo no posee; la reinvención solo requiere el búfer actual.
  • Contexto del repositorio limitado / con pérdidas. El ayudante canónico, la configuración del linter y el ADR que dice "usa el envoltorio de fetch" suelen quedar fuera del prompt. El modelo no puede seguir una convención que nunca se le mostró. Además, gran parte de una convención es tácita: patrones que los humanos absorben leyendo la base de código y que un agente nunca ve a menos que se pongan por escrito.
  • Adulación / foco literal en la tarea. Cuando se le pide "añade getUser", el modelo hace exactamente eso y no se ofrece a decir "en realidad ya tenemos un cliente para esto". Optimiza para completar la tarea enunciada, no para encajar en el sistema. La "fijación por el manual" de OX Security (80–90 % de las muestras) es la misma fuerza: sigue una convención genérica de libro de texto en lugar de evaluar la del propio proyecto.
  • Desactualización por la fecha de corte del entrenamiento. Si el repositorio migró a una biblioteca o patrón más reciente después de la fecha de corte del modelo, este reintroduce con seguridad la versión con la que se entrenó.

Por qué resulta perjudicial

  • Mantenibilidad y carga cognitiva. Cada elección desviada es una forma más de hacer lo mismo. Los lectores deben tener en la cabeza N variantes de "cómo formateamos las fechas"; la base de código pierde su única fuente de verdad.
  • Corrección ante los cambios. La lógica duplicada/reinventada diverge en silencio: un error corregido en el ayudante compartido no se corrige en la copia de la IA. Según los datos de GitClear de 2025, las líneas asociadas a la refactorización (líneas «movidas») cayeron del 25 % de las líneas modificadas en 2021 a menos del 10 % en 2024, mientras que las líneas copiadas/pegadas subieron del 8,3 % al 12,3 % (GitClear 2025); el resumen de DevClass del informe lo relaciona con que los asistentes abaratan insertar un bloque nuevo frente a reutilizar uno existente (DevClass).
  • Seguridad. La validación, la autenticación o la construcción de consultas reinventadas eluden la vía compartida y endurecida (URLs base codificadas, errores tragados, SQL ad hoc por cadenas). OX compara las herramientas de IA con "un ejército de desarrolladores júnior talentosos pero inexpertos": rápidos y funcionales, pero sin criterio arquitectónico (OX Security).
  • Carga de revisión y acumulación de deuda técnica. La deriva no la detectan las pruebas (el código funciona), así que llega a la revisión o no se detecta en absoluto, agravándose hasta una fragmentación a nivel de arquitectura en la que el comportamiento relacionado queda disperso entre archivos sin cohesión real. Zhu, Tsantalis y Rigby catalogan un olor arquitectónico relacionado en el código escrito por LLM y agentes al que llaman "Modular Mirage" (arXiv 2605.02741).

##Tratamiento

Tácticas de prompting y de flujo de trabajo

  • Muestra las convenciones. Pon las reglas donde el agente las lee: un archivo CLAUDE.md/AGENTS.md/de reglas que enumere el cliente autorizado, el logger, el tipo de error, los nombres y el "usa X, no Y". Las convenciones que el modelo no puede ver, no puede seguirlas.
  • Apunta al código canónico. "Usa @/lib/apiClient y @/utils/date; no añadas nuevas bibliotecas HTTP. Iguala el manejo de errores de services/orders.ts." Da ejemplos few-shot de la expresión idiomática del repositorio pegando un archivo modelo.
  • Pregunta antes de que escriba. "¿Qué ayudantes/abstracciones existentes cubren esto? Reutilízalos; solo añade código nuevo si ninguno encaja." Esto convierte la reinvención en reutilización por adelantado.
  • Haz del linter la barrera. Exige que el agente ejecute el formateador, el linter y el verificador de tipos y corrija todos los hallazgos antes de devolver. El consejo de Factory es sistematizar las señales de calidad (lint/formato/verificación de tipos/pruebas) para que el agente optimice hacia ellas en lugar de derivar.
  • Revisa el encaje, no solo la función. Revisa las importaciones en busca de bibliotecas no autorizadas; busca con grep la lógica que debería haber llamado al ayudante compartido; ejecuta un detector de copiar/pegar sobre el PR.

La refactorización: nombra los movimientos clásicos: elimina el Código Duplicado sustituyendo la copia en línea por una llamada al ayudante existente, Sustituir algoritmo y Extraer método si la abstracción adecuada aún no existe.

// ANTES — desviado, reinventa el cliente + la lógica de fechas, URL codificada, error tragado
import axios from "axios";
async function getUser(id: string) {
  try {
    const res = await axios.get(`https://api.example.com/users/${id}`);
    return { ...res.data, joined: new Date(res.data.joined).toLocaleDateString() };
  } catch (e) {
    console.log("error", e);
  }
}

// DESPUÉS — reutiliza las convenciones establecidas
import { apiClient } from "@/lib/apiClient";
import { formatDate } from "@/utils/date";

async function getUser(id: string) {
  const user = await apiClient.get<User>(`/users/${id}`); // la URL base, la auth, los reintentos y los errores tipados se gestionan aquí
  return { ...user, joined: formatDate(user.joined) };
}

Si el mismo bloque desviado aparece en varios PR, es señal de que la convención es indetectable: corrige la causa raíz documentándola en el archivo de reglas y/o exponiéndola a través de un único punto de entrada obvio, en lugar de volver a revisar cada instancia.

##Detectado por