Módulo 8: Refactorizar con criterio (capstone)
2. Leer el código antes de tocarlo
Descripción
Al terminar esta lección vas a tener la regla que evita la mayoría de los desastres de refactorización, y no es una regla técnica: es una regla de humildad con procedimiento. La regla dice que antes de mejorar un código tienes que entender qué hace y por qué está así, y la parte difícil es la segunda. Vas a conocer la ley de la cerca de Chesterton contada en cristiano, vas a tener un procedimiento de siete movimientos para leer un rincón desconocido —cada uno con qué buscar y qué esperar—, y vas a aplicarlo a una línea del checkout de Boletia que parece un error de principiante, que tres personas distintas intentaron borrar, y que tiene una razón excelente que no está escrita en ninguna parte del código.
Esto importa porque el error más caro de este módulo no es elegir mal el patrón. Elegir mal un patrón produce código incómodo, y el código incómodo se arregla. Borrar una decisión que tenía una razón produce un incidente, y a veces produce uno que nadie relaciona con tu cambio hasta semanas después, cuando el cierre de mes no cuadra. Y la asimetría es brutal: entender por qué está una línea rara cuesta entre veinte minutos y una tarde; equivocarse cuesta una caída, una investigación y la confianza del equipo en las refactorizaciones —lo cual, a la larga, es lo más caro de todo, porque el siguiente que quiera limpiar algo va a tener que discutir contra tu incidente—.
Y hay algo más, menos dramático y más frecuente. Leer bien antes de tocar no solo evita desastres: cambia el diagnóstico. Muchas veces lo que parece una abstracción innecesaria resulta ser la cicatriz de un problema real, y lo que parece un condicional feo resulta ser cuatro reglas de negocio que nadie escribió en ningún otro lugar. Quien lee primero no refactoriza más despacio: refactoriza otra cosa, y normalmente la correcta.
Conexión con el módulo: la lección 1 te dio la pregunta de la balanza —¿la estructura corresponde a la variación?— y te dijo que la evidencia decisiva casi nunca está en el código. Esta lección es la que te enseña a ir a buscarla. Las lecciones 3 y 4 son diagnósticos —qué patrón quiere emerger, cuál hay que quitar— y los dos dependen de que hayas leído bien: un diagnóstico sobre un código que no entendiste es adivinación con vocabulario. La lección 5 te da el procedimiento seguro para ejecutar, y su paso cero —la red de pruebas— es literalmente el último movimiento de esta lección. La 6 te enseña a justificar, y la mitad de una buena justificación es haber entendido lo que había antes. Y la 7 usa lo que descubras aquí para decidir si vale la pena tocar algo.
La cerca en medio del campo
Hay una imagen vieja que resume esta lección mejor que cualquier explicación. Se la debemos a G. K. Chesterton, un escritor inglés de principios del siglo XX, y va así.
Caminas por un campo y te encuentras una cerca. No delimita nada: está en medio de la nada, cruzando el camino, sin ganado a la vista, sin un vecino, sin una razón aparente. Llega alguien y dice: "esto no sirve para nada, vamos a quitarlo". Y la respuesta de Chesterton es: "si no sabes para qué la pusieron, no te dejo quitarla. Ve y averígualo; cuando vuelvas y me digas para qué servía, entonces podemos hablar de quitarla".
Lo importante es que no dice "no la quites". Dice "no la quites todavía". La regla no es conservadora, es de orden: primero se averigua, después se decide. Y es asimétrica a propósito, porque las consecuencias lo son. Si averiguas y resulta que la cerca no servía para nada, perdiste una tarde. Si la quitas sin averiguar y resulta que ahí abajo hay un pozo, perdiste una vaca.
En código, la cerca aparece todo el tiempo y tiene formas reconocibles:
- Una espera de dos segundos en medio de una función.
- Un
try/exceptque atrapa una excepción y no hace nada. - Un campo duplicado que parece redundante.
- Un orden de operaciones que podría ser el contrario y no lo es.
- Una comparación con
>=donde parecía obvio un>. - Un caso especial para un solo cliente, con su nombre escrito en el código.
Todas esas se ven mal, y algunas están mal de verdad. La lección no es que el código antiguo siempre tiene razón: la lección es que no lo sabes hasta que averiguas, y que averiguar es más barato de lo que crees porque tienes herramientas que Chesterton no tenía. Él tenía que buscar al dueño del campo. Tú tienes el historial completo del repositorio, con la fecha, el autor y el mensaje de cada línea.
Los tres tipos de cerca
Cuando averigües, vas a encontrarte con uno de estos tres casos, y conviene tener los nombres claros porque cada uno lleva a una decisión distinta.
Tipo 1 — la cerca que sigue teniendo razón. Hubo un problema, la línea lo resuelve, el problema sigue existiendo. Aquí no se toca; a lo sumo se documenta, que es exactamente lo que faltaba. Un comentario de tres líneas convierte una cerca en una señal.
Tipo 2 — la cerca que tuvo razón y ya no. Hubo un problema, la línea lo resolvió, y el problema desapareció —cambió el proveedor, se arregló el error del que se protegía, se migró la base de datos—. Aquí sí se quita, y se quita con confianza. Pero fíjate en la forma del argumento: no es "esto se ve raro", es "esto protegía de X, X ya no ocurre desde tal fecha, aquí está la evidencia".
Tipo 3 — la cerca que nunca tuvo razón. Alguien copió una línea de otro lado, o probó algo y se le olvidó quitarlo, o entendió mal un problema. Existe. No es raro. Pero es el caso menos frecuente de los tres en código que lleva años en producción, porque lo que nunca tuvo razón normalmente falla pronto y se quita pronto. La línea rara que sobrevivió tres años ganó una presunción a su favor, y esa presunción es lo que hay que vencer con evidencia.
La proporción importa para calibrar tu actitud. Si crees que casi todo es tipo 3, vas a romper cosas. Si crees que casi todo es tipo 1, no vas a limpiar nunca nada. La postura útil es: no sé de qué tipo es, y averiguarlo me cuesta veinte minutos.
Ejemplo trabajado: la línea absurda del checkout
Vamos al código de Boletia. Estás en checkout/checkout.py, leyendo el bloque de cobro para preparar la refactorización de los proveedores, y te encuentras esto:
# Archivo: checkout/checkout.py (fragmento del bloque de cobro)
elif order.provider == "mercadopago":
client = MercadoPagoClient(token=settings.MP_TOKEN)
result = client.pay(order.total, description=f"Boletia #{order.id}")
order.external_id = result.payment_id
order.status = "paid"
# No quitar.
if order.total > 10000:
time.sleep(2)
check = client.get_payment(result.payment_id)
if check.status != "approved":
order.status = "pending"
Léelo como lo leería alguien con prisa. Hay una espera fija de dos segundos, en el camino del checkout, es decir, en la ruta donde un cliente real está mirando una rueda girar. Hay un número mágico, 10000, sin explicación. Hay una segunda llamada a la API que consulta lo que la primera acaba de contestar. Y hay un comentario de tres palabras —"No quitar."— que parece más una súplica que una razón.
Cualquiera diría: esto es un parche. Y de hecho, tres personas distintas han intentado borrarlo. Vamos a averiguar por qué sigue ahí, con los movimientos que vas a usar el resto de tu carrera.
Movimiento 1 — ¿quién llama a esto y quién depende del resultado? Antes de entender la línea, entiende su vecindario. Aquí lo que se está tocando es order.status, así que la pregunta es quién lee ese campo.
grep -rn '\.status' --include='*.py' . | grep -i order
Aparecen cuatro lectores, y uno es interesante: reports/reconciliation.py, la conciliación nocturna que compara lo que Boletia cree que cobró contra lo que dice cada proveedor. Toma nota. El código que parece no tener sentido en su propio archivo muchas veces lo tiene en el archivo que lo consume.
Movimiento 2 — pregúntale al historial por la línea, no por el archivo. Esta es la herramienta más subestimada del oficio. git log -S busca commits donde una cadena apareció o desapareció:
git log -S "time.sleep(2)" --oneline -- boletia/checkout/checkout.py
Salida:
a91c3f2 Arregla conciliación de MercadoPago para montos altos (#2214)
7d4e881 Revierte "Quita espera innecesaria en checkout"
5b0a129 Quita espera innecesaria en checkout
Detente aquí, porque este resultado ya vale la tarde. Alguien la quitó (5b0a129), y alguien la volvió a poner (7d4e881) inmediatamente después. Un revert en el historial es la señal más fuerte que existe de que una cerca es del tipo 1. Significa que ya se hizo el experimento, y salió mal.
Movimiento 3 — lee el mensaje del commit que la trajo.
git show a91c3f2 --stat
Arregla conciliación de MercadoPago para montos altos (#2214)
En órdenes de más de $10,000 MXN, MercadoPago responde "approved" en
la llamada de cobro pero su API de consulta tarda entre 1 y 3 segundos
en reflejarlo, porque esas operaciones pasan por su revisión antifraude.
La conciliación de las 3:00 a.m. consulta esa misma API y marcaba las
órdenes como faltantes: 47 órdenes en enero, 3 de ellas canceladas a
mano por el equipo de operaciones. Dos clientes se quedaron sin boletos
por una compra que sí se había pagado.
Confirmamos con soporte de MercadoPago (ticket MP-88431) que es
comportamiento esperado y no van a cambiarlo.
Ahí está la razón completa: el proveedor tiene una inconsistencia conocida entre su respuesta inmediata y su API de consulta, para montos altos, por su revisión antifraude. Y hay un costo real ya pagado: cuarenta y siete órdenes descuadradas, tres canceladas a mano, dos clientes sin sus boletos.
Movimiento 4 — busca el ticket y el intento fallido. El mensaje menciona #2214 y MP-88431. Y el commit 5b0a129 —el que la quitó— también tiene su historia:
Quita espera innecesaria en checkout
Un sleep fijo en el camino del checkout es inaceptable para el p95.
Y su revert, tres días después:
Revierte "Quita espera innecesaria en checkout"
Volvieron las órdenes descuadradas (11 en dos días). Ver #2214.
El autor del intento tenía razón en su premisa —una espera fija en el camino del checkout es mala— y aun así se equivocó, porque su premisa era cierta y estaba incompleta. Casi todos los errores de refactorización se cometen desde una premisa cierta.
Movimiento 5 — pregúntale al código de al lado. Ahora vuelve a reports/reconciliation.py con la información nueva y lee lo que hace con status. Ahí encuentras el otro extremo de la cuerda:
# Archivo: reports/reconciliation.py (fragmento)
def reconcile_day(day):
"""Compara lo que creemos que cobramos contra lo que dice el proveedor.
Las órdenes en 'pending' se revisan al día siguiente; las 'paid' que el
proveedor no reconoce se escalan a operaciones.
"""
Y ahí se cierra el círculo: dejar la orden en "pending" no es un parche cosmético. Es lo que hace que la conciliación la vuelva a mirar mañana en vez de escalarla como un descuadre. Las dos piezas son un mecanismo, no dos líneas sueltas, y solo se ve si lees las dos.
Movimiento 6 — busca la prueba que la protege. Última verificación:
grep -rn "sleep\|10000\|MP-88431" tests/
No hay nada. Ninguna prueba cubre este comportamiento. Ese es un hallazgo tan importante como los otros: significa que si alguien vuelve a borrar la línea, nada se lo va a impedir. La cerca sobrevive solo por el comentario de tres palabras y por la memoria de dos personas del equipo, una de las cuales ya no está.
Qué esperar de esta investigación. Cuatro conclusiones, y ninguna es "no toques nada".
Primera: la línea se queda, pero el rincón no queda igual. Lo que descubriste no es "había que dejarlo": es que faltaban tres cosas alrededor. Un comentario que explique el porqué. Una prueba que fije el comportamiento. Y un nombre para el número mágico. Ese es el resultado real de una investigación de cerca del tipo 1: no la quitas, la vuelves legible.
# Archivo: checkout/checkout.py (después de investigar)
# MercadoPago revisa por antifraude las operaciones por encima de este monto,
# y su API de consulta tarda 1-3 s en reflejar el cobro aunque ya lo aprobó.
# La conciliación de las 3:00 a.m. usa esa misma API: sin esta verificación
# marcaba como faltantes órdenes ya pagadas (47 en enero de 2025, #2214).
# Confirmado por el proveedor como comportamiento esperado (MP-88431).
# Se quita cuando MercadoPago haga consistente su API de consulta.
MERCADOPAGO_ANTIFRAUD_REVIEW_THRESHOLD = 10_000.0
MERCADOPAGO_REVIEW_SETTLE_SECONDS = 2
Fíjate en la última línea del comentario, porque es la más valiosa de las cinco: dice bajo qué condición la cerca deja de hacer falta. Un comentario que solo explica el pasado obliga a la próxima persona a repetir tu investigación completa. Uno que además nombra la condición de salida le permite comprobarla en diez minutos.
Segunda: la investigación completa tomó unos veinte minutos. Seis comandos y dos archivos leídos. Compáralo con el costo del intento fallido: un cambio, un despliegue, dos días, once órdenes descuadradas, una investigación de urgencia, un revert y la sensación en el equipo de que el checkout es intocable. El presupuesto de "leer antes de tocar" es siempre ridículamente menor que el de equivocarse.
Tercera: el diagnóstico cambió, no solo la decisión. Antes de investigar, tu plan era "refactorizar el bloque de proveedores". Después de investigar, sabes que ese bloque contiene una regla de negocio real —la de MercadoPago— que tiene que sobrevivir al refactor y tiene que quedar en algún lugar sensato. Cuando en la lección 5 muevas cada proveedor a su clase, esta lógica se va con MercadoPagoProvider y no se pierde por el camino. Sin la investigación, había una probabilidad muy alta de que se perdiera: es exactamente el tipo de detalle que desaparece cuando alguien "reescribe limpio".
Cuarta: encontraste trabajo que nadie había pedido y que sí vale la pena. La prueba que falta. Escribirla cuesta quince minutos y convierte una cerca frágil —protegida por un comentario— en una cerca sólida, protegida por algo que falla en rojo cuando alguien la toca.
El procedimiento de lectura, en siete movimientos
Lo que hiciste en el ejemplo tiene forma, y la forma se puede repetir en cualquier rincón desconocido. Estos son los siete movimientos, en el orden en que conviene hacerlos. No siempre hacen falta los siete: en un rincón simple, con dos o tres tienes suficiente. La regla es que paras cuando puedes explicar el rincón con tus palabras, no cuando llegaste al final de la lista.
Movimiento 1 — Lee desde afuera: quién lo llama y quién lo consume. Un rincón de código no se entiende leyéndolo de arriba abajo: se entiende sabiendo qué se le pide y qué se espera de él. Empieza por los que llaman, no por la implementación.
grep -rn "nombre_del_modulo\." --include='*.py' .
Búsqueda por módulo, no por función. Es la lección del usuario número cuatro del módulo 2: admin/panel.py importaba _REGISTRY directamente, así que una búsqueda por get_for no lo habría encontrado nunca.
Movimiento 2 — Recorre el flujo con el dedo, una vez, entero. Elige un caso concreto —"una compra de dos boletos VIP con tarjeta"— y sigue el camino sin desviarte a leer nada más. El objetivo no es entender todo: es tener el esqueleto. Anota los saltos. Si necesitas más de cuatro o cinco archivos para seguir una operación, ya tienes un dato de diagnóstico gratis.
Movimiento 3 — Pregúntale al historial por el archivo. Qué se ha tocado ahí, con qué frecuencia y por qué:
git log --oneline --since="2 years ago" -- ruta/al/archivo.py
git log --oneline --diff-filter=A -- 'ruta/carpeta/*' # qué se agregó y cuándo
Esto te da dos cosas: la frecuencia de cambio, que la lección 7 usa para decidir si vale la pena tocar, y el tipo de cambios —¿funcionalidad o mantenimiento?—. Tres commits en dos años, todos de mantenimiento, cuentan una historia muy distinta a treinta commits de funcionalidad.
Movimiento 4 — Pregúntale al historial por la línea rara. Cuando algo específico no tiene sentido:
git log -S "el texto exacto de la línea" --oneline -- ruta/al/archivo.py
git blame -L 120,140 ruta/al/archivo.py
git log -S es el que encuentra el commit que introdujo la línea; git blame te dice quién tocó cada línea por última vez —que no es lo mismo, y por eso los dos hacen falta: un cambio de formato reciente puede haber "borrado" la autoría original ante blame, mientras que -S la encuentra igual—.
Movimiento 5 — Sigue el rastro fuera del repositorio. Los mensajes de commit buenos apuntan a tickets, incidentes o conversaciones con proveedores. Ábrelos. Ahí es donde suele estar la mitad del porqué, y en particular el costo pagado, que es lo que vas a necesitar cuando propongas cambiar algo.
Movimiento 6 — Lee las pruebas como documentación. Las pruebas son la única documentación que no puede mentir por mucho tiempo, porque si miente, falla. Antes de tocar nada, mira qué prueba existe:
- Si hay pruebas del comportamiento, tienes red y sabes qué se considera correcto.
- Si hay pruebas del andamiaje —que verifican que el registro registra, no que el asiento se asigna—, tienes una señal de sobre-estructura, y además vas a tener que borrarlas: prueban algo que va a dejar de existir.
- Si no hay pruebas, ese es tu primer trabajo, y va antes que el refactor.
Movimiento 7 — Pregúntale a una persona, con la pregunta correcta. Es el movimiento más eficiente y el que más se evita, por una mezcla de prisa y de no querer parecer novato. Dos consejos que lo hacen funcionar:
- Llega con la investigación hecha. "¿Sabes por qué está este
sleep?" recibe un "ni idea". "Vi que esto vino con el #2214 por lo del antifraude de MercadoPago; ¿sigue pasando?" recibe una respuesta útil en un minuto. - Pregunta por el problema, no por el código. "¿Qué pasaba antes de que existiera esto?" es la mejor pregunta de esta lección. La gente no recuerda decisiones; recuerda problemas.
Y una advertencia: si el equipo original ya no está, no es una excusa para saltarse el procedimiento. Es la razón por la que el procedimiento se apoya en el historial, que sí se queda.
Cuándo la respuesta es "no lo sé, y aun así hay que avanzar"
Sería deshonesto terminar la lección sin decir esto: a veces investigas y no encuentras nada. El commit dice "fix", el autor se fue, no hay ticket, no hay prueba, y la línea lleva cuatro años ahí. ¿Entonces?
La respuesta no es parálisis. Es bajar el riesgo del experimento en lugar de bajar la incertidumbre. Tres formas, de menos a más costosa:
Uno: hazlo observable antes de cambiarlo. Si no sabes si una rama se ejecuta, no la borres: instruméntala. Un registro que anote cada vez que entra, desplegado una o dos semanas, convierte una suposición en un dato.
# Antes de borrar la rama que "seguro nunca se usa":
if order.legacy_flow:
log.warning("rama heredada usada: order=%s provider=%s", order.id, order.provider)
...
Dos semanas después tienes la respuesta y ya no tienes que discutirla. Es el equivalente moderno de ir a preguntarle al dueño del campo: cuando no hay a quién preguntar, le preguntas a producción.
Dos: cámbialo de forma reversible y en pequeño. Si vas a quitar algo de lo que no estás seguro, quítalo en un cambio propio, chiquito, fácil de revertir, y no mezclado con otras siete cosas. Así, si aparece el pozo debajo de la cerca, revertir cuesta un minuto y no una tarde de arqueología sobre tu propio diff.
Tres: si toca datos, no lo hagas. Aquí la regla se endurece y no admite matices, porque es la misma del módulo 2: el código es reversible; los datos no. Puedes experimentar con una rama de código; no puedes experimentar con una columna que borras. Si tu incertidumbre está del lado de los datos, la respuesta es esperar y confirmar, no probar.
Y una última idea, que te va a servir mucho más de lo que parece: cuando no encuentres el porqué, escríbelo. Un comentario que diga "esta rama lleva cuatro años, no encontramos su origen (buscado en el historial hasta 2022, sin ticket asociado); si sabes para qué es, documéntalo" es una contribución real. La próxima persona empieza donde tú terminaste en vez de empezar de cero. La ignorancia documentada vale mucho más que la ignorancia silenciosa.
Errores comunes
Confundir "no entiendo por qué está" con "no hay razón" (de razonamiento). Qué pasa: alguien encuentra una línea que no le cierra, no encuentra explicación en cinco minutos y concluye que no la tiene. Borra. Semanas después aparece el problema del que esa línea protegía, normalmente en un lugar que nadie asocia con el cambio —la conciliación nocturna, el reporte de fin de mes, un caso de un solo cliente grande—. Por qué pasa: la ausencia de una explicación visible se siente igual que la ausencia de explicación, y el código no distingue entre las dos: una línea sin comentario se ve idéntica tenga o no razón. Cómo detectarlo: pregúntate si tu argumento para borrar es "esto protege de X y X ya no ocurre" o simplemente "esto se ve mal". Si es lo segundo, no investigaste, opinaste. Cómo corregirlo: los movimientos 4 y 5, que cuestan diez minutos. Y una regla de bolsillo excelente: el código raro que sobrevivió años tiene presunción a su favor; el código raro de la semana pasada, no.
Leer la implementación antes que los llamadores (de método). Qué pasa: alguien abre el archivo más grande del rincón y lo lee de arriba abajo, línea por línea. Sale una hora después con la cabeza llena de detalles y sin entender para qué sirve nada, porque le faltó lo único que da sentido a una implementación: qué se le pide y quién usa el resultado. Y como se sintió agotador, la siguiente vez ni lo intenta. Por qué pasa: es lo que uno hace por defecto, y además da la sensación de rigor. Cómo detectarlo: si después de leer no puedes explicar el rincón en dos frases —"recibe esto, decide aquello, y lo usan estos tres"—, leíste en el orden equivocado. Cómo corregirlo: movimientos 1 y 2 primero, siempre. Primero el vecindario y el flujo, después el detalle. La implementación se entiende desde su contrato, nunca al revés.
Investigar hasta entenderlo todo (de proporción). Qué pasa: alguien toma en serio la lección, y se pasa tres días leyendo el sistema entero antes de tocar una línea. Aprende muchísimo, no entrega nada, y para cuando empieza a refactorizar ya olvidó la mitad. Este error es el opuesto del anterior y es menos comentado, pero cuesta igual —en tiempo, y en la credibilidad de la práctica: un equipo que ve que "leer antes de tocar" significa tres días deja de hacerlo—. Por qué pasa: no hay una señal clara de cuándo parar, y siempre hay un archivo más. Cómo detectarlo: si estás leyendo código que no vas a tocar y que no llama ni es llamado por lo que vas a tocar, te pasaste. Cómo corregirlo: define de entrada el perímetro —lo que voy a tocar, más quien lo llama, más quien lo consume— y date un tiempo fijo, media hora o una tarde según el tamaño. La prueba de suficiencia es esta: ¿puedo escribir la prueba de caracterización? Si puedes escribir la prueba que fija el comportamiento actual, entendiste lo necesario. Si no puedes, todavía no.
Ejercicios
Ejercicio 1 — Clasifica cinco cercas. Para cada una, di si es tipo 1 (sigue teniendo razón), tipo 2 (la tuvo y ya no) o tipo 3 (nunca la tuvo), o qué averiguarías para decidirlo. Justifica en dos líneas.
(a) En pricing/: if now <= cutoff para el descuento de early-bird, con un comentario que dice "después del corte el early-bird cuesta como el general, así el organizador no tiene que despublicar boletos".
(b) En notifications/sms_channel.py: text = text[:160], sin comentario, agregado en 2021.
(c) En checkout/checkout.py: except Exception: pass alrededor de la llamada a analítica, agregado en el mismo commit que un incidente titulado "El checkout se cayó porque el proveedor de métricas estaba caído".
(d) En api/routes.py: if request.headers.get("X-Client") == "ios-1.2": ..., una rama especial para una versión concreta de la app.
(e) En utils/dates.py: una función _fix(dt) que suma seis horas a una fecha, llamada desde un solo lugar.
Ver solución
(a) Tipo 1, y ya está documentada. La razón está escrita y es de negocio, no técnica: evita trabajo manual al organizador. Nota que esta cerca es del tipo que más se borra por accidente durante un refactor, porque parece una simplificación obvia ("si pasó el corte, no debería venderse como early-bird"). El comentario es lo único que la protege; una prueba lo haría mejor.
(b) Falta averiguar, pero la hipótesis es fuerte: tipo 1. Ciento sesenta caracteres es el largo de un SMS estándar; cortar ahí evita que el mensaje se parta en varios y se cobre doble. Qué averiguar: el commit (git log -S "160"), y si el proveedor actual sigue cobrando por segmento. Si el proveedor cambió y hoy soporta mensajes largos, pasa a tipo 2. Fíjate en que la respuesta depende de un hecho externo al código, que es lo típico de esta lección.
(c) Tipo 1, y de manual. El commit que la trajo tiene el incidente escrito al lado: el checkout se caía porque un sistema secundario estaba caído. Atrapar y seguir es lo correcto ahí —analítica no puede tumbar una venta—. Lo que sí conviene mejorar es la forma: except Exception: pass se traga también los errores propios; mejor registrar el fallo antes de continuar. Ese es un cambio que respeta la cerca y la vuelve legible, no uno que la quita.
(d) Falta averiguar, y aquí la evidencia decisiva es de producción, no del repositorio. La pregunta es: ¿cuántos usuarios siguen en la versión 1.2 de la app de iOS? Si son cero desde hace meses, es tipo 2 y se quita. Si son el 3%, es tipo 1 y se queda hasta que ese 3% actualice. Ninguna cantidad de lectura de código contesta esto; se contesta con la analítica de la app. Es el caso más claro de "la evidencia no está en el código".
(e) Sospechosa, pero no la clasifiques todavía: es la más peligrosa de las cinco. Sumar seis horas fijas huele a un arreglo de zona horaria hecho a mano, que sería tipo 2 o 3 —y una fuente potencial de errores dos veces al año, con el horario de verano—. Pero también podría estar compensando un dato que otro sistema entrega mal, y en ese caso quitarla rompe la corrección. Qué averiguar: el commit, el único sitio de llamada, y con qué dato exacto trabaja. Regla general: nada que involucre fechas, zonas horarias o dinero se toca sin entenderlo del todo.
Por qué funciona: de las cinco, solo una se puede clasificar leyendo el código. Dos necesitan el historial, una necesita datos de producción y una necesita las dos cosas. Esa proporción es la realista, y es el argumento entero de la lección.
Ejercicio 2 — Escribe la investigación, no la conclusión. Encuentras esta línea en el checkout de Boletia, al final del bloque de cortesías:
if customer.email.endswith("@boletia.com"):
issued = 0 # ← ¿?
Escribe el plan de investigación: los comandos exactos que correrías, en orden, y qué esperarías encontrar en cada uno. Después escribe las dos conclusiones posibles y qué harías en cada caso.
Ver solución
El plan:
# 1. ¿Cuándo apareció y con qué mensaje?
git log -S "@boletia.com" --oneline -- boletia/checkout/checkout.py
# 2. ¿Quién la tocó por última vez y en qué contexto?
git blame -L 210,220 boletia/checkout/checkout.py
# 3. ¿Aparece el mismo truco en otros lados? (si sí, es un mecanismo, no un parche)
grep -rn "@boletia.com" --include='*.py' .
# 4. ¿Hay prueba que lo cubra?
grep -rn "boletia.com" tests/
# 5. Y fuera del repositorio: el ticket que mencione el commit.
Y una consulta de solo lectura a producción, si tienes acceso: cuántas cortesías se han emitido con correos de ese dominio y con qué frecuencia. Un dato de uso vale más que cualquier interpretación.
Conclusión posible A — es un mecanismo de trabajo real. El equipo interno emite cortesías para pruebas, demostraciones comerciales y accesos de soporte, y el tope de cincuenta por evento no debería aplicarles. Entonces la cerca es tipo 1 y lo que falta es hacerla explícita: sacarla del if anónimo y convertirla en algo con nombre, por ejemplo un permiso del cliente o una constante INTERNAL_EMAIL_DOMAIN con su comentario. La lógica no cambia; deja de estar escondida.
Conclusión posible B — es un atajo que alguien dejó puesto. Alguien necesitó saltarse el tope una tarde para una demostración y no lo quitó. Entonces es tipo 3 y se quita. Pero ojo: quitarla cambia comportamiento, así que va en su propio cambio, con aviso al equipo, y conviene mirar antes si alguien la está usando hoy sin saber que existe.
Lo que no puedes hacer es elegir entre A y B por cómo se ve la línea, porque en los dos casos se ve exactamente igual. Y fíjate en un detalle de seguridad que aplica en las dos: comparar por el final del correo es una comprobación frágil —atacante@no-es-boletia.com.mx no coincide, pero un dominio mal validado en el registro sí podría—. Ese hallazgo es independiente del tipo de cerca y vale la pena reportarlo aparte.
Por qué funciona: el ejercicio te obliga a producir un plan en vez de un veredicto. En una revisión de código, quien llega con un plan de verificación siempre gana la conversación frente a quien llega con una impresión, incluso cuando la impresión es correcta.
Ejercicio 3 — El comentario que faltaba. Vuelve al caso de MercadoPago del ejemplo trabajado. Supón que la investigación la hiciste tú, hoy, y que la línea se queda. Escribe el comentario definitivo —máximo seis líneas— que dejarías en el código, y explica por qué incluiste cada elemento. Después di qué otra cosa dejarías además del comentario.
Ver solución
Una versión que funciona:
# MercadoPago revisa por antifraude las operaciones por encima de este monto:
# su API de consulta tarda 1-3 s en reflejar un cobro que ya aprobó.
# La conciliación de las 3:00 usa esa API y marcaba como faltantes órdenes
# ya pagadas (47 en enero de 2025; 2 clientes se quedaron sin boletos, #2214).
# Confirmado por el proveedor (MP-88431). Se quita cuando su API sea consistente.
Los cinco elementos, y por qué está cada uno:
- Qué hace el proveedor (la causa técnica). Sin esto, el lector siguiente cree que el problema es nuestro.
- Quién sufre el problema (la conciliación). Es lo que convierte dos líneas sueltas en un mecanismo con dos extremos; sin esto, alguien "arregla" un extremo sin ver el otro.
- El costo real, con números y fecha. Cuarenta y siete órdenes y dos clientes sin boletos es lo que hace que nadie lo borre por estética. Un comentario que dice "importante, no quitar" no convence a nadie; uno con un número, sí.
- La referencia externa (
#2214,MP-88431). Permite que la próxima persona continúe la investigación en lugar de repetirla. - La condición de salida. Es el elemento que más falta en los comentarios del mundo real. Sin él, esta línea se queda para siempre aunque el proveedor arregle su API mañana, porque nadie sabrá que se podía quitar.
Qué más dejarías: la prueba. Un comentario protege de quien lee; una prueba protege de quien no lee, que son más. Algo así:
def test_high_amount_mercadopago_orders_stay_pending_until_confirmed():
"""Fija el comportamiento del #2214: por encima del umbral, la orden no se
marca como pagada hasta que la consulta al proveedor lo confirma."""
...
Y nota el nombre de la prueba: menciona el ticket. Cuando falle dentro de dos años, quien la vea fallar va a tener el hilo completo en el nombre.
Por qué funciona: el resultado de investigar una cerca del tipo 1 no es "no hice nada". Es un rincón que quedó estrictamente mejor que antes —documentado, probado, con su número mágico bautizado y con una condición de salida escrita— sin que el comportamiento cambiara ni un carácter. Ese es, de hecho, uno de los cambios más valiosos que puedes entregar en un sistema heredado.
Resumen y siguiente paso
En esta lección instalaste la regla que evita los desastres: entender qué hace un código y por qué está así, antes de mejorarlo. La imagen es la cerca en medio del campo: nadie te prohíbe quitarla, se te pide que averigües para qué la pusieron antes de decidir. La regla es asimétrica a propósito, porque las consecuencias lo son: averiguar cuesta una tarde, equivocarse cuesta un incidente.
Viste los tres tipos de cerca —la que sigue teniendo razón, la que la tuvo y ya no, la que nunca la tuvo— y que en código con años en producción la tercera es la menos frecuente, porque lo que nunca tuvo razón suele fallar y quitarse pronto. De ahí la presunción a favor de la línea rara que sobrevivió tres años.
Investigaste una línea real del checkout de Boletia con los siete movimientos: leer desde afuera, recorrer el flujo, preguntarle al historial por el archivo y por la línea (git log -S, git blame), seguir el rastro fuera del repositorio, leer las pruebas como documentación y preguntarle a una persona con la investigación ya hecha. Encontraste un revert —la señal más fuerte de que una cerca es del tipo 1—, un incidente con números, un ticket con el proveedor, y el otro extremo del mecanismo en la conciliación nocturna. Veinte minutos de trabajo contra dos días de descuadres.
Y viste que el resultado de una investigación bien hecha casi nunca es "no toques nada": es un rincón documentado, con su número mágico bautizado, con la prueba que le faltaba y con la condición de salida escrita. Más las tres salidas para cuando investigas y no encuentras nada: hazlo observable, cámbialo de forma reversible y pequeña, y si toca datos, no lo hagas.
Antes de avanzar deberías poder: explicar la cerca de Chesterton en dos frases y por qué la regla es de orden y no de conservadurismo; nombrar los tres tipos de cerca con la decisión que corresponde a cada uno; y ejecutar los movimientos 3 y 4 —los del historial— sin consultar la lección.
Ahora que sabes leer, empieza el diagnóstico. La lección 3 se ocupa de la primera de las dos direcciones: reconocer que un código está pidiendo estructura. Vas a ver las señales concretas —el condicional que crece por el mismo eje, la duplicación con variación, el módulo que cambia por tres razones distintas— y, sobre todo, la diferencia entre imponer un patrón porque lo conoces y dejar que el patrón emerja del problema. Vamos a trabajar sobre el checkout de Boletia, que acabas de leer, y vas a descubrir que la técnica para que un patrón emerja es sorprendentemente mecánica: extraer primero, mirar las firmas después, y nombrar al final.
Recursos
- Chesterton's Fence (el pasaje original, en The Thing, 1929) — la fuente de la imagen, con el argumento completo de Chesterton. Vale leerlo una vez porque el matiz importa: no dice que la cerca deba quedarse, dice que el que no sabe para qué está no es la persona indicada para quitarla.
- Working Effectively with Legacy Code (Michael Feathers) — el manual completo de cómo tocar código que no entiendes, con las técnicas de "sondeo" y las pruebas de caracterización que son la red del movimiento 6.
- Git Log Searching: pickaxe (
-Sy-G) — la documentación de la herramienta del movimiento 4. Es la funcionalidad de Git que más gente desconoce y la que más rápido paga: encuentra el commit exacto que introdujo o borró un texto. - Coding Without Comments (Jeff Atwood) — sobre por qué un comentario que explica qué hace el código sobra, y uno que explica por qué está ahí es irreemplazable. Es la diferencia entre "espera 2 segundos" y el bloque completo que escribiste en el ejercicio 3.