Módulo 2: Cuándo NO abstraer

2. Cada abstracción se paga

Descripción

Al terminar esta lección vas a poder decir cuánto cuesta una abstracción, no solo que cuesta. Vas a tener los cuatro costos concretos desglosados —un salto más al leer, un archivo más que abrir, un concepto más que aprender, y una capa más donde puede esconderse un bug—, una forma de estimarlos que se puede poner en una discusión de equipo, y una distinción que resuelve la mitad de los casos por sí sola: la que separa una abstracción que oculta algo de una indirección que solo redirige.

Esto importa porque casi todo el material sobre diseño de software habla de los beneficios de abstraer y muy poco del precio. Y cuando el precio no se nombra, el cerebro asume que es cero. Así se llega a frases como "total, agregar una interfaz no cuesta nada", que es exactamente igual de falsa que "total, agregar una escala al vuelo no cuesta nada". Cuesta. Cuesta poco en algunos casos y muchísimo en otros, y la diferencia entre esos casos es lo que estamos aquí para aprender a ver.

Hay un motivo por el que este desglose viene tan temprano en el módulo. Las tres lecciones que siguen —la regla de tres, la abstracción prematura, YAGNI— son en el fondo aplicaciones de esta misma idea a situaciones distintas. Si la cuenta del costo te queda clara aquí, las otras tres se te van a hacer evidentes. Si no, van a sonar a reglas arbitrarias que hay que memorizar, que es justo lo que esta guía intenta evitar.

Conexión con el módulo: la lección 1 puso el marco y te mostró el rincón plugins/ de Boletia con dos números —seis saltos, ciento ochenta y tres líneas alrededor de nueve—. Esta lección explica de dónde salen esos números y cómo se calculan en cualquier otro caso. Con esta herramienta en la mano, la lección 3 va a poder decirte cuándo tienes información suficiente para pagar ese precio (la regla de tres), la lección 4 va a comparar ese precio contra el de la alternativa más obvia (duplicar), y la lección 5 va a distinguir las compras baratas y reversibles de las caras y permanentes. La lección 7 cierra el círculo mostrando que este costo y el acoplamiento son las dos caras de una misma moneda.

El intermediario

Imagina que tienes una cafetería y compras café en grano.

Opción A: le compras directo al productor. Conoces a don Ernesto, que tiene una finca en Veracruz. Le hablas por teléfono, le pides sesenta kilos, te los manda. Cuando el café llega raro, le hablas a él y él sabe exactamente qué pasó, porque él lo cosechó.

Opción B: le compras a un distribuidor. El distribuidor trabaja con veinte fincas. Tú le pides "sesenta kilos de arábica de altura" y él resuelve de dónde salen. Si la finca de don Ernesto tiene mala temporada, el distribuidor te consigue el mismo perfil de otro lado y tú ni te enteras.

Fíjate en lo que la opción B te da de verdad. No te da café —el café lo sigue produciendo una finca—. Te da la libertad de que la finca cambie sin que tu operación cambie. Eso es exactamente lo que hace una abstracción: no hace el trabajo, protege a quien la usa de los detalles de quién lo hace.

Ahora fíjate en lo que te cobra, porque es lo que casi nadie enumera:

  • Un margen. Un porcentaje sobre cada compra, para siempre, uses o no la flexibilidad.
  • Un eslabón más donde algo puede fallar. Tu pedido puede perderse en la finca o en la bodega del distribuidor. Antes había un lugar; ahora hay dos.
  • Pérdida de información. Cuando el café llega raro, le hablas al distribuidor y él te dice "voy a preguntar". Ya no sabes de qué finca vino ni por qué. La capa que te protege de los detalles también te esconde los detalles cuando los necesitas.
  • Un concepto más que explicarle a quien llega. Al empleado nuevo ya no le basta con saber "le pedimos a don Ernesto". Ahora tiene que entender qué es un distribuidor, qué le puedes pedir y qué no, y qué significan sus categorías de producto.

Y ahora la pregunta que decide todo: ¿con cuántas fincas trabaja tu distribuidor?

Si trabaja con veinte y tú de verdad te beneficias de esa rotación, el margen está bien pagado. Si trabaja con una sola —la de don Ernesto— entonces no tienes un distribuidor: tienes un peaje. Estás pagando margen, eslabón y opacidad a cambio de una flexibilidad que no existe.

