Logotipo de ConstructiCat
CodeBust.
Explorar la sección ▾

Abstracción superficial.

Un asistente de IA envuelve el código en funciones, clases o interfaces adicionales que añaden una capa de indirección sin ocultar complejidad alguna ni habilitar la reutilización: abstracciones que se limitan a reenviar a un único punto de llamada.

##Signos y síntomas

Un revisor reconoce la Abstracción superficial cuando una nueva función/clase/interfaz añade un nombre y una capa pero ningún aprovechamiento: su cuerpo repite su firma, reenvía directamente a una única llamada subyacente, se invoca exactamente una vez o no oculta ninguna decisión, invariante ni variación. El código parece estructurado en capas y «de nivel empresarial», pero hay que leer cada capa para entender cualquier cosa: la abstracción no encapsula nada.

Señales reveladoras:

  • Funciones/métodos de paso cuyo cuerpo es una única llamada delegada sin comportamiento añadido.
  • Clases envoltorio (FooService → FooRepository → db) donde cada capa se limita a llamar a la siguiente.
  • Interfaces con una sola implementación (IClock con solo SystemClock) introducidas de forma especulativa «para facilitar las pruebas» sin una segunda implementación ni un doble.
  • Funciones utils/helpers usadas exactamente en un solo lugar.
  • Alias/renombrado redundante que reenvía un valor sin cambios.

Este es el clásico olor Hombre en el medio / Clase perezosa / Generalidad especulativa, y la otra cara del hallazgo de sobreespecificación de OX Security ("soluciones hiperespecíficas de un solo uso que no pueden adaptarse a otros fines").

// La IA añadió tres "capas" que reenvían cada una a la siguiente.
function getUserName(id: string) {
  return fetchUserName(id);            // no aporta nada
}
function fetchUserName(id: string) {
  return userRepository.getName(id);   // no aporta nada
}
class UserRepository {
  getName(id: string) {
    return db.query("SELECT name FROM users WHERE id = ?", [id]);
  }
}
// getUserName es el único punto de llamada, usado una sola vez.
// Dos de las tres capas no aportan ningún comportamiento: puro Intermediario (Middle Man).

##Causas del problema

Por qué los modelos la producen

  • Imitar la forma de una buena arquitectura. Los corpus de entrenamiento están saturados de tutoriales y andamiaje empresarial: capas de servicio/repositorio/factoría, inyección de dependencias, interfaces con una sola implementación. El modelo reproduce por coincidencia de patrones la ceremonia de la abstracción sin su sustancia, porque un ayudante con nombre resulta plausible a nivel local y «parece profesional». La generación del siguiente token se optimiza para parecerse a código bien estructurado, no para determinar si una capa justifica su existencia.
  • Sin contexto del repositorio. Al generar de forma local, el modelo no ve que el helper se llama una sola vez ni que ya existe una abstracción canónica en otra parte, así que inventa capas superficiales nuevas en lugar de reutilizar. El análisis de GitClear de 2025 midió la misma tendencia: las líneas asociadas a refactorización ("movidas") cayeron del 25% de las líneas cambiadas en 2021 a menos del 10% en 2024, mientras que las copiadas y pegadas subieron del 8,3% al 12,3%, lo que GitClear resume en su titular como un crecimiento de 4x en los clones de código. Los modelos añaden estructura, pero rara vez la consolidan.
  • Apego al manual + sobreespecificación. El informe de OX Security sobre más de 300 repositorios recoge la fijación con el manual (By-The-Book Fixation) —código que "se ciñe rígidamente a los patrones estándar incluso cuando soluciones más innovadoras serían más eficaces"— y la sobreespecificación (Over-Specification) —"soluciones hiperespecíficas de un solo uso que no pueden adaptarse a otros fines"—, cada una en el 80–90% del código generado por IA, además de la evitación de refactorizaciones (Avoidance of Refactors, también 80–90%): la IA "destaca generando código nuevo, pero nunca vuelve atrás para mejorar la arquitectura". Los envoltorios superficiales son el residuo visible.
  • Literalidad con el prompt y complacencia. Si se le pide "añade una capa de servicio" o "hazlo limpio/modular", el modelo añade capas al pie de la letra aunque aplique YAGNI: sobreingeniería por encargo, no por necesidad.
  • Obsolescencia por la fecha de corte del entrenamiento. Envolverá una API obsoleta en un adaptador delgado en lugar de migrar fuera de ella.

