Módulo 1: Por qué no reescribir
Por qué la reescritura grande fracasa
Descripción
En la lección anterior viste el primer golpe: durante una reescritura grande, el negocio recibe cero valor mientras el equipo trabaja, y el backlog de features pedidas se acumula sin freno. Esta lección abre ese modo de falla y muestra que es todavía peor de lo que parecía, porque hay un detalle que la ventana de silencio esconde: la meta se mueve. Cuando reescribes un sistema vivo, tu objetivo no es fijo —"alcanzar las features que el legacy tiene hoy"— sino móvil, porque el legacy sigue creciendo mientras tú corres hacia él. El negocio no se congela por cortesía: sigue pidiendo cambios, sigue tapando bugs, sigue agregando reglas al sistema viejo, y cada uno de esos cambios aleja un poco más la línea de meta. Reescribir un sistema vivo es correr hacia un punto que se aleja cada vez que das un paso.
A esto se le llama el problema de la paridad de features: para poder apagar el legacy, la reescritura tiene que hacer todo lo que el legacy hace —no el 90%, no "lo importante", sino todo, porque cada función faltante es un cliente que se queja o un proceso que se rompe—. Y "todo lo que el legacy hace" no es un número fijo: crece cada semana. Esta lección lo mide. Vamos a modelar la brecha de features entre el legacy y la reescritura a lo largo del tiempo, en varios escenarios de velocidad del negocio, y vas a ver que la paridad —cuando llega— llega en años, y que en el escenario donde el negocio corre tan rápido como la reescritura, la paridad no llega nunca: el blanco se aleja exactamente al mismo ritmo al que te acercas.
Conexión con el módulo. La lección 1 midió la ventana de silencio (el rewrite entrega 0); esta lección explica por qué esa ventana no se cierra en la fecha planeada, o no se cierra nunca: la paridad de features es un blanco móvil. Es el primero de los cuatro modos de falla estructurales del big rewrite. La lección 3 ataca el segundo (el síndrome del segundo sistema, que infla el alcance) y la lección 4 el tercero (el conocimiento tácito que se pierde). Juntas construyen el caso de por qué el rewrite fracasa; las lecciones 5 y 6 construyen el caso a favor de la alternativa incremental. La decisión de si migrar o no —con su costo formal— es la guía de decisiones; aquí solo demostramos por qué el camino del rewrite es una trampa.
Una analogía: alcanzar un tren que ya está en movimiento
Estás parado en el andén y el tren empieza a moverse. Decides alcanzarlo corriendo. Si el tren estuviera detenido, sería fácil: corres, lo alcanzas, subes. Pero el tren no está detenido —acelera mientras corres—. Ahora la pregunta no es "¿puedo correr?", sino "¿corro más rápido de lo que el tren acelera?". Si sí, lo alcanzarás, aunque tal vez después de una carrera agotadora de varios minutos. Si el tren acelera igual de rápido que tú corres, mantendrás siempre la misma distancia: nunca lo alcanzas, por más que corras, porque cada metro que ganas el tren lo gana también. Y si el tren acelera más rápido que tú, cada segundo estás más lejos, no más cerca.
Reescribir un sistema vivo es correr tras ese tren. El legacy es el tren en movimiento: no te espera en el andén. Tu velocidad de reescritura es tu carrera. Y la velocidad a la que el negocio agrega features al legacy es la aceleración del tren. La pregunta que decide todo no es "¿puede el equipo reescribir?" —claro que puede—, sino "¿reescribe más rápido de lo que el negocio hace crecer al legacy?". Cuando el negocio es activo —como el de cualquier marketplace vivo, incluido Mercado—, la respuesta suele ser incómoda. Vamos a ponerle números.
Ejemplo trabajado: la paridad de features como blanco móvil
Vamos a modelar el legacy y la reescritura como dos contadores de features. El legacy arranca con las que ya tiene (Mercado tiene, digamos, 120) y suma unas cuantas por mes (el negocio no se detiene). La reescritura arranca en 0 y suma features por mes a su propio ritmo. La brecha es la diferencia; la paridad se alcanza cuando la brecha llega a 0 —y solo se alcanza si la reescritura cierra la brecha más rápido de lo que el legacy la abre—:
import math
LEGACY_START = 120 # features que el monolito de Mercado ya tiene el dia 0
HORIZON = 24 # meses que vamos a observar
def months_to_parity(legacy_vel, rewrite_vel):
# El rewrite alcanza al legacy solo si cierra la brecha mas rapido de lo
# que el legacy crece. Si no, el blanco se aleja para siempre.
if rewrite_vel <= legacy_vel:
return None
return math.ceil(LEGACY_START / (rewrite_vel - legacy_vel))
scenarios = [
# (nombre, features/mes del legacy, features/mes del rewrite)
("legacy congelado (mito)", 0, 8),
("negocio lento", 3, 8),
("negocio activo", 6, 8),
("presion competitiva", 8, 8),
]
print(f"{'escenario':<26}{'legacy/mes':>11}{'rewrite/mes':>12}{'paridad':>12}")
print("-" * 61)
for name, legacy_vel, rewrite_vel in scenarios:
m = months_to_parity(legacy_vel, rewrite_vel)
when = f"mes {m}" if m is not None else "NUNCA"
print(f"{name:<26}{legacy_vel:>11}{rewrite_vel:>12}{when:>12}")
# La brecha mes a mes en el escenario "negocio activo" (el realista en Mercado).
print("\nBrecha de features (escenario 'negocio activo', legacy 6/mes vs rewrite 8/mes):")
print(f"{'mes':>4}{'legacy':>9}{'rewrite':>9}{'brecha':>9}")
print("-" * 31)
legacy = LEGACY_START
rewrite = 0
for month in range(0, HORIZON + 1):
if month in (0, 6, 12, 18, 24, 60):
print(f"{month:>4}{legacy:>9}{rewrite:>9}{legacy - rewrite:>9}")
legacy += 6
rewrite += 8
parity = months_to_parity(6, 8)
print(f"\n A este ritmo la paridad llega hasta el mes {parity} "
f"({parity // 12} anios) - y solo si nadie sube la velocidad del legacy.")
print(" Mientras tanto: dos sistemas que mantener, cero valor nuevo entregado.")
Qué esperar. Al correr el archivo, la salida es exactamente esta:
escenario legacy/mes rewrite/mes paridad
-------------------------------------------------------------
legacy congelado (mito) 0 8 mes 15
negocio lento 3 8 mes 24
negocio activo 6 8 mes 60
presion competitiva 8 8 NUNCA
Brecha de features (escenario 'negocio activo', legacy 6/mes vs rewrite 8/mes):
mes legacy rewrite brecha
-------------------------------
0 120 0 120
6 156 48 108
12 192 96 96
18 228 144 84
24 264 192 72
A este ritmo la paridad llega hasta el mes 60 (5 anios) - y solo si nadie sube la velocidad del legacy.
Mientras tanto: dos sistemas que mantener, cero valor nuevo entregado.
Lee la primera tabla de arriba hacia abajo, porque cada fila es un escalón hacia la mala noticia.
El primer escenario, "legacy congelado", es el mundo de fantasía en el que casi todo mundo planea la reescritura: se asume que el legacy se queda quieto (0 features/mes) mientras la reescritura lo alcanza a 8/mes. En ese mundo la paridad llega en el mes 15: poco más de un año, duro pero finito. El problema es que ese mundo no existe: el legacy nunca se congela, porque congelarlo significa decirle al negocio "no vamos a arreglar ni un bug ni agregar ni una feature durante más de un año", y ningún negocio vivo acepta eso.
Baja una fila. "Negocio lento": el legacy crece apenas 3 features/mes. La paridad se corre al mes 24 —de 15 a 24, casi el doble—, solo porque el blanco se movió un poco. Baja otra. "Negocio activo", el escenario realista de Mercado: el legacy crece 6/mes. La paridad salta al mes 60 —cinco años—. Fíjate en el salto: la velocidad de la reescritura no cambió (sigue en 8/mes), pero como el legacy ahora se mueve a 6, la brecha se cierra a solo 2/mes efectivos, y cerrar 120 features a 2/mes toma cinco años. La velocidad que importa no es la tuya: es la diferencia entre las dos.
Y la última fila es la que hay que tatuarse. "Presión competitiva": el negocio, empujado por la competencia, agrega features al legacy a 8/mes —exactamente lo mismo que la reescritura—. Resultado: NUNCA. La brecha se queda clavada en 120 para siempre. Mira la segunda tabla para verlo pasar: en el escenario "negocio activo", la brecha empieza en 120 y baja lentísimo (108, 96, 84, 72...); en el de presión competitiva ni siquiera baja, porque cada feature que agrega la reescritura, el legacy la agrega también. Corres con toda tu alma y el tren mantiene la distancia. Nunca subes.
Ese es el modo de falla en su forma más pura. El big rewrite no fracasa porque el equipo sea lento o flojo —en todos estos escenarios la reescritura produce 8 features al mes, un ritmo excelente—. Fracasa porque estaba persiguiendo un blanco móvil, y la única forma de alcanzarlo era pedir que el negocio dejara de moverse, que es justo lo que el negocio no puede hacer.
Profundización: por qué la paridad tiene que ser total (y por qué eso duele)
Podrías objetar: "no necesito paridad total; apago el legacy cuando la reescritura tenga lo importante". Aquí está la trampa: en un sistema legacy, no sabes qué es "lo importante". Las 120 features del legacy incluyen muchísimas que parecen triviales o muertas, pero que algún cliente usa, algún proceso nocturno depende de ellas, o alguna integración externa las llama. La lección 4 lo mide en detalle —el conocimiento tácito—, pero el punto para esta lección es aritmético: mientras la reescritura no cubra el 100%, no puedes apagar el legacy, así que tienes que mantener los dos sistemas corriendo en paralelo. Y mantener dos sistemas cuesta más que mantener uno: cada bug hay que arreglarlo dos veces, cada feature urgente que no puede esperar a la reescritura hay que meterla en el legacy (moviendo el blanco todavía más) y cada cambio de datos hay que reflejarlo en ambos.
Por eso la última línea del output dice lo que dice: "dos sistemas que mantener, cero valor nuevo entregado". Durante los años que dura la persecución, el equipo no solo no entrega valor nuevo (está ocupado reescribiendo lo viejo): además paga el sobrecosto de operar dos sistemas a la vez. Es lo peor de ambos mundos.
Compara esto con el incremental, que la lección 5 desarrolla. En una migración incremental nunca existe una brecha de 120 features que cerrar, porque no reescribes el sistema entero: reescribes una rebanada, la pones en producción, y esa rebanada ya alcanzó paridad (es pequeña, su paridad es alcanzable en semanas), la apagas del legacy, y pasas a la siguiente. La paridad total del sistema se alcanza rebanada por rebanada, cada una un blanco pequeño y quieto, en vez de un blanco gigante y móvil. El incremental no gana la carrera contra el tren: se sube en la primera estación, viaja un tramo, se baja, y repite. Nunca corre tras el tren completo.
flowchart LR
subgraph Rewrite["Big rewrite: un blanco gigante y movil"]
A["reescritura<br/>(0 -> 120+)"] -. persigue .-> B["legacy<br/>(120 y creciendo)"]
end
subgraph Incremental["Incremental: blancos pequenios y quietos"]
S1["rebanada 1<br/>(paridad en semanas)"] --> S2["rebanada 2"] --> S3["rebanada 3"] --> S4["..."]
end
Errores comunes
Planear la reescritura asumiendo que el legacy se congela. Qué pasa: el plan del rewrite estima la fecha de paridad contando solo las features actuales del legacy, como si a partir del día 1 nadie tocara el sistema viejo. Por qué pasa: es la única forma de que el plan dé una fecha aceptable —si contaras el crecimiento del legacy, la fecha se iría a años—, así que se asume el congelamiento sin decirlo. Cómo detectarlo: pregunta "¿este plan supone que no vamos a agregar ni una feature ni arreglar ni un bug en el sistema viejo durante toda la reescritura?". Si la respuesta honesta es "sí", el plan vive en el escenario "legacy congelado" que la tabla mostró irreal. Cómo corregirlo: estima la fecha de paridad con la velocidad real del negocio (los escenarios "negocio activo" o "presión competitiva"), no con la de fantasía. Al hacerlo, la fecha suele saltar tanto que el rewrite deja de ser defendible por sí solo —y ese salto es exactamente el argumento a favor del incremental.
Confundir "el equipo es rápido" con "vamos a alcanzar la paridad". Qué pasa: el equipo mide su éxito por su propia velocidad ("¡producimos 8 features al mes, vamos genial!") y concluye que la paridad está cerca. Por qué pasa: la velocidad propia es visible y motivante; la velocidad del legacy es de otro equipo, o del negocio, y no se contabiliza en el mismo tablero. Cómo detectarlo: si el reporte de avance del rewrite muestra "features construidas" pero no "brecha contra el legacy", falta la mitad que importa. Cómo corregirlo: la métrica que decide no es tu velocidad, es la diferencia entre tu velocidad y la del legacy. Un equipo a 8/mes contra un legacy a 6/mes cierra a 2/mes efectivos —lentísimo—; el mismo equipo contra un legacy a 8/mes no cierra nunca. Mide la brecha, no tu esfuerzo. La lección 7 retoma esto: la reescritura solo es segura cuando el legacy sí puede congelarse (sistema chico, sin negocio activo encima), y Mercado no es ese caso.
Congelar features "temporalmente" para ganar la carrera. Qué pasa: al ver que no se alcanza la paridad, el equipo pide congelar el legacy —"solo unos meses, para que la reescritura alcance"—. Por qué pasa: es la salida aparente al blanco móvil: si detienes el tren, lo alcanzas. Cómo detectarlo: aparece la política de "no más cambios en el sistema viejo salvo emergencias". Cómo corregirlo: reconoce que el congelamiento es la ventana de silencio de la lección 1, con otro nombre —le estás pidiendo al negocio los dos años sin entrega que dijimos que no puede dar—. Y casi nunca aguanta: las "emergencias" que sí se cuelan al legacy vuelven a mover el blanco, y ahora encima hay que reescribir esas emergencias también. El congelamiento no resuelve el problema del blanco móvil; lo disfraza. La salida real no es detener el tren: es no perseguirlo, y migrar por rebanadas.
Ejercicios
Ejercicio 1 — Calcula la paridad. Usando la fórmula del ejemplo (meses = ceil(features_iniciales / (velocidad_rewrite - velocidad_legacy))), calcula el mes de paridad para un sistema con 90 features iniciales, una reescritura que produce 10 features/mes, y un legacy que crece a 7 features/mes. Luego di qué pasa con esa misma reescritura si la velocidad del legacy sube a 10/mes, y explica por qué.
Ver solución
Con legacy a 7/mes: la reescritura cierra la brecha a 10 - 7 = 3 features/mes efectivas. Paridad = ceil(90 / 3) = 30 meses, dos años y medio. Ya de por sí muy largo para un sistema que además hay que mantener en paralelo todo ese tiempo.
Si el legacy sube a 10/mes: la brecha se cierra a 10 - 10 = 0 features/mes. La fórmula no aplica (división por cero), y el resultado es NUNCA: la reescritura y el legacy avanzan al mismo ritmo, así que la brecha inicial de 90 se queda clavada en 90 para siempre. Por qué: la velocidad que cierra la brecha no es la de la reescritura en abstracto, sino su ventaja sobre el legacy. Cuando esa ventaja es cero, no importa cuán rápido corra el equipo —produce 10 features al mes, un ritmo excelente— porque el legacy produce otras 10 al mismo tiempo. Es el escenario "presión competitiva" de la tabla: correr con todo y no acercarse ni un metro.
Ejercicio 2 — Por qué la paridad tiene que ser total. Un gerente propone: "no esperemos a la paridad del 100%; apaguemos el legacy cuando la reescritura tenga el 95% de las features, y las que falten las agregamos después". Da dos razones, usando las ideas de esta lección y de la lección 1, de por qué ese 5% faltante puede costar mucho más de lo que su tamaño sugiere.
Ver solución
- No sabes qué features son ese 5%. En un sistema legacy sin documentación, no puedes elegir a conciencia cuáles son las 6 features "sacrificables" de 120. Ese 5% puede incluir la regla de facturación de la que depende un cliente que factura millones, un proceso nocturno del que nadie se acuerda hasta que no corre, o una integración con un socio externo. Apagar el legacy con ese 5% sin cubrir significa apostar a que ninguna de esas seis cosas importaba —y la lección 4 muestra que las partes "raras" del legacy suelen ser justo las que más importan—.
- Cada feature faltante rompe la promesa de apagar el legacy. Si al apagar el legacy descubres que faltaban funciones que sí se usaban, tienes que reencenderlo (o correr los dos sistemas otra vez), y vuelves al peor de los mundos de la profundización: dos sistemas en paralelo, doble mantenimiento. El 100% de paridad no es perfeccionismo: es el requisito literal para poder apagar el sistema viejo, que es el único punto en el que la reescritura empieza a pagar. Un 95% que te obliga a mantener los dos sistemas es, en términos de costo, casi como un 0%.
Nota el contraste con el incremental (lección 5): ahí sí puedes apagar por partes, porque cada rebanada alcanza su propio 100% (pequeño y verificable) antes de apagarse, en vez de apostar el 100% del sistema entero de una vez.
Ejercicio 3 — El blanco móvil en tus palabras. Explica, sin usar código, por qué la afirmación "si le damos suficiente tiempo, la reescritura eventualmente alcanzará al legacy" es falsa en general. ¿Bajo qué condición exacta sí se vuelve verdadera?
Ver solución
La afirmación es falsa porque supone que la brecha se cierra con el tiempo por el solo hecho de esperar, y eso solo ocurre si la reescritura avanza más rápido que el legacy. Si el legacy crece al mismo ritmo que la reescritura, esperar no cierra nada: la brecha es constante, y "suficiente tiempo" es infinito. Si el legacy crece más rápido que la reescritura, esperar la agranda: cada mes que pasa estás más lejos, no más cerca. El tiempo no es un aliado automático; solo ayuda si ya vas ganando la carrera de velocidad.
La condición exacta bajo la cual sí se vuelve verdadera es: la velocidad de la reescritura debe ser estrictamente mayor que la velocidad a la que el legacy crece (rewrite_vel > legacy_vel). Solo entonces la brecha se reduce cada mes y la paridad se alcanza en un tiempo finito (features_iniciales / (rewrite_vel - legacy_vel)). En la práctica, esa condición se cumple cuando el legacy puede congelarse —sistema pequeño, sin negocio activo encima, poca presión de features—, que es exactamente la región estrecha donde una reescritura es defendible (lección 7). Mercado, con un negocio activo que no se detiene, no está en esa región.
Resumen y siguiente paso
En esta lección abriste el primer modo de falla estructural del big rewrite: la paridad de features es un blanco móvil. Viste, con el tren que acelera mientras corres, que reescribir un sistema vivo no es alcanzar un objetivo fijo sino perseguir uno que se aleja, porque el legacy sigue creciendo. Y lo mediste: en el escenario realista de un negocio activo la paridad llega hasta el mes 60 —cinco años—, y en el de presión competitiva no llega nunca, porque la brecha se cierra a la velocidad de tu ventaja sobre el legacy, no a tu velocidad absoluta. Además, hasta que no alcances el 100%, tienes que mantener los dos sistemas en paralelo: lo peor de ambos mundos.
Antes de avanzar deberías poder: explicar por qué la velocidad que importa es la diferencia, no la propia; calcular el mes de paridad con la fórmula de la brecha; argumentar por qué la paridad tiene que ser total para poder apagar el legacy; y detectar un plan de rewrite que asume, sin decirlo, que el legacy se congela.
La lección 3 ataca el segundo modo de falla, y es uno que ni siquiera necesita que el negocio se mueva: el síndrome del segundo sistema. Aun con el legacy congelado y el blanco quieto, la reescritura tiende a fracasar por una razón interna al propio equipo —la tentación de aprovechar el "nuevo comienzo" para meterle todo lo que siempre se quiso—, y vas a medir cómo esa inflación de alcance corre la fecha de cierre del mes 20 al 50, o al infinito.
Recursos
- Joel Spolsky, "Things You Should Never Do, Part I" (2000) — joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i. El caso Netscape: mientras reescribían desde cero, la competencia (Internet Explorer) siguió avanzando y les comió el mercado. El blanco móvil de esta lección, contado con una empresa real que desapareció por él. En inglés.
- Sam Newman, Monolith to Microservices (O'Reilly, 2019), cap. 1, "Just Enough Microservices" — por qué la migración incremental evita la carrera de paridad: cada rebanada alcanza su propio objetivo pequeño en vez de perseguir el sistema entero. En inglés.
- Martin Fowler, "StranglerFigApplication" (2004) — martinfowler.com/bliki/StranglerFigApplication.html. El contrapunto: reemplazar por partes en vez de correr tras el sistema completo. La metáfora que la lección 6 desarrolla. En inglés.
- Chad Fowler, "Legacy Code" y la charla "Software as a Reflection of Values" — sobre por qué los sistemas vivos nunca dejan de cambiar y qué implica eso para modernizarlos. Buen complemento conceptual. En inglés.