Un intermediario que trabaja con un solo proveedor no es un intermediario: es un peaje. Guarda esa frase, porque es la lección 6 entera en nueve palabras.

Qué es exactamente una abstracción (y qué es la indirección)

Vamos a definir las dos palabras con cuidado, porque se usan como sinónimos y no lo son. Esa confusión es la fuente de la mitad de los malentendidos sobre este tema.

Una abstracción es un nombre que se pone sobre un conjunto de detalles, para poder hablar del conjunto sin mencionar los detalles. Tiene tres partes:

  1. Un nombre. PaymentProvider, distribuidor, sofrito.
  2. Una frontera. Qué queda adentro (los detalles que el nombre esconde) y qué queda afuera (lo que quien usa el nombre sí tiene que saber).
  3. Una promesa. Lo que puedes asumir sin mirar adentro. Cuando pides "sesenta kilos de arábica", la promesa es que va a llegar café bebible. Cuando llamas a provider.charge(order), la promesa es que el cobro se intenta y te devuelven un resultado.

Fíjate que el valor de una abstracción está en la tercera parte. Si puedes confiar en la promesa, dejas de pensar en lo de adentro y tu cabeza se libera para pensar en otra cosa. Ese es el beneficio real: no menos código, sino menos cosas que sostener en la cabeza al mismo tiempo.

La indirección es otra cosa: es cuando para llegar de A a B tienes que pasar por C. C no hace el trabajo; C reenvía, decide o traduce. Es la forma física, en el código, que suele tomar una abstracción.

Y aquí está la distinción que resuelve la mitad de los casos:

Una abstracción que oculta algo te ahorra pensamiento y te cobra un salto. Una indirección que solo redirige te cobra el salto y no te ahorra nada.

Cuando llamas a provider.charge(order) y no tienes que saber que Stripe quiere centavos como entero, que MercadoPago quiere un float y que el pago en efectivo ni siquiera cobra —eso es una abstracción que oculta. Te ahorró tres detalles incompatibles.

Cuando llamas a MoneyFormatterFactory.get("MXN").format(1250.0) y adentro lo único que ocurre es f"$ {amount:,.2f} MXN" —eso es indirección que solo redirige. No te ahorró nada; te movió de archivo.

La prueba práctica es de una línea: ¿qué dejaste de tener que saber gracias a esta capa? Si puedes nombrar algo concreto, la capa oculta. Si tu respuesta es "nada, pero es más flexible", la capa redirige, y la flexibilidad hay que cotizarla aparte.

Ejemplo trabajado: el formateador de moneda de Boletia

Vamos a ver las dos versiones del mismo requisito, medidas lado a lado.

El requisito, tal como llegó: "en los correos de confirmación y en el PDF del corte de caja, los montos tienen que verse como $ 1,250.00 MXN, con separador de miles y dos decimales".

Boletia vende en México, cobra en pesos, y todos sus organizadores están en México.

Así se implementó:

# Archivo: formatting/money.py
from abc import ABC, abstractmethod

class MoneyFormatter(ABC):
    """Contrato para dar formato a un monto según su moneda."""

    @abstractmethod
    def currency_code(self) -> str:
        """Código ISO de la moneda que este formateador maneja."""

    @abstractmethod
    def format(self, amount: float) -> str:
        """Devuelve el monto listo para mostrarle a una persona."""
# Archivo: formatting/mxn_formatter.py
from formatting.money import MoneyFormatter

class MXNFormatter(MoneyFormatter):
    def currency_code(self) -> str:
        return "MXN"

    def format(self, amount: float) -> str:
        # El separador de miles y los dos decimales los da el formato de Python.
        return f"$ {amount:,.2f} MXN"
# Archivo: formatting/factory.py
from formatting.mxn_formatter import MXNFormatter

_FORMATTERS = {
    "MXN": MXNFormatter(),
}

def get_formatter(currency_code: str) -> "MoneyFormatter":
    """Devuelve el formateador de la moneda pedida."""
    formatter = _FORMATTERS.get(currency_code)
    if formatter is None:
        raise ValueError(f"Moneda no soportada: {currency_code}")
    return formatter

Y así se usa desde el resto del sistema:

# Archivo: notifications/email_channel.py  (fragmento)
from formatting.factory import get_formatter

def build_confirmation_body(order):
    formatter = get_formatter("MXN")               # ← la moneda va escrita a mano aquí
    total = formatter.format(order.total)
    return f"Tu compra por {total} fue confirmada."

