ConstructiCat Logo
CodeBust.
Browse section ▾

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.

##Signs and Symptoms

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.

##Reasons for the Problem

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ó. Como dijo Eno Reyes (Factory), gran parte de una convención es tácita: patrones que los humanos absorben leyendo la base de código y que los agentes simplemente nunca ven.
  • 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. GitClear vincula la explosión de clones directamente a que los asistentes de IA hacen que insertar con la tecla de tabulación sea más barato que reutilizar, mientras que la proporción de cambios por refactorización cayó del 25 % (2021) a menos del 10 % (2024).
  • 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 llama al efecto agregado un "ejército de júniores": rápido, funcional, sin criterio arquitectónico.
  • 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 el olor arquitectónico de "funcionalidad dispersa / espejismo modular", donde el comportamiento relacionado queda fragmentado entre archivos sin cohesión real.

##Treatment

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: Eliminar duplicación / Reemplazar código en línea con llamada a función, Sustituir algoritmo y Extraer función 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.

##Detected by