Por qué resulta perjudicial

  • Mantenibilidad y carga de revisión. Cada cambio tiene que atravesar capas muertas; quien lee debe recorrer cada Hombre en el medio para llegar a la llamada real. OX compara la IA con "un ejército de desarrolladores júnior con talento pero sin experiencia" cuya producción supera a la revisión, y el código que parece estructurado pero no tiene costuras reales es justo el que se cuela.
  • Corrección y seguridad. Cada envoltorio y cada alias es un salto más entre la entrada y la llamada que importa: el revisor tiene que seguirlo, y también cualquier análisis de flujo de datos o de contaminación (taint). Un paso intermedio que "no añade nada" puede aun así tragarse un error u ocultar un sumidero (sink) tras un nombre inofensivo.
  • Acumulación de deuda técnica. Las interfaces especulativas con una sola implementación se osifican; y como cada abstracción superficial es de un solo uso, la siguiente variación se copia y pega en lugar de parametrizarse, alimentando directamente el crecimiento de clones que midió GitClear. Pagas por la indirección sin llegar nunca a cobrar la reutilización que prometía.

##Tratamiento

Tácticas de revisión y de prompting

  • Aplica YAGNI y la regla de tres: "No añadas una función/clase/interfaz para reutilizarla hasta que tres puntos de llamada la necesiten de verdad; por debajo de eso, una capa nueva debe ocultar una decisión o un invariante reales. Incorpora en línea los helpers de un solo uso."
  • Fuerza la reutilización: "Antes de añadir un ayudante/servicio/interfaz, busca uno existente en el repositorio y reutilízalo." Dirige el modelo al módulo canónico.
  • Haz que justifique cada capa: "Enumera cada nueva función/clase e indica qué variación o invariante oculta; elimina las que se limiten a reenviar."
  • Exígele que ejecute el linter (no-useless-constructor, no-useless-rename) y un análisis de duplicación (jscpd) y que corrija lo que señalen.

Refactorizaciones (nómbralas): Método en Línea, Incorporar clase, Eliminar intermediario para los pasos intermedios; Colapsar jerarquía o eliminar la interfaz para las interfaces con una sola implementación. Cuando el verdadero motor es la duplicación, aplica Extraer método una sola vez para obtener un helper realmente compartido y parametrizado, no un envoltorio por cada punto de llamada.

Antes — Hombre en el medio:

function getUserName(id: string) { return fetchUserName(id); }
function fetchUserName(id: string) { return userRepository.getName(id); }

Después — Método en Línea / Eliminar intermediario (un único punto de llamada, sin comportamiento añadido):

const name = userRepository.getName(id);

Antes — Interfaz especulativa con una sola implementación:

interface IClock { now(): number; }
class SystemClock implements IClock { now() { return Date.now(); } }

Después — Colapsar jerarquía (usa el tipo concreto; reintroduce la interfaz solo cuando exista realmente una segunda implementación, p. ej. un doble de prueba):

class SystemClock { now() { return Date.now(); } }

##Detectado por

  • ESLint no-useless-constructor — Constructor de paso que solo delega en super o está vacío
  • ESLint no-useless-rename — Alias redundante de import/export/desestructuración que reenvía un nombre sin cambios
  • PMD (Java) UselessOverridingMethod — Método sobrescrito que se limita a llamar a super con los mismos argumentos
  • SonarSource (Sonar S4144) S4144 — Señala abstracciones de un solo uso duplicadas con cuerpos idénticos
  • jscpd --threshold — Detecta los ayudantes de un solo uso casi duplicados que las abstracciones superficiales no consiguen consolidar