Módulo 2: Entender y fijar el legacy
Leer y caracterizar antes de cambiar
Descripción
Tienes ya todas las piezas del módulo sueltas sobre la mesa: sabes qué es un characterization test (fijar el comportamiento actual), por qué el código sin tests da miedo (Feathers), cómo escribir la red capturando en vez de adivinar, cómo abrir un seam cuando una dependencia lo impide, cómo escalar con golden master, y qué hacer con las rarezas de las que alguien depende. Esta lección las une en una sola cosa: una disciplina, un orden de pasos que aplicas cada vez que vas a tocar código legacy. Porque el error más común no es no conocer las técnicas; es aplicarlas en el orden equivocado —cambiar primero y verificar después, cuando el oficio del legacy exige exactamente lo contrario—.
La disciplina es un loop de cinco pasos: leer → encontrar el seam → caracterizar → cambiar → dejar que la red decida. Léelo y fíjate dónde cae el cambio: en el cuarto lugar, no en el primero. Primero lees para entender qué hace el código (sin juzgarlo todavía). Luego encuentras el seam si alguna dependencia impide fijarlo. Luego caracterizas —tomas la foto del comportamiento actual—. Recién entonces cambias. Y al final dejas que la red decida si tu cambio preservó el comportamiento (verde) o lo alteró (rojo). El corazón de la disciplina es que el test se vuelve un árbitro objetivo de la única pregunta que importa al modernizar: "¿cambié algo, o no?". No tu opinión, no "se ve igual", no "corrí el sistema y no tronó": el veredicto binario de la red.
Conexión con el módulo. Esta es la lección de síntesis: no introduce una técnica nueva, sino que ordena las cinco anteriores en el flujo que las hace útiles. Es también el puente hacia el resto de la guía: la red que aquí certifica "no cambié comportamiento" es exactamente la que en el módulo 3 te dará confianza para desviar tráfico al servicio nuevo (si el nuevo reproduce la foto, el desvío es seguro) y en el módulo 4 para migrar la implementación por dentro. La lección 8 aplica esta disciplina de punta a punta sobre el catálogo. La frontera se mantiene: aquí el characterization test es árbitro de una migración, no un tema de teoría de testing; y el cambio que hacemos es un refactor local, no todavía el desvío de tráfico (M3) ni branch by abstraction (M4).
Una analogía: el escalador que fija el seguro antes de cada movimiento
Mira a un escalador subir una pared. Un principiante sube rápido, confiado, moviéndose de presa en presa sin detenerse —hasta que resbala, y como no puso ningún seguro, cae toda la distancia hasta el suelo—. Un escalador con oficio sube distinto, y más lento: antes de cada movimiento arriesgado, fija un seguro —clava un anclaje en la roca y pasa la cuerda—, y solo entonces hace el movimiento. Si resbala, cae medio metro hasta el último seguro y sigue. La diferencia no es la técnica de escalar —los dos trepan igual de bien— ni el valor. Es el orden: el que tiene oficio pone la protección antes del movimiento peligroso, no después. Y esa protección tiene una segunda virtud, menos obvia: le permite intentar movimientos difíciles con calma, porque sabe que un error cuesta medio metro, no la vida. El seguro no solo lo salva de caer; lo libera para escalar mejor.
Modernizar legacy es escalar esa pared. Cada cambio que haces al código viejo es un movimiento; algunos son fáciles y otros arriesgados. El principiante cambia y reza —"se ve bien, ojalá no rompí nada"—; si resbaló, se entera en producción, cayendo toda la distancia. El que tiene oficio pone el seguro antes: caracteriza el comportamiento actual (fija el anclaje) y solo entonces cambia. Si su cambio rompió algo, la red se pone roja de inmediato —cayó medio metro—, lo corrige y sigue. Y como en la escalada, el seguro no solo lo protege: lo libera. Con la red puesta puede intentar refactors ambiciosos —reorganizar, extraer, limpiar— con calma, porque sabe que si rompe una rareza el test se lo dirá al instante. Sin la red, cada refactor es un movimiento sin seguro, así que el miedo lo paraliza y el código nunca mejora. La disciplina de esta lección es, literalmente, "pon el seguro antes de moverte" —y el orden de los cinco pasos no es burocracia, es la diferencia entre caer medio metro y caer hasta el suelo—.
Ejemplo trabajado: el mismo legacy, dos cambios, el árbitro decide
Vamos a poner el catálogo bajo una red de 300 órdenes y a hacerle dos cambios distintos, cada uno bajo la misma red. El primero es un refactor legible: extraemos cada paso del cálculo a una función con nombre, para que el código se lea mejor —sin tocar el comportamiento—. El segundo altera el comportamiento: reordena el cupón y la categoría. La red va a arbitrar cuál preservó el comportamiento y cuál no.
import math
import random
TAX_RATE = 0.16
CATEGORY_DISCOUNT = {"electronics": 0.05, "books": 0.10, "toys": 0.08, "grocery": 0.00}
BULK_MIN_QTY, BULK_DISCOUNT, GOLD_LOYALTY_DISCOUNT = 10, 0.05, 0.03
def _floor_cents(a):
return math.floor(a * 100) / 100
# --- El legacy original: todo apelmazado en una funcion ---
def legacy_catalog_price(item):
subtotal = _floor_cents(item["unit_price"] * item["quantity"])
price = _floor_cents(subtotal * (1 - CATEGORY_DISCOUNT.get(item["category"], 0.0)))
if item["quantity"] >= BULK_MIN_QTY:
price = _floor_cents(price * (1 - BULK_DISCOUNT))
price = _floor_cents(price * (1 - item.get("coupon_percent", 0.0)))
price = _floor_cents(price * (1 + TAX_RATE))
if item.get("loyalty") == "gold":
price = _floor_cents(price * (1 - GOLD_LOYALTY_DISCOUNT))
return price
# --- Refactor SEGURO: extraemos pasos con nombre. MISMO orden, MISMO redondeo ---
def _apply_category(price, item):
return _floor_cents(price * (1 - CATEGORY_DISCOUNT.get(item["category"], 0.0)))
def _apply_bulk(price, item):
if item["quantity"] >= BULK_MIN_QTY:
return _floor_cents(price * (1 - BULK_DISCOUNT))
return price
def _apply_coupon(price, item):
return _floor_cents(price * (1 - item.get("coupon_percent", 0.0)))
def _apply_tax(price):
return _floor_cents(price * (1 + TAX_RATE))
def _apply_gold(price, item):
if item.get("loyalty") == "gold":
return _floor_cents(price * (1 - GOLD_LOYALTY_DISCOUNT))
return price
def refactored_catalog_price(item):
price = _floor_cents(item["unit_price"] * item["quantity"])
price = _apply_category(price, item)
price = _apply_bulk(price, item)
price = _apply_coupon(price, item)
price = _apply_tax(price) # gold DESPUES del impuesto: preservamos la rareza
price = _apply_gold(price, item)
return price
# --- Cambio que ALTERA comportamiento: mueve el cupon antes de la categoria ---
def reordered_catalog_price(item):
price = _floor_cents(item["unit_price"] * item["quantity"])
price = _apply_coupon(price, item) # <-- orden cambiado
price = _apply_category(price, item)
price = _apply_bulk(price, item)
price = _apply_tax(price)
price = _apply_gold(price, item)
return price
def make_batch(n, seed=11):
rng = random.Random(seed)
cats = list(CATEGORY_DISCOUNT)
return [{
"sku": f"S-{i:03d}",
"unit_price": round(rng.uniform(5, 300), 2),
"quantity": rng.randint(1, 15),
"category": rng.choice(cats),
"coupon_percent": rng.choice([0.0, 0.0, 0.10, 0.20]),
"loyalty": rng.choice([None, None, "gold"]),
} for i in range(n)]
BATCH = make_batch(300)
GOLDEN = {it["sku"]: legacy_catalog_price(it) for it in BATCH}
def characterize(price_fn, label):
fails = [it["sku"] for it in BATCH if price_fn(it) != GOLDEN[it["sku"]]]
verdict = "VERDE" if not fails else "ROJO"
print(f"characterization_test ({len(BATCH)} ordenes) vs {label}:")
print(f" {len(BATCH)-len(fails)} passed, {len(fails)} failed ==> {verdict}")
if fails:
print(f" primeras fallas: {', '.join(fails[:5])} ...")
print()
print("Loop de cambio seguro: caracterizar -> cambiar -> el test decide.\n")
characterize(refactored_catalog_price,
"el REFACTOR (extraer pasos, sin tocar el comportamiento)")
characterize(reordered_catalog_price,
"el CAMBIO que reordena cupon y categoria (altera comportamiento)")
print("El refactor legible pasa en VERDE: el test certifica que no cambio nada.")
print("El reordenamiento pasa en ROJO: el test atrapo el cambio de comportamiento.")
Qué esperar. Al correr el archivo, la salida es exactamente esta:
Loop de cambio seguro: caracterizar -> cambiar -> el test decide.
characterization_test (300 ordenes) vs el REFACTOR (extraer pasos, sin tocar el comportamiento):
300 passed, 0 failed ==> VERDE
characterization_test (300 ordenes) vs el CAMBIO que reordena cupon y categoria (altera comportamiento):
253 passed, 47 failed ==> ROJO
primeras fallas: S-002, S-006, S-009, S-010, S-026 ...
El refactor legible pasa en VERDE: el test certifica que no cambio nada.
El reordenamiento pasa en ROJO: el test atrapo el cambio de comportamiento.
Los dos resultados, leídos juntos, muestran a la red haciendo de árbitro objetivo. El refactor legible —que extrajo cada paso del cálculo a una función con nombre (_apply_category, _apply_bulk, etc.), dejando el código mucho más leíble— pasa en verde: 300 de 300. Y esto es lo valioso: el refactor tocó muchísimas líneas —reorganizó toda la función—, así que "se ve muy distinto" al original. Si tu criterio fuera "¿se ve igual?", dudarías. Pero la red no mira cómo se ve; mira lo que hace, y certifica con un verde rotundo que, a pesar de haber reescrito la estructura entera, el comportamiento observable es idéntico para las 300 órdenes. El refactor mejoró la legibilidad sin cambiar un solo precio. Eso es un refactor en el sentido estricto: cambiar la forma sin cambiar el comportamiento, con prueba de que así fue.
El reordenamiento hace un cambio que parece igual de inocente —solo mueve el cupón para que se aplique antes que la categoría, dos líneas intercambiadas— pero pasa en rojo: 47 de 300 órdenes fallan. La razón es aritmética: aplicar el cupón antes o después del descuento de categoría cambia sobre qué base se calcula cada porcentaje, y con el redondeo por pasos eso mueve el precio final en las órdenes que tienen cupón y categoría con descuento. El cambio se veía trivial —"solo reordené dos operaciones"— y alteró el precio del 15% del lote. Sin la red, ese reordenamiento se habría desplegado en verde aparente (no truena nada) y habría empezado a cobrar precios distintos a 47 de cada 300 clientes con cupón. Con la red, es un rojo inmediato con la lista de las órdenes afectadas.
Fíjate en lo que la red no hizo: no te dijo cuál de los dos cambios es "mejor". El refactor es deseable (mejora legibilidad, preserva comportamiento) y el reordenamiento podría ser deseable si el negocio decidiera que el cupón debe aplicarse primero —eso es una decisión aparte—. Lo que la red hizo es lo único que un árbitro debe hacer: responder, sin opinión y sin ambigüedad, la pregunta "¿este cambio alteró el comportamiento observable, sí o no?". Verde: no. Rojo: sí, y aquí están las 47 pruebas. Con esa respuesta objetiva en la mano, tú (y el negocio) deciden qué hacer —pero deciden con información, no a ciegas—.
Profundización: el loop de cinco pasos, en detalle
El ejemplo condensó el loop; vale la pena desplegarlo, porque el orden es lo que lo hace seguro. Los cinco pasos, aplicados al catálogo:
- Leer (entender, no juzgar). Antes de tocar nada, lees la función de precio para entender qué hace: subtotal, categoría, volumen, cupón, impuesto, gold. En este paso resistes el impulso de opinar ("esto está mal") —solo mapeas el comportamiento—. Si no entiendes una rama, la anotas como "zona a caracterizar con cuidado", no como "bug a arreglar". Leer es reconocer el terreno antes de clavar el primer seguro.
- Encontrar el seam (si hace falta). Si al intentar caracterizar descubres una dependencia soldada —el reloj, una global, la BD— que hace el comportamiento no determinista, abres un seam (lección 4) con su enabling point, verificando que no cambió el default. En el ejemplo la función era determinista, así que este paso no hizo falta; en el catálogo real con el cupón que consulta el reloj, sí. El seam es el anclaje que te permite tomar una foto nítida en vez de movida.
- Caracterizar (tomar la foto). Fijas el comportamiento actual con un characterization test, capturando —no adivinando— la salida, y cubriendo los bordes, no solo el camino feliz. Si los casos son muchos, muestreas y grabas un golden master (lección 5). Al terminar este paso tienes la red: 300 precios fijados, verde contra el propio legacy. El seguro está clavado.
- Cambiar (ahora sí). Recién con la red puesta haces el cambio que querías —un refactor, una extracción, una limpieza—. Aquí puedes ser ambicioso, porque el seguro te protege: reorganizas toda la función si quieres. Este es el movimiento en la pared, y lo haces con calma porque un error cuesta medio metro.
- Dejar que la red decida. Corres el characterization test. Verde: tu cambio preservó el comportamiento, puedes mergear con confianza —hiciste un refactor de verdad—. Rojo: tu cambio alteró el comportamiento; ahora decides a conciencia si eso era intencional (y coordinas el cambio, quizá con el negocio, como en la lección 6) o un accidente (y lo corriges). El rojo no es un fracaso: es información. El árbitro habló.
El loop se repite en cada cambio. Y el orden es rígido a propósito: caracterizar (3) va antes de cambiar (4), siempre. Invertirlo —cambiar y luego intentar caracterizar— es el error del principiante que se mueve antes de poner el seguro: si el cambio ya alteró el comportamiento, tu "foto" captura el comportamiento ya roto, y la red pierde su sentido.
flowchart LR
R["1. Leer<br/>(entender, no juzgar)"] --> S["2. Encontrar el seam<br/>(si una dependencia estorba)"]
S --> C["3. Caracterizar<br/>(tomar la foto)"]
C --> CH["4. Cambiar<br/>(ahora si, con calma)"]
CH --> D{"5. La red decide"}
D -- "VERDE" --> OK["comportamiento preservado:<br/>merge con confianza"]
D -- "ROJO" --> DEC["comportamiento alterado:<br/>fue intencional? decidir a conciencia"]
DEC --> CH
Hay una consecuencia liberadora en todo esto, la misma de la escalada. Con el loop, refactorizar deja de dar miedo. El código legacy suele estar feo justamente porque nadie se atreve a limpiarlo —cada refactor es un movimiento sin seguro—. Una vez que tienes la red, puedes reorganizar, renombrar, extraer y simplificar con la tranquilidad de que el árbitro te dirá al instante si rompiste algo. El módulo entero no era solo para migrar; era para devolverte la capacidad de mejorar el código viejo sin apostar el negocio. Y eso es lo que hace posible que la modernización incremental avance: cada rebanada, una vez con red, se vuelve tocable.
Errores comunes
Cambiar primero y caracterizar después ("ya que estoy, lo mejoro y luego le pongo tests"). Qué pasa: el desarrollador abre la función, la refactoriza mientras la entiende, y luego intenta escribir tests. Por qué pasa: leer y cambiar se sienten como una sola actividad —entiendes el código mejorándolo—, y poner la red parece un trámite posterior. Cómo detectarlo: si tu characterization test se escribió sobre el código ya cambiado, no tienes una foto del "antes"; tienes una foto del "después", que no sirve para verificar que no rompiste nada. Cómo corregirlo: el orden es sagrado —caracterizar (3) antes de cambiar (4)—. La foto tiene que ser del comportamiento original, tomada antes de que tu primera edición lo altere. Es el seguro antes del movimiento: si te mueves primero y clavas el anclaje después, el anclaje registra tu posición después del resbalón, cuando ya caíste. Si de verdad ya cambiaste algo, la salida es volver al estado original (con control de versiones), caracterizar eso, y recién entonces re-aplicar tu cambio bajo la red.
Usar "se ve igual" o "no tronó" como veredicto, en vez de la red. Qué pasa: tras un refactor grande, el equipo lo revisa a ojo, concluye "hace lo mismo" y lo mergea sin correr una red que lo pruebe. Por qué pasa: cuando entiendes el cambio, tu confianza en que "es equivalente" se siente como evidencia. Cómo detectarlo: si la justificación de que un cambio preserva el comportamiento es humana ("lo revisé", "es obvio que es lo mismo", "corrí la app y anda") y no un test verde, no hay árbitro objetivo. Cómo corregirlo: el ejemplo lo mostró —el refactor tocó tantas líneas que "se veía distinto", y aun así la red certificó equivalencia con un verde de 300/300; el reordenamiento "se veía trivial" y la red lo atrapó en rojo con 47 fallos—. La intuición humana falla en ambas direcciones: teme cambios seguros y confía en cambios peligrosos. La red no. Deja que decida ella, siempre, incluso cuando estés seguro.
Tratar un rojo como un fracaso a esconder en vez de información a usar. Qué pasa: el characterization test se pone rojo tras un cambio, y el reflejo es "hacerlo pasar" —ajustar el test, o revertir el cambio sin entender qué reveló—. Por qué pasa: culturalmente, rojo = malo, y queremos que desaparezca. Cómo detectarlo: si ante un rojo tu primera acción es modificar el test para que pase (en vez de leer qué cambió), estás tapando el árbitro. Cómo corregirlo: un rojo es el dato más útil del loop —te dice, con precisión, qué comportamiento alteraste y en qué casos (las 47 órdenes, con sus SKUs)—. Ante un rojo, la pregunta no es "¿cómo lo pongo verde?" sino "¿este cambio de comportamiento era intencional?". Si no lo era, corriges el código (y agradeces que la red te salvó de producción). Si lo era —el negocio quiere el cupón primero—, entonces es un cambio deliberado que se coordina y del que se re-aprueba la foto conscientemente (lección 6). El rojo nunca se "hace pasar"; se entiende y se decide.
Ejercicios
Ejercicio 1 — Ordena el loop. Un compañero te describe cómo va a modernizar el módulo shipping de Mercado, en este orden: "(1) reescribo la función más limpia, (2) corro el sistema a ver si anda, (3) si algo se ve raro, escribo un test para ese caso, (4) despliego". Identifica qué pasos del loop de cinco están mal ordenados o faltan, reescribe su plan en el orden correcto, y explica qué riesgo concreto corre con su orden actual.
Ver solución
El plan del compañero tiene el cambio (reescribir) en el paso 1 y la caracterización (parcial y reactiva) recién en el paso 3, después de haber cambiado y de que "algo se vea raro". Eso invierte el orden crítico del loop y omite pasos.
Plan reescrito en el orden correcto:
- Leer el
shippinglegacy para entender qué hace, sin reescribir nada todavía. - Encontrar el seam si hay dependencias soldadas (por ejemplo, si consulta un servicio de tarifas o el reloj), para poder caracterizar de forma determinista.
- Caracterizar el comportamiento actual con una red que cubra los bordes —tomar la foto del
shippingoriginal, antes de tocarlo—, muestreando si hay muchos casos. - Cambiar: recién ahora reescribir la función más limpia, con la red puesta.
- Dejar que la red decida: correr el characterization test. Verde → el refactor preservó el comportamiento, desplegar con confianza. Rojo → decidir si el cambio fue intencional o accidental.
El riesgo concreto de su orden actual: al caracterizar solo después de cambiar y solo los casos que "se ven raros", su red (a) es una foto del comportamiento ya modificado, no del original —así que no puede detectar las regresiones que introdujo su reescritura, porque su "esperado" ya es el valor nuevo—, y (b) cubre solo el camino feliz que él revisó a ojo, dejando ciegos justo los bordes donde el legacy es peligroso. Es el escalador que clava el anclaje después de moverse: registra su posición ya caída. Una regresión silenciosa —un costo de envío que cambia por centavos en cierto rango de peso— pasaría su plan sin ser detectada y estallaría en producción.
Ejercicio 2 — Verde a pesar de verse distinto. En el ejemplo, el refactor legible tocó muchísimas líneas (extrajo cinco funciones nuevas) y aun así pasó en verde 300/300, mientras que el reordenamiento tocó solo dos líneas y pasó en rojo con 47 fallos. Explica por qué "cantidad de líneas cambiadas" y "cuánto se ve distinto el código" son malos predictores de si un cambio alteró el comportamiento, y qué es lo único que sí lo determina.
Ver solución
"Cantidad de líneas" y "cuánto se ve distinto" son malos predictores porque miden la forma del código, no su comportamiento, y las dos cosas son independientes. El refactor legible cambió mucha forma (cinco funciones nuevas, toda la estructura reorganizada) pero cero comportamiento: cada _apply_* hace exactamente el mismo cálculo, en el mismo orden, con el mismo redondeo que la función original; solo les puso nombre. Muchas líneas, cero cambio de comportamiento → verde. El reordenamiento cambió poquísima forma (dos líneas intercambiadas) pero sí el comportamiento: aplicar el cupón antes que la categoría cambia la base sobre la que se calcula cada porcentaje, y eso mueve el precio final. Pocas líneas, cambio real de comportamiento → rojo.
Lo único que determina si un cambio alteró el comportamiento es si la salida observable cambió para alguna entrada —exactamente lo que el characterization test mide, corriendo la función sobre las 300 órdenes y comparando cada resultado contra la foto—. No importa cuánto código tocaste ni qué tan distinto se ve: importa si algún precio salió diferente. Por eso la intuición humana ("se ve muy cambiado, ha de romper algo" / "es solo un reorden, ha de estar bien") falla en ambas direcciones, y por eso necesitas un árbitro que mida comportamiento, no forma. La red es ese árbitro: le da igual la estética del diff; solo compara salidas.
Ejercicio 3 — El rojo intencional. Corres el loop sobre el catálogo, haces un cambio, y la red se pone roja con 47 fallos. Al investigar, descubres que el cambio era exactamente lo que el negocio pidió: ahora el cupón debe aplicarse antes que la categoría, a propósito. Describe qué haces a continuación —paso a paso— para cerrar el cambio correctamente, y explica por qué "modificar el test para que pase" no es lo mismo que "re-aprobar la foto conscientemente".
Ver solución
Pasos para cerrar un cambio de comportamiento intencional:
- Confirmar que el rojo corresponde exactamente al cambio deseado. Lees el diff de las 47 órdenes fallidas y verificas que las diferencias son las que el nuevo cálculo (cupón antes de categoría) debe producir —solo órdenes con cupón y categoría con descuento, cambiando en la dirección esperada—. Si aparecieran fallos en órdenes que no deberían cambiar (por ejemplo, sin cupón), eso sería una regresión accidental además del cambio intencional, y habría que corregirla.
- Verificar que no hay dependientes que se rompan (la pregunta de Chesterton, lección 6). ¿Quién consume estos precios? ¿Hay conciliaciones, facturas, socios que dependan de los valores viejos? Si los hay, el cambio se coordina con ellos (aviso, fecha de corte, quizá versionado) antes de desplegar.
- Re-aprobar la foto conscientemente. Regeneras el golden master / actualizas los valores esperados del characterization test para que reflejen el nuevo comportamiento acordado, dejando registro (un commit claro, idealmente un ADR que explique la decisión y su costo —territorio de
architecture-decisions—). - Correr la red de nuevo: ahora en verde contra el nuevo comportamiento, que queda fijado para proteger futuros cambios accidentales.
Por qué "modificar el test para que pase" no es lo mismo que "re-aprobar conscientemente": mecánicamente pueden parecer iguales (ambos terminan con el test en verde y valores esperados nuevos), pero el proceso y la intención los separan por completo. "Modificar el test para que pase" es un acto reflejo para quitarse el rojo de encima, sin leer el diff, sin verificar dependientes, sin registrar por qué —justo el error de "tratar el rojo como algo a esconder"—. "Re-aprobar conscientemente" es una decisión: leíste exactamente qué cambia (el diff), confirmaste que es lo deseado, verificaste que nadie se rompe, y dejaste constancia. El primero desactiva el árbitro; el segundo actualiza deliberadamente lo que el árbitro protege. La diferencia es la misma que entre firmar el acta de estado sin mirar el departamento y recorrerlo anotando cada cambio a conciencia.
Resumen y siguiente paso
En esta lección uniste las piezas del módulo en una disciplina: leer → encontrar el seam → caracterizar → cambiar → dejar que la red decida. Viste, con el escalador que fija el seguro antes de cada movimiento, que el oficio del legacy no está en las técnicas sueltas sino en su orden —la protección va antes del movimiento peligroso, siempre— y que ese seguro no solo te salva de caer: te libera para refactorizar con calma. Y lo mediste: bajo una misma red de 300 órdenes, un refactor que reorganizó toda la función pasó en verde (300/300, comportamiento preservado a pesar de verse muy distinto), mientras que un reordenamiento de dos líneas pasó en rojo (47 fallos, comportamiento alterado a pesar de verse trivial) —la red arbitró la única pregunta que importa, "¿cambié algo?", sin dejarse engañar por cómo se ve el código—.
Antes de avanzar deberías poder: recitar y aplicar el loop de cinco pasos en orden; explicar por qué caracterizar va antes de cambiar y qué se rompe si lo inviertes; argumentar por qué "líneas cambiadas" y "se ve igual" no predicen el cambio de comportamiento; y manejar un rojo —distinguir el accidental (corregir) del intencional (re-aprobar conscientemente)—.
La lección 8 es el mini-proyecto: aplicas la disciplina completa de punta a punta sobre el catálogo de Mercado. Vas a construir la red de seguridad con muestreo y golden master, encontrar los seams (la tasa de impuesto y el reloj), caracterizar y documentar la rareza gold sin borrarla, y cambiar bajo la red —un refactor legible en verde, un "arreglo" riesgoso atrapado en rojo—. Al terminar, el catalog queda fijado y listo para lo que viene en el módulo 3: ponerlo detrás de un strangler facade y empezar a desviarle tráfico, con esta red garantizando que el servicio nuevo hace exactamente lo que hacía el viejo.
Recursos
- Martin Fowler, Refactoring: Improving the Design of Existing Code, 2ª ed. (Addison-Wesley, 2018), cap. 1-2 — la definición estricta de refactor (cambiar la forma sin cambiar el comportamiento) y por qué exige una red de tests que lo certifique. La base del "refactor en verde" de esta lección. En inglés.
- Michael Feathers, Working Effectively with Legacy Code (Prentice Hall, 2004), cap. 6 "I Don't Have Much Time and I Have to Change It" — el flujo práctico de poner la función bajo test antes de cambiarla, y por qué el orden importa. La disciplina de esta lección, en su fuente. En inglés.
- Martin Fowler, "Workflows of Refactoring" — martinfowler.com/articles/workflowsOfRefactoring. Los distintos ritmos de refactor (litter-pickup, comprehension, preparatory) y cómo la red de tests los habilita. Complemento directo al loop. En inglés.
- Kent Beck, "Test-Driven Development" y la idea de "make the change easy, then make the easy change" — sobre separar el refactor que prepara el terreno del cambio de comportamiento en sí, exactamente la distinción verde/rojo de esta lección. En inglés.