Módulo 2: Entender y fijar el legacy

Cuando el legacy hace algo raro (y alguien depende de ello)

Descripción

Todo el módulo te ha estado insistiendo en algo que suena casi terco: cuando el legacy hace algo raro, no lo "arregles", fíjalo. La rareza gold del catálogo —la lealtad aplicada después del impuesto en vez de antes— parece un error de manual, y el characterization test la ha estado protegiendo a propósito, salga en verde con el bug y todo. Esta lección explica por qué esa terquedad es sabiduría, y lo hace con la idea que gobierna el comportamiento de todo sistema con usuarios: la ley de Hyrum. En su formulación original: con un número suficiente de consumidores de una API, no importa lo que prometas en el contrato: todos los comportamientos observables de tu sistema, aunque no los hayas prometido, serán algo de lo que alguien dependa. Dicho en cristiano: cuando bastante gente usa tu sistema, cada rareza —incluidos los bugs— se convierte, sin que nadie lo firme, en un contrato.

La consecuencia es incómoda y da vuelta a la intuición de todo programador. La distinción entre "bug" y "feature" no vive solo en el código; vive en quién depende del comportamiento. Un cálculo que aplica la lealtad después del impuesto es, mirando el código, probablemente un accidente histórico —alguien parchó algo hace años y quedó así—. Pero si un socio de negocio lleva años facturando contra ese número exacto, ese "accidente" es hoy un contrato de negocio, y "corregirlo" no es una limpieza técnica: es un cambio de contrato que rompe a un tercero. El bug se volvió una feature por el solo hecho de que alguien construyó encima. Esta lección te entrena a reconocer esos casos y a saber qué hacer con ellos —que no es ni "arreglar a ciegas" ni "no tocar nunca", sino algo más disciplinado—.

Conexión con el módulo. Las lecciones 3, 4 y 5 te dieron las herramientas para fijar el comportamiento del legacy; esta te dice qué hacer cuando lo que fijaste es una rareza —el caso más delicado y el que más daño causa cuando se maneja mal—. Es la culminación conceptual del módulo: el characterization test no solo atrapa regresiones accidentales, también convierte cada rareza en una decisión explícita en vez de un accidente. La lección 7 cerrará con la disciplina completa que une todo. La frontera con las guías hermanas importa aquí: la decisión formal de cambiar (o no) la rareza —con su costo, su reversibilidad, su ADR— pertenece a architecture-decisions-and-tradeoffs; aquí trabajamos cómo detectar la dependencia y por qué el characterization test la vuelve visible, no el registro formal de la decisión.

Una analogía: la pared torcida que sostiene el techo

Vuelve al baño en renovación. Al abrir la pared te encuentras con algo que no tiene sentido: un muro corrido, torcido, en un lugar donde no debería haber nada —parece que alguien lo levantó mal hace décadas—. Tu instinto de renovador es tumbarlo: estorba, se ve chapucero, el plano "limpio" no lo tiene. Un buen maestro de obra te detiene la mano con una pregunta: "¿y si esa pared está cargando el techo?". Esa es la regla de la cerca de Chesterton, el principio que dice: antes de quitar una cerca que parece no servir para nada, averigua por qué la pusieron —porque el hecho de que tú no le veas el propósito no significa que no lo tenga—. La pared torcida puede ser un error estético y, a la vez, la que evita que se te caiga el techo encima. Las dos cosas pueden ser verdad al mismo tiempo. Tumbarla "porque se ve mal" es descubrir su función de la peor manera posible: cuando ya no está.

La rareza gold es esa pared. Mirando el código, aplicar la lealtad después del impuesto se ve torcido, chapucero, "mal construido". Y probablemente lo es, en el sentido de que nadie lo diseñó a propósito. Pero antes de tumbarla —de "limpiarla" a gold-antes-del-impuesto— la regla de Chesterton exige preguntar: ¿quién se apoya en este número?. Y la respuesta, como vas a medir, es que el socio de lealtad de Mercado lleva su contabilidad apoyada exactamente en ese centavo. La pared torcida sostiene el techo. El characterization test es lo que te frena la mano a tiempo: al ponerse rojo cuando "arreglas" la rareza, te obliga a hacer la pregunta de Chesterton antes de tumbar el muro, no después. No te prohíbe tumbarlo —a veces el muro sí sobra—; te obliga a averiguar primero qué carga.

