ConstructiCat Logo
CodeBust.
Browse section ▾

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.

##Signs and Symptoms

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 (FooServiceFooRepositorydb) 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 a Intermediario (Middle Man) / Clase perezosa (Lazy Class) / Generalidad especulativa (Speculative Generality), y la otra cara del hallazgo de «sobreespecificación» de OX Security (soluciones hiperespecíficas de un solo uso en lugar de componentes generalizables).

// 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).

##Reasons for the Problem

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 puede ver que el ayudante se invoca una sola vez, ni que ya existe en otro lugar una abstracción canónica, así que inventa nuevas capas superficiales en lugar de reutilizarlas. Esta es exactamente la tendencia medida por GitClear: las líneas refactorizadas («movidas») cayeron de ~24% (2020) a menos del 10% (2024), mientras las líneas copiadas y pegadas aumentaban y los clones de código crecían ~4 veces. Los modelos añaden estructura, pero nunca la consolidan.
  • Fijación con seguir el manual al pie de la letra + sobreespecificación. OX Security descubrió que la IA sobreespecifica en el 80–90% del código generado, produciendo «soluciones estrechas que no se pueden reutilizar», y evita refactorizaciones en el 80–90% («se detiene en lo suficientemente bueno»). Los envoltorios superficiales son el residuo visible.
  • Literalidad ante las instrucciones y adulación. Cuando se le pide «añade una capa de servicio» o «hazlo limpio/modular», el modelo añade capas de forma literal aunque aplique YAGNI: la tendencia a la sobreingeniería o de «agente dios» que los profesionales describen en la discusión code-smells-for-AI-agents.
  • 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 debe atravesar capas muertas; quienes leen el código tienen que recorrer mentalmente los Intermediarios. El argumento del «ejército de júniores» de OX: los revisores no pueden seguir el ritmo de un código que parece estructurado pero no tiene costuras reales.
  • Corrección y seguridad. Tanto arXiv 2509.20491 como SonarSource señalan que los alias y los envoltorios «ocultan los puntos de llamada decisivos», degradando el análisis de flujo de datos/control y de contaminación (taint): un envoltorio puede tragarse un error en silencio u ocultar un sumidero (sink) a un escáner.
  • 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.

##Treatment

Tácticas de revisión y de prompting

  • Aplica la regla de tres: "No añadas una función/clase/interfaz a menos que se use en ≥2 lugares o oculte una decisión/invariante real. Incorpora (inline) los ayudantes de un solo uso (YAGNI)."
  • 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): Incorporar función (Inline Function), Incorporar clase (Inline Class), Eliminar intermediario (Remove Middle Man) para los pasos directos; Colapsar jerarquía (Collapse Hierarchy) / eliminar la interfaz en las interfaces con una sola implementación. Cuando la duplicación es el motivo real, aplica Extraer función una sola vez hacia un ayudante genuinamente compartido y parametrizado, no un envoltorio por cada punto de llamada.

Antes — Intermediario (Middle Man):

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

Después — Incorporar función / 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(); } }

##Detected by