Ahora la versión directa, que hace exactamente lo mismo:

# Archivo: utils/money.py
def format_money(amount: float) -> str:
    """Formatea un monto en pesos: 1250.0 -> '$ 1,250.00 MXN'."""
    # Boletia cobra solo en pesos mexicanos. Si algún día hay otra moneda,
    # esta función es el único lugar que hay que tocar.
    return f"$ {amount:,.2f} MXN"
# Archivo: notifications/email_channel.py  (fragmento)
from utils.money import format_money

def build_confirmation_body(order):
    total = format_money(order.total)
    return f"Tu compra por {total} fue confirmada."

Pongamos las dos versiones en una tabla, con los cuatro costos medidos:

Con la abstracciónDirecto
Archivos31
Líneas415
Saltos para responder "¿cómo se ve un monto?"3 (email_channelfactorymxn_formatter)1
Conceptos nuevos del proyecto3 (MoneyFormatter, MXNFormatter, get_formatter)1 (format_money)
Implementaciones que existen hoy1
Qué dejas de tener que saber gracias a la capanada
Costo de agregar una segunda moneda mañanacrear una clase nueva y registrarlaagregar un parámetro o una función hermana

Qué esperar de esta comparación. Lo primero es lo que más incomoda: la última fila. El argumento entero a favor de la versión abstracta era "así, cuando agreguemos otra moneda, será fácil". Y resulta que en la versión directa también es fácil: son quince minutos de trabajo el día que ocurra. La flexibilidad que se compró por adelantado no era una flexibilidad que costara caro comprar después.

Esa es una prueba que conviene volver un reflejo: ¿cuánto costaría hacer este cambio el día que de verdad se necesite? Si la respuesta es "poco", la preparación anticipada no está comprando gran cosa, y ese "poco" hay que compararlo contra el costo recurrente de cargar la estructura mientras tanto. La lección 5 formaliza esta cuenta.

Lo segundo: mira la fila de "qué dejas de tener que saber". Está vacía. Quien usa get_formatter("MXN") tiene que saber exactamente lo mismo que quien usa format_money() —de hecho tiene que saber más, porque además tiene que saber que hay que pedir un formateador y con qué código—. La capa no ocultó nada. Solo redirigió. Y encima el código de la moneda quedó escrito a mano en el sitio de llamada, así que ni siquiera consiguió centralizar la decisión que decía centralizar.

Lo tercero, que se ve al leer el código de llamada de las dos versiones seguidas: format_money(order.total) se entiende sin abrir nada. get_formatter("MXN").format(order.total) obliga a preguntarte qué otras monedas hay —hay una— y qué pasa si pides una que no existe —una excepción que nunca se ha lanzado—. La versión abstracta plantea preguntas que no tienen respuesta interesante, y esas preguntas se las plantea a todo el que pasa por ahí, para siempre.

Y un cuarto punto, que quiero dejar claro por honestidad: si Boletia vendiera en México, Colombia, Chile y España, con cuatro formatos de moneda de verdad distintos —y los hay: en varios países el separador de miles es el punto y el decimal es la coma, y el símbolo va antes o después según la convención local—, entonces la versión abstracta sería la correcta y la función suelta se convertiría en un condicional creciente. La misma estructura, buena en un contexto y mala en otro. Todo este módulo vive en esa frase.

Los cuatro costos, uno por uno

Ya viste la cuenta en un caso. Vamos a desarmarla en sus componentes, porque cada uno se comporta distinto y se estima distinto.

Costo 1 — Un salto más al leer

Qué es. Un salto es cada vez que, leyendo código, tienes que dejar donde estás e ir a otro lugar para entender qué pasa. En la práctica: un Ctrl+clic, un grep, una pestaña nueva.

La anatomía del costo. El tiempo del salto en sí es poco —unos segundos—. El costo de verdad es otro y tiene nombre en psicología cognitiva: la memoria de trabajo. Cuando saltas, tienes que sostener en la cabeza dónde estabas, qué estabas buscando y qué habías entendido hasta ahí. La memoria de trabajo humana maneja cómodamente unas cuatro cosas a la vez. Al tercer o cuarto salto anidado, algo se cae, pierdes el hilo y tienes que empezar de nuevo.