Ejemplo trabajado: el centavo que rompe la conciliación con el socio

Mercado tiene un socio de lealtad al que le paga un rebate: por cada compra de un cliente gold, Mercado le transfiere una parte del "ahorro" que el cliente obtuvo por ser gold. Ese ahorro se calcula a partir del precio del legacy —precio sin lealtad menos precio con lealtad—, y el socio ya emitió su factura del mes conciliada contra esos números. Ahora un desarrollador "arregla" la rareza (gold antes del impuesto). Vamos a medir qué le pasa al rebate y a ver la red atraparlo.

import math

TAX_RATE = 0.16
CATEGORY_DISCOUNT = {"electronics": 0.05, "books": 0.10, "toys": 0.08, "grocery": 0.00}
GOLD_LOYALTY_DISCOUNT = 0.03


def _floor_cents(a):
    return math.floor(a * 100) / 100


def _base_price(item):
    price = _floor_cents(item["unit_price"] * item["quantity"])
    price = _floor_cents(price * (1 - CATEGORY_DISCOUNT.get(item["category"], 0.0)))
    price = _floor_cents(price * (1 - item.get("coupon_percent", 0.0)))
    return _floor_cents(price * (1 + TAX_RATE))


def legacy_price(item):
    # HOY: la lealtad gold se aplica DESPUES del impuesto (parche viejo).
    price = _base_price(item)
    if item.get("loyalty") == "gold":
        price = _floor_cents(price * (1 - GOLD_LOYALTY_DISCOUNT))
    return price


def fixed_price(item):
    # "Arreglo": aplicar gold ANTES del impuesto, como los demas descuentos.
    price = _floor_cents(item["unit_price"] * item["quantity"])
    price = _floor_cents(price * (1 - CATEGORY_DISCOUNT.get(item["category"], 0.0)))
    price = _floor_cents(price * (1 - item.get("coupon_percent", 0.0)))
    if item.get("loyalty") == "gold":
        price = _floor_cents(price * (1 - GOLD_LOYALTY_DISCOUNT))
    return _floor_cents(price * (1 + TAX_RATE))


# --- El consumidor RIO ABAJO: el rebate que Mercado le paga al socio de lealtad ---
# El socio factura contra "cuanto ahorro el cliente gold". Ese ahorro se calcula
# a partir del precio del legacy, y se concilio el mes pasado con estos numeros.
GOLD_ORDERS = [
    {"sku": "G-1", "unit_price": 100.00, "quantity": 1, "category": "electronics", "loyalty": "gold"},
    {"sku": "G-2", "unit_price": 250.00, "quantity": 2, "category": "books",       "loyalty": "gold"},
    {"sku": "G-3", "unit_price": 40.00,  "quantity": 3, "category": "toys",        "loyalty": "gold"},
    {"sku": "G-4", "unit_price": 999.00, "quantity": 1, "category": "electronics", "loyalty": "gold"},
]


def gold_rebate(item, price_fn):
    # Ahorro del cliente gold = precio sin lealtad - precio con lealtad.
    without = dict(item); without["loyalty"] = None
    return round(price_fn(without) - price_fn(item), 2)


print("Rebate al socio de lealtad, ANTES y DESPUES del 'arreglo':")
print(f"{'sku':<6}{'legacy':>12}{'fixed':>12}{'delta':>10}")
legacy_total = fixed_total = 0.0
for it in GOLD_ORDERS:
    r_legacy = gold_rebate(it, legacy_price)
    r_fixed = gold_rebate(it, fixed_price)
    legacy_total += r_legacy
    fixed_total += r_fixed
    print(f"{it['sku']:<6}{r_legacy:>12}{r_fixed:>12}{round(r_fixed-r_legacy,2):>10}")
print("-" * 40)
print(f"{'TOTAL':<6}{round(legacy_total,2):>12}{round(fixed_total,2):>12}"
      f"{round(fixed_total-legacy_total,2):>10}")
print()