Cómo se siente. Es la experiencia de abrir el cuarto archivo y darte cuenta de que ya no te acuerdas por qué lo abriste. Cualquiera que haya leído código enredado la reconoce inmediatamente.

Qué esperar al medirlo. Un salto suelto es barato y se paga sin quejarse. El problema es la profundidad: tres saltos anidados no cuestan el triple que uno, cuestan mucho más, porque cada nivel consume un espacio de esa memoria que ya estaba ocupada. La regla práctica que uso: si para entender una funcionalidad pequeña necesitas más de tres niveles de profundidad, el costo dejó de ser lineal.

Un matiz importante. No todos los saltos cuestan igual. Un salto que atraviesa una frontera real —de la capa HTTP a la lógica de negocio, de la lógica a la base de datos— es barato en términos cognitivos, porque al cruzarlo puedes soltar lo que traías: ya no necesitas acordarte de los encabezados HTTP mientras lees la lógica. Un salto que te deja en el mismo nivel conceptual, solo que en otro archivo, no te deja soltar nada: cargas todo lo de antes más lo nuevo. Esos son los caros.

Costo 2 — Un archivo más que abrir

Qué es. Cada archivo nuevo es una unidad más de navegación, de búsqueda, de historial y de conflicto.

La anatomía del costo. Se paga en cuatro monedas distintas:

  • La búsqueda deja de funcionar como esperas. Si buscas en el proyecto la palabra "MXN" para ver dónde se formatea el dinero, en la versión directa encuentras una función. En la versión abstracta encuentras el código de la moneda en tres lugares y ninguno es donde ocurre el formato.
  • El historial se dispersa. Para entender por qué el formateo quedó así, git log sobre un archivo te cuenta la historia completa. Repartida en tres, cada archivo te cuenta un tercio y ninguno te dice el porqué.
  • El diff de un cambio se reparte. Un cambio que en la versión directa es un diff de dos líneas en un archivo, en la abstracta es un diff en tres archivos. Quien lo revisa tiene que reconstruir mentalmente cómo encajan.
  • Los conflictos de merge se multiplican. Más archivos tocados por el mismo cambio significa más superficie de choque cuando dos personas trabajan cerca.

Qué esperar. Hay un umbral cualitativo que conviene conocer: cuando algo cabe entero en una pantalla, el cerebro lo trata de una manera; cuando hay que navegar, lo trata de otra. Pasar de "una función que veo completa" a "tres archivos que tengo que recorrer" no es un cambio de grado, es un cambio de tipo. Por eso partir un archivo de mil líneas en cinco de doscientas es casi siempre bueno, y partir una función de cinco líneas en tres archivos es casi siempre malo. El mismo movimiento, dos resultados opuestos, según de dónde partas.

Costo 3 — Un concepto más que aprender

Qué es. Cada abstracción introduce una palabra nueva que solo significa algo dentro de tu proyecto.

La anatomía del costo. SeatingPlugin no aparece en ningún libro, ninguna documentación externa, ningún curso. Es vocabulario privado. Quien entra al equipo tiene que aprenderlo, y no puede buscarlo en Google: solo puede preguntarle a alguien o deducirlo del código. Ese costo se paga una vez por persona, y la gente rota.

Cómo se siente. Es la reunión de onboarding donde alguien pregunta "¿y qué es un seating plugin?" y el veterano contesta "ah, eso… es el mecanismo para los mapas de asientos, pero solo hay uno, no te preocupes". Fíjate en la última parte de esa frase: es la confesión de que el concepto no vale lo que cuesta enseñarlo.

El agravante: el concepto zombi. Los conceptos sobreviven a sus razones. La persona que metió el sistema de plugins ya no está en el equipo. La razón por la que lo metió —"el organizador del Teatro Metropolitan dijo que quizá querría su propio sistema de butacas"— no está escrita en ningún lado y ese organizador se fue hace año y medio. Lo que queda es una estructura sin justificación, que todo el mundo respeta porque supone que hay un motivo. Ese respeto por lo que no se entiende es el mecanismo por el cual las abstracciones sobrantes se vuelven permanentes.

Qué esperar al medirlo. Un buen estimador es la pregunta: "¿cuánto tardaría en explicárselo a alguien que entra el lunes?". Si la respuesta pasa de dos minutos y el concepto atiende un solo caso, tienes un problema de proporción.

Costo 4 — Una capa donde puede esconderse un bug

Qué es. Este es el costo que más sorprende, porque va en contra de la intuición: solemos pensar que más estructura da más control. La estructura también es código, y el código que agregas para organizar también puede fallar.

La anatomía del costo. Hay tres formas en que una capa genera bugs que no existirían sin ella:

  • Bugs propios de la capa. El registro dinámico de Boletia importa módulos por nombre. Ese mecanismo tiene sus propios modos de falla —archivos que no debían importarse, orden de importación, el __path__ que cambia entre versiones de Python— y ninguno de ellos tiene nada que ver con asignar asientos.
  • Bugs de rendimiento por el momento en que ocurren las cosas. El descubrimiento de Boletia es perezoso: pasa en la primera petición que necesita un asiento, no al arrancar. Resultado: después de cada despliegue, el primer cliente que compra paga el costo de importar los módulos. Nadie diseñó eso; salió gratis con la abstracción.
  • Diagnóstico más lento cuando algo falla. Más capas significan más marcos en el rastro de la pila, más lugares donde poner un punto de interrupción, y errores que se reportan en la capa en vez de en el sitio real del problema.

El caso vivo de Boletia, para que no quede en teoría. Hace ocho meses, alguien del equipo estaba explorando la idea de un segundo algoritmo de asientos. Escribió un borrador a medias en plugins/impls/experimental_seating.py, lo dejó sin terminar, y por descuido quedó incluido en un despliegue. El archivo tenía un import a una librería que no estaba instalada en producción.

Lo que pasó fue esto: el discover() recorre todos los módulos de plugins/impls/ y los importa. Importó el borrador. El borrador reventó al importar. Y como el descubrimiento ocurre dentro de get_for(), que se llama desde el checkout, toda compra de un evento con asientos devolvió error durante cuarenta minutos.

Y el detalle que lo hace ejemplar: el rastro de la pila apuntaba a plugins/registry.py, línea 31 —la línea del import_module—, no al archivo culpable. Veinticinco de esos cuarenta minutos se fueron en entender que el problema era un archivo que nadie estaba usando.

Detente en la frase anterior: un archivo que nadie estaba usando tiró el checkout. Eso solo puede pasar si existe un mecanismo que importa archivos por el hecho de estar en una carpeta. Sin la abstracción, ese borrador habría sido un archivo muerto, inofensivo, esperando a que alguien lo borrara.

"Más flexible" es una compra, no un regalo

Ya tenemos los cuatro costos. Ahora la parte que convierte esto en una herramienta de decisión.

Cuando alguien propone una abstracción, el argumento casi siempre es "así es más flexible". Y suele ser cierto. El problema no es el argumento: es que está incompleto. Una frase completa se ve así:

"Así, el día que necesitemos X, el cambio cuesta Y en vez de Z. A cambio, todos pagamos W mientras tanto."

Vamos a poner nombre a las cuatro variables, porque tenerlas separadas cambia la conversación:

VariableQué esCuándo se paga
Costo de construirEl tiempo de escribir la abstracciónUna vez, al principio
Costo de cargarLos cuatro costos de arriba: saltos, archivos, conceptos, superficie de bugEn cada lectura, por cada persona, mientras el código exista
Costo de quitarLo que costaría desmontarla si resulta que sobrabaUna vez, y crece con el tiempo
BeneficioLo que ahorra el día que llegue el cambio para el que la pusisteSolo si el cambio ocurre

Tres observaciones sobre esta tabla, y son las que hacen todo el trabajo.

La primera: la única variable que la gente estima es la primera, y es la menos importante. "Son tres días" suena a una decisión pequeña. Pero el costo de cargar es recurrente y el de construir no. En un equipo de seis personas, con un código que va a vivir tres años, el costo de cargar supera al de construir por un margen que ni siquiera está cerca.

La segunda: el beneficio es condicional y el costo no. El costo de cargar lo pagas seguro, todos los días, desde el primero. El beneficio lo cobras solo si el cambio ocurre. Si estimas que hay un 20% de probabilidad de que alguna vez haga falta un segundo algoritmo de asientos, entonces estás pagando el 100% del costo por el 20% de un beneficio. Esa asimetría es la razón matemática por la que la sobre-ingeniería es tan cara: no es que el beneficio no exista, es que se descuenta por su probabilidad y el costo no.