# El socio ya emitio su factura contra el total del legacy.
PARTNER_INVOICE = round(legacy_total, 2)
print(f"Factura del socio (conciliada el mes pasado): {PARTNER_INVOICE}")
print(f"Total que produce el 'arreglo':               {round(fixed_total,2)}")
reconciles = round(fixed_total, 2) == PARTNER_INVOICE
print(f"Concilia con el socio: {reconciles}  -> "
      f"{'ok' if reconciles else 'DISPUTA: los numeros ya no cuadran'}")
print()

# --- La red de seguridad atrapa el 'arreglo' antes de que llegue al socio ---
GOLDEN = {it["sku"]: legacy_price(it) for it in GOLD_ORDERS}
print("characterization_test (fija el precio gold ACTUAL, rareza incluida):")
fails = 0
for it in GOLD_ORDERS:
    expected, actual = GOLDEN[it["sku"]], fixed_price(it)
    ok = expected == actual
    fails += 0 if ok else 1
    print(f"  {'PASS' if ok else 'FAIL'}  {it['sku']}  "
          f"esperado={expected:<9} actual={actual}")
print(f"  -> {'VERDE' if fails==0 else 'ROJO'}: el 'arreglo' toca una rareza que "
      f"el rebate del socio usa. No es un bug libre de tocar.")

Qué esperar. Al correr el archivo, la salida es exactamente esta:

Rebate al socio de lealtad, ANTES y DESPUES del 'arreglo':
sku         legacy       fixed     delta
G-1           3.31         3.3     -0.01
G-2          15.66       15.66       0.0
G-3           3.85        3.85       0.0
G-4          33.03       33.03       0.0
----------------------------------------
TOTAL        55.85       55.84     -0.01

Factura del socio (conciliada el mes pasado): 55.85
Total que produce el 'arreglo':               55.84
Concilia con el socio: False  -> DISPUTA: los numeros ya no cuadran

characterization_test (fija el precio gold ACTUAL, rareza incluida):
  FAIL  G-1  esperado=106.88    actual=106.89
  PASS  G-2  esperado=506.34    actual=506.34
  PASS  G-3  esperado=124.21    actual=124.21
  PASS  G-4  esperado=1067.86   actual=1067.86
  -> ROJO: el 'arreglo' toca una rareza que el rebate del socio usa. No es un bug libre de tocar.

Mira primero la tabla de rebates, y fíjate en lo pequeño que es el daño —y en que eso es justo lo que lo hace peligroso—. De cuatro órdenes gold, tres dan exactamente el mismo rebate con el legacy y con el "arreglo" (G-2, G-3, G-4: delta 0.0). Solo una, G-1, cambia: el rebate baja de 3.31 a 3.30, un centavo. Si te quedas en la tabla, dirías "un centavo en una orden de cuatro, ¿a quién le importa?". Pero baja a la línea del total y a la conciliación: el total del mes pasa de 55.85 a 55.84, y el socio ya emitió su factura por 55.85 —conciliada contra el número del legacy—. La línea Concilia con el socio: False es el momento en que el centavo se vuelve un problema de negocio: los libros de Mercado y los del socio ya no cuadran, y eso dispara una disputa, una investigación, correos, y la peor pregunta de todas —"¿desde cuándo está mal esto, y en cuántas facturas anteriores?"—. Un centavo, multiplicado por miles de órdenes gold a lo largo de meses, deja de ser un centavo.

Aquí está la ley de Hyrum hecha carne. Nadie prometió nunca que la lealtad se aplicaría después del impuesto —no está en ningún contrato, probablemente ni siquiera fue una decisión—. Pero el socio construyó su facturación sobre el comportamiento observable del sistema, y por lo tanto ese comportamiento —bug y todo— se volvió un contrato de facto. El desarrollador que "arregla" la rareza cree estar limpiando código; en realidad está cambiando, unilateralmente y sin avisar, un contrato con un tercero. La pared torcida cargaba el techo.

Ahora mira la última sección: el characterization test. Fijó el precio gold actual —106.88 para G-1, la rareza incluida— y al correr el "arreglo" se pone rojo en G-1 (esperaba 106.88, obtuvo 106.89). Ese rojo es exactamente el maestro de obra deteniéndote la mano. No dice "está prohibido cambiar la rareza"; dice "oye, esto que vas a tumbar está cargando algo —¿ya averiguaste qué?—". Convierte lo que habría sido una regresión silenciosa (descubierta semanas después en la disputa con el socio) en una pregunta consciente, aquí y ahora, antes de mergear. El characterization test no solo atrapa accidentes; transforma cada rareza en una decisión explícita.