La tercera: el costo de quitar crece. Este es el que casi nadie ve venir. El día uno, desmontar el sistema de plugins es media hora: todavía lo tienes fresco y nada depende de él. Dos años después es un proyecto: hay una columna en la base de datos, hay tests que prueban el registro, hay documentación que lo menciona, y —sobre todo— nadie recuerda por qué está y todos suponen que hay una razón. Una abstracción se vuelve permanente no cuando es difícil de quitar, sino cuando nadie sabe si se puede.

Junta las tres y sale la forma de la decisión:

Una abstracción se gana su lugar cuando probabilidad_del_cambio × ahorro_si_ocurre supera a costo_de_cargar × vida_del_código.

No es una fórmula para calcular; es una fórmula para discutir. Su valor está en que obliga a poner sobre la mesa las cuatro variables en vez de una. Cuando alguien dice "es más flexible", tú puedes preguntar: ¿qué tan probable es ese cambio? ¿cuánto ahorraría de verdad? ¿cuánta gente va a leer esto mientras tanto?. Esas tres preguntas son los tres ejes de la lección 7.

Errores comunes

Contar líneas en vez de contar saltos (de medición). Qué pasa: alguien evalúa dos diseños por su tamaño total y concluye que el que tiene menos líneas es más simple. Pero la versión con la abstracción a veces tiene menos líneas totales —justamente porque elimina duplicación— y aun así es más difícil de leer. Por qué pasa: las líneas son fáciles de contar y los saltos no; medimos lo que es cómodo medir. Cómo detectarlo: si tu argumento a favor de un diseño es "queda más corto", pregúntate cuántos archivos hay que abrir para seguir el flujo. Cómo corregirlo: usa la métrica que de verdad importa —cuántos archivos y cuántos saltos para responder una pregunta concreta que un usuario podría hacer—. Esa métrica correlaciona con el esfuerzo real de leer mucho mejor que el conteo de líneas.

Aceptar "es más flexible" como si fuera un argumento completo (de razonamiento). Qué pasa: en una revisión, alguien justifica una capa nueva diciendo que hace el código más flexible, y la conversación se acaba ahí, porque contradecir eso suena a estar en contra de la flexibilidad. Por qué pasa: "flexible" es una palabra con carga positiva y sin unidades. Nadie quiere ser el que se opone a la flexibilidad, igual que nadie quiere oponerse a la calidad. Cómo detectarlo: si en una discusión de diseño la palabra "flexible" aparece sin ir seguida de "para qué cambio concreto", la discusión está incompleta. Cómo corregirlo: convierte la afirmación en una frase completa con sus cuatro variables. Y hazlo sin agresividad, porque es una pregunta genuina: "¿qué cambio concreto nos ahorraría, y qué tan probable es?". Muchas veces la respuesta es buena y la abstracción se queda; otras veces quien la propuso descubre en voz alta que no tenía un caso.

Confundir el costo de este código con el costo del sistema entero (de escala). Qué pasa: alguien lee este módulo, se convence, y empieza a pelear por quitar una interfaz de una librería pública que su empresa distribuye a cientos de clientes. La quita, publica, y rompe el código de todos ellos. Por qué pasa: los cuatro costos de una abstracción son reales, pero el beneficio cambia radicalmente según quién lee y quién depende. Una interfaz dentro de un servicio que leen seis personas y una interfaz que es la superficie pública de un paquete son cosas distintas aunque el código se vea igual. Cómo detectarlo: pregunta quién puede depender de esto. Si la respuesta incluye a alguien fuera de tu repositorio, la cuenta cambia. Cómo corregirlo: aplica el criterio de este módulo dentro de tu frontera de despliegue con toda su fuerza, y con mucha más cautela en las fronteras públicas. La lección 6 tiene una sección entera sobre las excepciones legítimas, y la 7 pone "cuánta gente lee este código" como uno de los tres ejes de la decisión precisamente por esto.

Ejercicios

Ejercicio 1 — Aplica la prueba de una línea. Para cada una de estas tres capas, contesta la pregunta "¿qué dejo de tener que saber gracias a esta capa?" y clasifícala como oculta o redirige. (a) La interfaz PaymentProvider de Boletia, con Stripe, MercadoPago y efectivo detrás. (b) Una clase UserRepository cuyo único método es find_by_id(id) y que por dentro hace return db.query("SELECT * FROM users WHERE id = ?", id). (c) Una función send_email(to, subject, body) que por dentro configura el cliente SMTP, arma el mensaje MIME, maneja el reintento y registra el envío.

Ver solución

(a) Oculta, y mucho. Dejas de tener que saber que Stripe quiere el monto en centavos como entero, que MercadoPago lo quiere como float con la descripción aparte, y que el pago en efectivo no cobra nada sino que genera una referencia y devuelve estado pendiente. Son tres modelos mentales incompatibles y la capa te libera de los tres. Este es el caso de libro de una abstracción que se gana su lugar: hay tres implementaciones reales y las diferencias entre ellas son de verdad.

(b) Redirige. ¿Qué dejas de saber? Que la tabla se llama users y que la clave es id. Eso es… casi nada, y de hecho probablemente lo sabías igual. A cambio pagas un archivo, un concepto y un salto. Ahora bien —y aquí está el matiz honesto— esta capa puede ganarse su lugar por otra razón que no es ocultar: si te permite sustituirla por un doble en los tests sin tocar una base de datos real, entonces compró algo concreto y verificable. Fíjate en que ese es un argumento distinto y mejor: no es "es más flexible", es "me deja probar sin base de datos". La lección 6 vuelve sobre esta excepción.

(c) Oculta, claramente. Dejas de tener que saber cómo se configura SMTP, cómo se arma un mensaje MIME multiparte, cuántas veces reintentar y con qué espera, y dónde se registra el envío. Son cuatro cosas reales que ya no cargas. Y nota algo: esto no es una interfaz ni una jerarquía de clases. Es una función. Ocultar no requiere ceremonia; muchas veces la mejor abstracción disponible es una función con un buen nombre. Ese punto vuelve varias veces en la guía.

Por qué funciona: la prueba de una línea separa dos cosas que se confunden todo el tiempo. (a) y (c) ocultan complejidad real. (b) mueve código de lugar. Y (b) muestra además que una capa puede justificarse por razones que no son ocultar —la testabilidad—, siempre que esas razones se digan en voz alta y sean verificables.

Ejercicio 2 — Haz la cuenta de las cuatro variables. Un compañero propone, en Boletia, meter una capa de caché detrás de una interfaz Cache con un método get(key) y otro set(key, value, ttl), con una implementación en memoria hoy y "por si algún día pasamos a Redis". Estima las cuatro variables de la tabla —construir, cargar, quitar, beneficio— y da tu recomendación con una condición explícita.

Ver solución

Una respuesta bien formada se parece a esto:

  • Costo de construir: bajo. Una interfaz con dos métodos y una implementación en memoria son quizá dos horas.
  • Costo de cargar: también bajo, y esto es importante reconocerlo. La interfaz tiene dos métodos con nombres estándar que cualquiera reconoce —get y set son vocabulario universal, no vocabulario privado del proyecto—, y el salto es de un solo nivel. El costo del concepto es casi cero porque nadie tiene que aprender qué es un caché.
  • Costo de quitar: bajo. Dos métodos, un puñado de sitios de llamada. Es reversible en una tarde.
  • Beneficio si ocurre: alto y probable. Un servicio que crece a más de un proceso necesita un caché compartido casi con seguridad, y en ese momento cambiar de implementación tocando un archivo en vez de veinte es un ahorro real.

Recomendación: sí, pero sin ceremonia. Y con dos condiciones que la hacen defendible: la interfaz debe tener exactamente los métodos que se usan hoy —dos, no ocho "por completitud"— y la selección de implementación debe ser un diccionario literal o un condicional de dos ramas, no un registro con descubrimiento dinámico.

Fíjate en lo que hace distinto a este caso del sistema de plugins: aquí las cuatro variables apuntan en la misma dirección. Barato de construir, barato de cargar, barato de quitar, beneficio probable. En el sistema de plugins: caro de construir, caro de cargar, carísimo de quitar y beneficio improbable. El criterio no es "abstraer o no", es hacer la cuenta. Y a veces la cuenta da que sí.

Por qué funciona: este ejercicio existe para que no salgas del módulo con la idea de que la respuesta correcta siempre es "no abstraigas". Aquí es "sí, en su versión más barata". La lección 5 le pone nombre a esta categoría: preparación barata y reversible.

Ejercicio 3 — Encuentra el bug que solo existe por la capa. Vuelve al incidente del experimental_seating.py que tiró el checkout de Boletia durante cuarenta minutos. Escribe: (a) cuál fue la causa inmediata; (b) cuál fue la causa estructural; (c) qué habría pasado con ese mismo archivo si el código de asientos fuera una función en seating.py; y (d) una manera de mitigar el riesgo sin quitar la abstracción, y por qué esa mitigación es en sí misma un costo.