Profundización: qué hacer con la rareza (que no es ni tumbarla ni congelarla)

Detectar que la rareza es un contrato no significa "no tocar nunca". Significa manejarla con un procedimiento, y el procedimiento tiene pasos claros. Ni el instinto de "arreglar de una vez" ni el miedo de "no tocar jamás" son la respuesta; los dos evitan pensar.

  • Primero: caracterízala y hazla visible. El paso que ya hiciste. La rareza pasa de estar escondida en el código (donde el próximo dev la "limpiará" sin saber) a estar fijada en un characterization test con un nombre y, idealmente, un comentario que explique qué es y quién depende de ella. Un test llamado test_gold_loyalty_applies_after_tax_partner_rebate_depends_on_this vale más que mil advertencias verbales: cuando alguien la toque, el rojo le contará la historia.
  • Segundo: averigua quién depende (la pregunta de Chesterton). Antes de decidir nada, rastrea a los consumidores del comportamiento. ¿Quién lee este número? ¿El socio de lealtad? ¿Un reporte de finanzas? ¿Una integración externa? ¿Los clientes mismos, que verían su precio cambiar? Sin esta respuesta, cualquier decisión es a ciegas. El characterization test rojo te dice que algo depende; el trabajo de campo te dice quién.
  • Tercero: convierte el "arreglo" en una decisión de negocio, no técnica. Si resulta que la rareza es indeseable y hay que corregirla, esa corrección deja de ser un commit silencioso y se vuelve un cambio coordinado: avisar al socio, renegociar o migrar su conciliación, elegir una fecha de corte, quizá versionar el cálculo. Eso es una decisión con dueños de negocio, costo y reversibilidad —territorio de la guía architecture-decisions (un ADR)—, no algo que un ingeniero decide solo mientras refactoriza.
  • Cuarto y clave para la migración: durante la modernización, preserva la rareza bit a bit ("bug-for-bug"). Este es el punto que ata la lección al módulo entero. Cuando estás migrando —moviendo el catálogo a un servicio nuevo, como harás en M3 y M5—, el servicio nuevo debe reproducir el comportamiento viejo exactamente, rarezas incluidas, incluso las que sabes que son bugs. ¿Por qué cargar el bug al código nuevo? Porque migrar y arreglar al mismo tiempo es mezclar dos cambios y perder la capacidad de saber cuál rompió qué. Primero migras con paridad total (la red en verde garantiza que el nuevo hace lo mismo, bug incluido); después, ya migrado y estable, arreglas el bug como un cambio separado, deliberado y coordinado con el negocio. "Bug-for-bug compatibility" no es pereza: es separar la migración del cambio de comportamiento, para que cada uno sea verificable por su cuenta.

La regla que resume todo: el characterization test no te dice si la rareza está bien o mal; te dice que existe y que alguien podría depender de ella, y te obliga a decidir a conciencia en vez de por accidente. Distinguir bug de feature no es una pregunta de código, es una pregunta de consumidores —y esa pregunta se responde con investigación, no con opinión estética—.

flowchart TD
    W["el legacy hace algo raro<br/>(gold despues del impuesto)"] --> CT["characterization_test<br/>lo fija y lo hace visible"]
    CT --> Q{"quien depende?<br/>(pregunta de Chesterton)"}
    Q -- "nadie observable" --> FIX["corrige como cambio<br/>deliberado + red en verde"]
    Q -- "el socio de lealtad" --> BIZ["decision de NEGOCIO<br/>(coordinar, avisar, ADR)"]
    BIZ --> MIG["durante la migracion:<br/>preservar bit a bit (bug-for-bug)"]
    MIG --> LATER["arreglar despues,<br/>ya migrado y coordinado"]

Errores comunes

"Arreglar" un bug del legacy en el mismo commit en que lo descubres. Qué pasa: mientras caracterizas o refactorizas, ves la rareza gold, te parece obviamente incorrecta, y la corriges de paso "ya que estoy aquí". Por qué pasa: el impulso de dejar el código "bien" es fortísimo, y una rareza que entiendes se siente como una invitación a corregirla. Cómo detectarlo: si un cambio cuyo objetivo era fijar o refactorizar también altera un comportamiento observable existente, mezclaste dos cosas. Cómo corregirlo: separa siempre "preservar comportamiento" de "cambiar comportamiento" en commits, momentos y decisiones distintas. Un characterization test rojo por tu "arreglo" es la señal de que cruzaste la línea. Corregir la rareza puede ser correcto —pero como decisión propia, coordinada con quien depende, no como efecto colateral de un refactor. La pregunta de Chesterton va antes de tumbar el muro, no después.

Asumir que un bug no puede ser un contrato porque "nadie debería depender de eso". Qué pasa: el equipo razona "aplicar la lealtad después del impuesto es claramente un error, así que nadie en su sano juicio dependería de ese número exacto" y lo corrige. Por qué pasa: confundimos "el comportamiento es indeseable" con "nadie lo usa", como si la gente solo dependiera de lo correcto. Cómo detectarlo: si tu justificación para cambiar un comportamiento observable es "nadie debería depender de esto" en vez de "verifiqué que nadie depende de esto", estás adivinando. Cómo corregirlo: la ley de Hyrum es tajante —con suficientes consumidores, todo comportamiento observable termina siendo un contrato para alguien, sea deseable o no—. La gente no depende de lo que debería pasar; depende de lo que pasa. El socio de lealtad no eligió depender de un bug; simplemente construyó sobre el número que el sistema le daba. "Debería" no es evidencia; la investigación de consumidores sí.

Congelar la rareza para siempre por miedo, sin caracterizarla ni entenderla. Qué pasa: el equipo, escarmentado, decide "no tocamos nada del cálculo de precios, es demasiado peligroso" y lo deja intocable indefinidamente. Por qué pasa: el susto de romper al socio empuja al extremo opuesto —la parálisis—. Cómo detectarlo: si hay módulos que "nadie toca por si acaso" sin una red que explique qué se protege, el miedo está gobernando en vez del método. Cómo corregirlo: el objetivo del characterization test no es congelar el legacy para siempre, es hacerlo cambiable con seguridad. Caracterizas la rareza, entiendes quién depende, y entonces sí puedes modernizar —preservando el contrato durante la migración y corrigiéndolo después de forma coordinada—. "No tocar por miedo" y "arreglar a ciegas" son los dos errores opuestos que la disciplina evita: el punto medio es tocar con la red puesta y la pregunta de Chesterton respondida. Congelar por miedo es quedarse con el legacy sin modernizar nunca —justo lo que toda la guía busca superar—.

Ejercicios

Ejercicio 1 — Bug o feature. Para cada rareza del catálogo de Mercado, di qué información necesitarías para clasificarla como "bug libre de corregir" o "contrato de facto que hay que preservar", y por qué la respuesta no está en el código: (a) la lealtad gold se aplica después del impuesto; (b) el redondeo es hacia abajo en cada paso en vez de al centavo más cercano; (c) un cupón vencido hace exactamente un día todavía se acepta por un bug en la comparación de fechas.

Ver solución

En los tres casos, la información que falta es la misma y no vive en el código: quién observa y depende de ese comportamiento (la pregunta de Chesterton). El código te dice qué hace el sistema; solo la investigación de consumidores te dice si eso se volvió un contrato.

  • (a) Gold después del impuesto: necesitas saber si algún consumidor lee el precio gold o el "ahorro" gold —el socio de lealtad (que ya vimos que sí), reportes de finanzas, dashboards de marketing, los clientes mismos—. Como el rebate del socio se concilia contra ese número, es un contrato de facto: preservar durante la migración, corregir después coordinado. Si nadie dependiera, sería más libre de corregir.
  • (b) Redondeo hacia abajo por pasos: necesitas saber quién consume los precios finales con precisión de centavo —la contabilidad (¿los cierres mensuales se conciliaron contra estos totales?), pasarelas de pago, facturas emitidas—. El redondeo afecta todos los precios (recuerda: el 88% de las órdenes cambió al tocarlo en la lección 2), así que su radio de dependientes es enorme. Casi seguro es un contrato de facto masivo.
  • (c) Cupón vencido que se acepta un día de más: necesitas saber si clientes o campañas se acostumbraron a ese "día de gracia" —si el equipo de marketing lo comunica como parte de la promoción, o si clientes lo usan sistemáticamente—. Aquí es más plausible que sea un bug puro sin dependientes, pero no puedes asumirlo: hay que verificar. Un "día de gracia" involuntario puede haberse vuelto una expectativa del cliente.