Ver solución

(a) Causa inmediata: un archivo a medio escribir, con un import a una librería ausente en producción, quedó incluido en un despliegue.

(b) Causa estructural: existe un mecanismo que importa todos los módulos de una carpeta por el solo hecho de estar ahí. Eso convierte "poner un archivo en una carpeta" en una acción con efecto en tiempo de ejecución. Estar presente y estar en uso dejaron de ser cosas distintas.

(c) Sin la abstracción: absolutamente nada. experimental_seating.py sería un archivo que nadie importa. Código muerto, feo, pero inofensivo. Alguien lo habría borrado en la siguiente limpieza. Cero minutos de caída.

(d) Mitigación sin quitar la abstracción: se puede hacer que discover() capture las excepciones de importación y registre una advertencia en vez de propagarlas. Con eso el checkout no se cae.

Ahora la parte que enseña, que es el "por qué esa mitigación es en sí misma un costo": esas cinco líneas de manejo de error son más código para sostener la capa, no para asignar asientos. Y traen su propio problema nuevo: ahora un plugin puede fallar en silencio, así que si algún día hubiera un segundo plugin real y su importación fallara, el sistema seguiría funcionando y usaría el equivocado sin avisar. Cambiaste una caída ruidosa por una falla silenciosa, que en producción suele ser peor.

Este es un fenómeno que se repite y que conviene reconocer: las capas generan trabajo para cuidar las capas. Cada mitigación es correcta en sí misma y suma al costo de cargar, y ninguna de ellas acerca el sistema a asignar mejor los asientos. Cuando notes que llevas tres cambios seguidos sobre el andamiaje y ninguno sobre la funcionalidad, tienes evidencia de que el andamiaje es el problema.

Por qué funciona: la mayoría de la gente acepta en abstracto que "una capa es una superficie donde puede esconderse un bug", y no logra imaginar un ejemplo. Este ejercicio te obliga a rastrear un incidente concreto hasta su causa estructural, que es exactamente el razonamiento que vas a necesitar en el proyecto y en el módulo 8.

Resumen y siguiente paso

En esta lección le pusiste precio a la abstracción. Viste la analogía del intermediario y la frase que la resume: un intermediario que trabaja con un solo proveedor no es un intermediario, es un peaje.

Separaste dos palabras que se confunden. Una abstracción es un nombre con una frontera y una promesa, y su valor está en la promesa: lo que puedes dejar de pensar. La indirección es la forma física que suele tomar: pasar por C para ir de A a B. Y de ahí salió la prueba de una línea: ¿qué dejo de tener que saber gracias a esta capa?. Si hay respuesta concreta, oculta. Si la respuesta es "nada, pero es más flexible", redirige.

Mediste el caso del formateador de moneda de Boletia —tres archivos y cuarenta y un líneas contra uno y cinco, para el mismo resultado— y viste que el argumento de la flexibilidad se caía al comprobar que el cambio futuro también era barato de hacer después.

Desglosaste los cuatro costos: el salto (que se paga en memoria de trabajo y crece más que lineal con la profundidad), el archivo (que se paga en búsqueda, historial, diff y conflictos), el concepto (que se paga una vez por persona y sobrevive a su razón) y la superficie de bug (que se paga en incidentes que no existirían sin la capa, como los cuarenta minutos de checkout caído por un archivo que nadie usaba).

Y armaste la cuenta de las cuatro variables: construir se paga una vez, cargar se paga siempre, quitar crece con el tiempo, y el beneficio es condicional a que el cambio ocurra.

Antes de avanzar deberías poder: aplicar la prueba de una línea a cualquier capa que te encuentres; nombrar los cuatro costos sin mirar; y explicar por qué el costo de construir es la variable menos importante de la cuenta.

Ahora bien, todo lo anterior te dice cuánto cuesta una abstracción. No te dice cuándo tienes información suficiente para saber que la vas a necesitar. Ese es un problema distinto, y tiene una respuesta sorprendentemente simple que la industria descubrió a base de tropezones: hay que esperar al tercer caso. La lección 3 explica por qué el número es tres y no dos, qué es exactamente un eje de variación, y cómo Boletia se equivocó de eje con sus proveedores de pago.

Recursos