La lección de fondo: en los tres, "esto es un bug" es una hipótesis sobre el código, no una licencia para cambiarlo. La licencia la da la evidencia de que nadie depende —y esa evidencia se busca, no se supone—.

Ejercicio 2 — El centavo que no era un centavo. En el ejemplo, "arreglar" la rareza cambió el rebate total de solo un centavo (55.85 → 55.84) y aun así rompió la conciliación con el socio. Explica por qué un cambio tan diminuto tiene una consecuencia tan desproporcionada, y por qué "en agregado casi no cambia" es un consuelo falso. ¿Qué tiene de especial un consumidor que concilia números, frente a uno que solo los muestra?

Ver solución

La consecuencia es desproporcionada porque la conciliación es una comparación de igualdad exacta, no de aproximación. El socio no pregunta "¿los números de Mercado están cerca de los míos?"; pregunta "¿son idénticos?". Y 55.84 no es idéntico a 55.85. Un solo centavo de diferencia hace que dos libros contables que deberían cuadrar al centavo dejen de cuadrar, y eso —no la magnitud del centavo— es lo que dispara el problema: una investigación, una disputa, la pregunta de "¿desde cuándo y en cuántas facturas?". En contabilidad, la diferencia entre cuadrar y no cuadrar es binaria; un centavo cae del lado de "no cuadra" con la misma contundencia que mil pesos.

"En agregado casi no cambia" es un consuelo falso por dos razones. Primera: aunque el total del mes cambie poco, el cambio se distribuye orden por orden —a G-1 le bajaste el rebate un centavo—, y el socio no factura "el total aproximado del mes", factura la suma de órdenes individuales que él calculó por su lado; cualquier desviación rompe la suma. Segunda, y más de fondo: en un volumen real de miles de órdenes gold a lo largo de meses, esos "casi nada" se acumulan y se multiplican —el ejemplo usó 4 órdenes; imagina 40,000—. "Casi no cambia" mira el promedio; la conciliación mira cada renglón.

Lo especial de un consumidor que concilia frente a uno que solo muestra: un consumidor que muestra un número (un dashboard que dice "ahorraste $3.31") tolera cambios pequeños —si mañana dice $3.30, casi nadie lo nota ni le importa—. Un consumidor que concilia usa el número como clave de una comparación exacta con otra fuente (la contabilidad del socio, un sistema de pagos, una auditoría), y para él cualquier diferencia, por diminuta que sea, es una falla dura: los sistemas no cuadran. Por eso, al buscar quién depende de una rareza (la pregunta de Chesterton), los consumidores que concilian son los más peligrosos: son los que convierten un centavo en una disputa.

Ejercicio 3 — El procedimiento correcto. Descubres que la rareza gold es, en efecto, un cálculo que el negocio preferiría "corregir" a gold-antes-del-impuesto, pero el socio de lealtad depende del comportamiento actual. Estás a punto de empezar a migrar el catálogo a un servicio nuevo (módulo 3 en adelante). Describe el orden correcto de acciones —qué haces durante la migración con la rareza, y cuándo y cómo corriges el cálculo— y justifica por qué migrar y corregir a la vez sería un error.

Ver solución

El orden correcto separa la migración del cambio de comportamiento en dos fases distintas:

  1. Durante la migración: preservar la rareza bit a bit (bug-for-bug). El servicio nuevo del catálogo debe reproducir el comportamiento actual exactamente, incluida la rareza gold —el precio gold sigue saliendo 106.88, no 106.89—. El characterization test / golden master en verde es la garantía de que el servicio nuevo hace lo mismo que el viejo. En esta fase no se corrige nada; el objetivo es una migración de paridad total, verificable, donde la única variable que cambia es dónde corre el código, no qué calcula.
  2. Después de migrar y estabilizar: corregir como cambio deliberado y coordinado. Ya con el catálogo servido por el sistema nuevo y estable, la corrección de la rareza se planea como un cambio propio: se coordina con el socio de lealtad (avisar, renegociar o migrar su conciliación), se elige una fecha de corte, se registra la decisión con su costo y reversibilidad (un ADR, territorio de architecture-decisions), y se despliega con la red confirmando que el único cambio es el intencional. El golden master se re-aprueba conscientemente en ese momento, leyendo el diff.

Por qué migrar y corregir a la vez sería un error: mezcla dos cambios y te quita la capacidad de saber cuál causó qué. Si el servicio nuevo cambia dónde corre el código y además corrige la rareza, y algo sale mal —una discrepancia, una queja del socio, un total que no cuadra—, no sabrás si la culpa es de la migración (un error al portar la lógica) o de la corrección deliberada (el cambio de comportamiento esperado). Con las fases separadas, cada una es verificable por su cuenta: la migración se valida con la red en verde (paridad total), y la corrección se valida contra la nueva especificación acordada con el negocio. Es la misma disciplina que atraviesa todo el módulo: un cambio a la vez, cada uno bajo su propia red. "Bug-for-bug compatibility" durante la migración no es cargar el bug por pereza; es proteger la verificabilidad de cada paso.

Resumen y siguiente paso

En esta lección enfrentaste el caso más delicado del módulo: el comportamiento raro del que alguien depende. Instalaste la ley de Hyrum —con suficientes consumidores, todo comportamiento observable, incluidos los bugs, se vuelve un contrato de facto— y viste, con la pared torcida que sostiene el techo (la cerca de Chesterton), que la distinción entre bug y feature no vive en el código sino en quién se apoya en él. Y lo mediste: "arreglar" la rareza gold cambió el rebate del socio de lealtad por un solo centavo (55.85 → 55.84) y con eso rompió la conciliación —una disputa de negocio—, mientras el characterization test se ponía rojo en G-1 y frenaba el "arreglo" antes de que llegara al socio. La red no solo atrapa accidentes: convierte cada rareza en una decisión explícita.

También aprendiste el procedimiento —caracterizar y hacer visible, averiguar quién depende (Chesterton), convertir el "arreglo" en una decisión de negocio, y durante la migración preservar la rareza bit a bit (bug-for-bug) para corregirla después por separado—. Antes de avanzar deberías poder: enunciar la ley de Hyrum y aplicarla a una rareza concreta; explicar por qué "nadie debería depender de esto" no es evidencia; distinguir un consumidor que concilia de uno que solo muestra; y ordenar la migración y la corrección en fases separadas.

La lección 7 cierra el módulo uniendo todas las piezas en una sola disciplina: leer → encontrar el seam → caracterizar → cambiar → dejar que la red decida. Vas a ver el loop completo en acción sobre el catálogo, con dos cambios bajo la misma red: un refactor legible que preserva el comportamiento (verde) y un cambio que lo altera (rojo), para que el test haga de árbitro objetivo de la única pregunta que importa al modernizar: "¿cambié algo, o no?".

Recursos

  • Hyrum Wright, "Hyrum's Law" — hyrumslaw.com. La formulación canónica en una frase, con su contexto: con suficientes consumidores, cada comportamiento observable se vuelve una dependencia. La idea que gobierna esta lección. En inglés.
  • Titus Winters, Tom Manshreck y Hyrum Wright, Software Engineering at Google (O'Reilly, 2020), cap. sobre compatibilidad — de dónde salió la ley de Hyrum y cómo Google la maneja al cambiar APIs con millones de consumidores; el marco de "bug-for-bug compatibility". En inglés.
  • G. K. Chesterton, The Thing (1929), el pasaje de "la cerca de Chesterton" — el principio de no quitar una cerca hasta saber por qué la pusieron; el clásico que da nombre a la regla de esta lección. En inglés (dominio público).
  • Martin Fowler, "ParallelChange" (expand-contract) — martinfowler.com/bliki/ParallelChange.html. Cómo cambiar un comportamiento del que otros dependen sin romperlos de golpe: expandir, migrar, contraer. La técnica para después de decidir corregir la rareza de forma coordinada. En inglés.