Módulo 8: Refactorizar con criterio (capstone)

6. Justificar el cambio (el porqué, no el nombre)

Descripción

Al terminar esta lección vas a saber defender una refactorización de forma que se apruebe, y la tesis es incómoda para quien acaba de aprender un vocabulario nuevo: el nombre del patrón no justifica nada. Nadie aprueba un cambio porque diga "apliqué Strategy". Se aprueba porque dice "agregar un proveedor de pago tocaba cuatro archivos y ahora toca uno; el costo es un archivo más y un salto al leer; lo revertiría si nos quedáramos con un solo proveedor". Vas a salir con la fórmula de tres partes que hace eso —unidad de medida, costo aceptado, condición de reversión—, con el catálogo de unidades que de verdad convencen, con la plantilla de cinco secciones del documento, y con las respuestas a las tres objeciones que siempre aparecen.

Esto importa por una razón que se ve apenas entras a un equipo: la mitad de las refactorizaciones correctas se rechazan por cómo están escritas. Quien revisa tu cambio no tiene tu contexto. No sabe cuántos archivos hay que tocar hoy para agregar un proveedor, no vio el historial que miraste, no sabe que hubo un incidente en noviembre. Lo único que ve es un diff que toca el corazón del sistema. Y en ausencia de información, cualquiera evalúa lo único que puede evaluar sin contexto: el riesgo. Un cambio de trescientas líneas en el checkout, sin justificación, es riesgo alto por definición. Se rechaza, o se queda meses sin revisar, que es la misma cosa con peor cara.

Hay además un efecto de segundo orden que conviene ver desde ahora. Un documento de justificación bien escrito no solo aprueba tu cambio: le enseña al equipo cómo se decide. Cuando la próxima persona tenga una duda parecida, va a encontrar tu documento y va a tener un ejemplo de cómo se argumenta con datos. Ese es el mecanismo por el que el criterio se propaga en un equipo, y no hay ninguno mejor.

Conexión con el módulo: las lecciones 3 y 4 te dieron los diagnósticos y —esto es lo importante aquí— las unidades de medida: cuántos archivos hay que tocar para agregar un caso, cuántos saltos hacen falta para responder una pregunta, cuántas líneas de andamiaje hay por cada línea de trabajo, cuántas implementaciones existen de verdad. La lección 5 te dio la bitácora, que es la prueba de que el sistema nunca estuvo roto y una de las cinco secciones del entregable. La lección 7 te va a dar la justificación más difícil de todas: la de no hacer nada, que se escribe con la misma fórmula. Y el proyecto final se evalúa, más que por el código, por el documento que vas a aprender a escribir aquí.

El presupuesto del plomero

Se te tapa el desagüe de la cocina. Llamas a un plomero, viene, trabaja dos horas y te deja una nota. Compara dos versiones de esa nota.

Versión A:

Se instaló un sifón botella con registro y se sustituyó el bajante por PVC de 50 mm.

Es exacta, es técnica, y es completamente inútil para ti. No sabes si eso era necesario, no sabes si te cobró bien, y no sabes qué hacer la próxima vez. Lo único que puedes hacer es confiar o desconfiar, y las dos son incómodas.

Versión B:

El desagüe se tapaba cada dos o tres meses porque el tubo viejo tenía un codo cerrado donde se acumulaba la grasa. Le puse un tubo más ancho y un registro que se abre a mano: si se vuelve a tapar, lo destapas tú en cinco minutos con un balde debajo, sin llamar a nadie. Costó $1,800 más que solo destaparlo esta vez, que habrían sido $600. Con que evites tres visitas, se pagó solo. Si la cocina fuera de uso ligero y se tapara una vez al año, no valía la pena y te habría cobrado los $600.

Fíjate en lo que hace la versión B y que la A no hace:

Empieza por el problema, no por la solución. "Se tapaba cada dos o tres meses" es la unidad de medida. Sin ella, todo lo demás es una opinión sobre tubos.

Dice qué se gana en la misma unidad. No dice "quedó mejor": dice que ahora lo destapas tú en cinco minutos.

Dice qué costó, en números y sin esconderlo. Mil ochocientos pesos más. Un plomero que no menciona el costo extra parece que lo está ocultando, aunque no lo esté.

Y —la parte que casi nadie hace— dice bajo qué condición la decisión habría sido la contraria. "Si se tapara una vez al año, no valía la pena." Esa frase es la que demuestra que hubo un juicio y no una costumbre. Y de paso te da algo concreto que discutir: puedes decirle "oye, es que se tapó una sola vez en dos años", y si tienes razón, los dos ganan.

Un buen argumento de refactorización tiene exactamente esa forma. Y nota lo que no aparece en la versión B: el nombre del sifón. El nombre técnico está en la A, que es la que no convence a nadie.

La fórmula: tres partes y ninguna sobra

Un argumento de refactorización que funciona tiene tres partes. Si le falta una, se cae; y cuál falta predice cómo se cae.

Parte 1 — La unidad de medida, antes y después. Una magnitud concreta, contable, que le importe a alguien. No "el código queda más limpio", sino "agregar un proveedor tocaba cuatro archivos y ahora toca uno".

Si falta: tu propuesta es estética y se discute como cuestión de gusto. Contra un adjetivo se pelea; contra un número se discute, que es distinto y mucho mejor.

Parte 2 — El costo aceptado, dicho por ti. Toda estructura se paga. Un archivo más, un salto más al leer, un concepto nuevo que explicarle a quien entre al equipo, dos consultas a la base que antes no se hacían. Decirlo tú, antes de que te lo digan, hace tres cosas: demuestra que entendiste el tradeoff, desactiva la objeción principal de quien revisa, y —sobre todo— te vuelve creíble en la parte 1.

Si falta: quien revisa asume que no lo viste, y entonces tiene que buscarlo él. Y cuando lo encuentra, la conversación deja de ser sobre tu propuesta y pasa a ser sobre lo que se te escapó.

Parte 3 — La condición de reversión, verificable. Bajo qué circunstancia concreta esta decisión sería la equivocada, y bajo cuál habría que deshacerla. "Si nos quedáramos con un solo proveedor de pago, esto sobra y volvería a una llamada directa."

Si falta: tu propuesta es una regla, no un criterio. Y una regla invita a discutir la regla en abstracto —"¿siempre hay que meter una fábrica?"— que es una discusión que nadie gana.

La forma completa cabe en tres líneas:

Hoy, agregar un proveedor de pago obliga a tocar cuatro archivos, y olvidarse de uno hace que la conciliación no cuadre a fin de mes sin que nadie se entere. Con este cambio pasa a ser un archivo. El costo es un archivo más en el árbol y un salto extra al leer el checkout. Si Boletia se quedara con un solo proveedor, este cambio sobra y lo revertiría a una llamada directa.

Y ahora fíjate en algo: el nombre del patrón no aparece. Puedes agregarlo —"esto es una Factory"— y ayuda a que la otra persona vea la estructura de un vistazo. Pero es una etiqueta que resume, no el argumento. La prueba está en que el párrafo funciona perfectamente sin ella, y el nombre solo no funciona en absoluto.

Ejemplo trabajado: el mismo cambio, tres mensajes

Acabas de terminar el refactor de la lección 5: los proveedores de pago salieron del if y quedaron detrás de un contrato con una fábrica. Ahora hay que escribir el mensaje que abre la revisión. Aquí van tres versiones del mismo trabajo.

Versión A — solo el nombre.

Refactor: proveedores de pago con Factory + Strategy

Extraigo los proveedores a clases que implementan PaymentProvider y agrego una factory para resolverlos por nombre. Queda mucho más limpio y respeta el principio abierto-cerrado.

Léelo como lo leería quien revisa. Sabe qué hiciste y no sabe por qué. Y hay tres frases que juegan en contra sin que el autor se dé cuenta:

  • "Queda mucho más limpio": es una afirmación sobre tu gusto. La respuesta honesta de la otra persona es "a mí me parecía claro el if", y ya están discutiendo preferencias.
  • "Respeta el principio abierto-cerrado": invocar un principio sin nombrar el eje donde el cambio es probable es invocar la mitad del principio, como viste en el módulo 2. Y peor: convierte la conversación en una discusión sobre SOLID, en abstracto, donde nadie gana.
  • Y los dos nombres de patrón al principio hacen que quien revisa evalúe el patrón en vez de tu problema. Si esa persona tuvo una mala experiencia con fábricas, ya empezaste perdiendo.

Versión B — solo la prosa, sin números.

Refactor: saca los proveedores de pago del checkout

Ahora mismo, cada vez que agregamos una forma de cobrar hay que tocar el checkout, las devoluciones, la conciliación y la validación de la petición. Es fácil olvidarse de alguno y el error no se nota hasta bastante después. Con este cambio, cada proveedor vive en su archivo y hay un solo lugar que sabe cuáles existen.

Esta ya es bastante mejor: empieza por el problema, no menciona ningún patrón, y describe el riesgo real. Le falta una sola cosa, y es la que la vuelve discutible: no tiene números ni condición de reversión. Alguien puede contestar "¿y con qué frecuencia agregamos proveedores?" y no hay con qué responder salvo con otra impresión.

Versión C — la fórmula completa.

Refactor: un solo lugar que sepa qué proveedores de pago existen

El problema, medido. Hoy la lista de proveedores está escrita en cuatro archivos: checkout.py, admin/refunds.py, reports/reconciliation.py y api/routes.py, cada uno con su propio if. Al agregar MercadoPago (commit c1a9e02) se tocaron los cuatro; al agregar efectivo se tocaron tres y se olvidó la conciliación, que estuvo dos meses sin contar los pagos en tienda —lo detectó contabilidad, no nosotros—. Hemos agregado dos proveedores en dos años y hay una conversación abierta sobre agregar un tercero para Colombia.

Qué cambia. Agregar un proveedor pasa de tocar 4 archivos a tocar 1: una clase nueva y una línea en el diccionario. La conciliación deja de tener la lista escrita a mano y la recorre, así que ya no puede quedarse desactualizada en silencio. Y la lógica rara del antifraude de MercadoPago (#2214), que vivía suelta en el checkout, ahora está dentro de su proveedor, con nombre y con prueba —antes no tenía ninguna—.

Qué cuesta. Cuatro archivos nuevos en payments/ y un salto más al leer: quien siga el flujo del cobro ya no ve el código de Stripe en el checkout, ve provider.charge(order) y tiene que abrir otro archivo. También hay un concepto nuevo del proyecto, PaymentResult, que hay que explicarle a quien entre. Me parece un intercambio favorable con tres proveedores; con uno solo no lo sería.

Cómo se hizo. Nueve commits, cada uno con las pruebas en verde (bitácora abajo). El camino nuevo se construyó completo antes de mover ningún tráfico; los cuatro archivos se movieron de a uno, empezando por la conciliación y dejando el checkout para el final. Las pruebas de caracterización se escribieron antes de tocar nada y no cambiaron en todo el trabajo: el comportamiento observable es idéntico.

Cuándo lo revertiría. Si nos quedáramos con un solo proveedor de pago —por ejemplo, si el acuerdo con la pasarela nueva incluyera exclusividad—, esto sobra: volvería a una llamada directa y borraría el contrato. Y si algún día un tercero tuviera que enchufar su propio proveedor sin que nosotros despleguemos, este diseño no alcanza y habría que subir a un mecanismo de extensión de verdad; hoy eso no está en el roadmap.

Qué esperar de estas tres versiones. Cinco observaciones, y la primera es la que más sorprende.

Primera: la versión C no menciona ningún patrón. Ni Factory, ni Strategy, ni abierto-cerrado. Y sin embargo describe exactamente la misma estructura que la versión A. Si quien revisa conoce el vocabulario, va a leer "un solo lugar que sabe cuáles existen" y va a pensar "ah, una Factory" por su cuenta —lo cual es mejor, porque lo pensó él—. Y si no lo conoce, entiende igual. El nombre es opcional; el argumento no.

Segunda: cada afirmación es verificable. "Se olvidó la conciliación" no es una acusación: es un hecho que cualquiera puede comprobar mirando el commit. "Dos proveedores en dos años" está en el historial. "No tenía ninguna prueba" se comprueba en cinco segundos. Cuando cada frase se puede verificar, la conversación deja de ser sobre si tienes razón y pasa a ser sobre qué hacer.

Tercera: la sección de costo es la que más credibilidad da, y es la que todo el mundo omite. Al decir tú mismo "un salto más al leer" y "un concepto nuevo", quien revisa deja de tener que buscarte el defecto. Y hay un efecto secundario notable: la frase "me parece favorable con tres proveedores; con uno solo no lo sería" demuestra en una línea que aplicaste criterio y no una regla.

Cuarta: no hay una sola referencia a personas. No aparece "quien escribió esto", ni "no se pensó bien", ni "el código estaba mal". El sujeto de todas las frases es el código. Eso no es corrección política: es lo que hace que la conversación siga siendo técnica. Un argumento correcto redactado como reproche pierde de todas formas.

Quinta: la sección "cómo se hizo" existe para desactivar el miedo. Quien revisa un cambio en el corazón del sistema tiene una preocupación por encima de todas las demás: ¿esto va a romper el cobro?. "Las pruebas de caracterización se escribieron antes y no cambiaron" contesta esa pregunta directamente. Sin esa frase, la persona tiene que deducirlo del diff, y con un diff grande no va a poder.

Las unidades que convencen

La parte 1 de la fórmula necesita una unidad, y no todas sirven igual. Estas son las que funcionan, con lo que cada una es buena para justificar:

UnidadSe mide asíBuena para justificar
Archivos que tocar para agregar un casomira el --stat del commit donde se agregó el últimoagregar estructura donde el conocimiento está disperso
Saltos para responder una pregunta concretacuéntalos: "¿cómo se elige el asiento?" → 6 saltos, 4 archivosquitar estructura innecesaria
Líneas de andamiaje por línea de trabajo183 líneas de las cuales 17 hacen algo → 10 a 1quitar estructura innecesaria
Implementaciones reales, en algún entornogrep en todo el repositorio, tests incluidoslas dos direcciones; es la unidad central
Veces que cambió en el último añogit log --oneline --since="1 year ago" -- ruta/priorizar: qué se toca seguido merece atención
Incidentes atribuiblesbusca en el registro de incidentes por el módulocualquier dirección, y es la más contundente
Tiempo de la última vez"el proveedor anterior tomó dos días y medio"trabajo que desbloquea a alguien
Pruebas que antes eran imposiblescuenta las que escribiste después y no podías antesagregar estructura; es el beneficio más duradero

La primera unidad de la tabla es la que más se usa y la que menos cuesta conseguir. Sale de un solo comando, y conviene pegar su salida tal cual en el documento:

# ¿Cuántos archivos hubo que tocar la última vez que agregamos un proveedor?
git show c1a9e02 --stat
Agrega proveedor de pago MercadoPago

 boletia/checkout/checkout.py          | 12 ++++++
 boletia/admin/refunds.py              |  9 +++++
 boletia/reports/reconciliation.py     |  7 +++++
 boletia/api/routes.py                 |  5 ++++
 4 files changed, 33 insertions(+)

Cuatro archivos, y ahí está tu frase: "de cuatro a uno". Nadie discute contra el historial de su propio repositorio.

Y las que no sirven, aunque se usen mucho:

  • "Líneas de código" a secas. Menos líneas no es mejor: un código comprimido puede ser peor. La cifra solo vale acompañada —"183 líneas de las cuales 17 hacen trabajo"—.
  • "Complejidad ciclomática" y métricas de herramienta, cuando se citan solas. Son útiles para encontrar dónde mirar, y muy malas como argumento: nadie ha tenido nunca un problema de negocio porque un número de una herramienta estuviera alto.
  • "Es una buena práctica" y "lo dice SOLID". Son apelaciones a la autoridad, y la respuesta correcta a una apelación a la autoridad es otra apelación a la autoridad. La discusión no termina.
  • "Yo lo haría distinto". Cierto y sin valor. Todos harían todo distinto.

Una nota sobre precisión. No hace falta que tus números sean exactos al decimal; hace falta que sean verificables y honestos. "Unos cuarenta y siete" está bien si es lo que dice el reporte. Inventar un número, aunque sea plausible, es la forma más rápida de perder toda tu credibilidad en un equipo: basta con que alguien vaya a comprobarlo una vez.

La plantilla, y las tres objeciones que siempre llegan

Las cinco secciones

El documento del proyecto final —y de cualquier refactorización seria en tu trabajo— tiene cinco secciones. Ninguna es larga.

1. Contexto, en dos líneas. Qué es el sistema, quién lo despliega, quién depende de este código. Sin esto, nada de lo demás se puede evaluar: la misma decisión es correcta en un servicio interno de un equipo de seis y equivocada en una librería pública que consumen cuarenta repositorios.

2. Qué costaba. Números, no adjetivos. Aquí van las unidades de la tabla.

3. Qué se gana, y qué se pierde. Las dos, en la misma sección y con el mismo tono. La segunda parte es la que te vuelve creíble.

4. Cómo se hizo. La bitácora de la lección 5, más la frase clave: las pruebas se escribieron antes y el comportamiento observable no cambió.

5. Bajo qué condición se revertiría. Verificable el lunes por otra persona. "Si en el futuro hay más casos" no cuenta; "si nos quedamos con un solo proveedor" sí, porque alguien puede abrir el roadmap y contestar.

Las tres objeciones

Objeción 1 — "¿Y esto para qué? Funcionaba bien."

Es la objeción más común y la más razonable. Quien la hace tiene un punto: el sistema no estaba caído. La respuesta no es explicar mejor el patrón; es traer el costo que ya se pagó.

"Funciona, sí. Lo que cuesta es cambiarlo: los dos últimos proveedores tomaron dos días y medio cada uno, y en uno de ellos se olvidó la conciliación y estuvo dos meses sin contar el efectivo. Si no vamos a agregar más, tienes razón y no vale la pena. Si el de Colombia va, esto lo paga en el primero."

Fíjate en la estructura: reconoce el punto, trae el número, y le devuelve la decisión a un hecho comprobable. Eso último es lo más importante. Una propuesta que se apoya en un hecho verificable convierte la discusión en una pregunta de negocio, y las preguntas de negocio se contestan.

Objeción 2 — "¿No es sobre-ingeniería?"

Viene de alguien que ha visto código sobre-diseñado, es decir, de alguien con buen criterio. Se responde con la pregunta de bolsillo del módulo 2 y con el peldaño.

"Es la preocupación correcta y me la hice. Hay tres implementaciones reales hoy, no una. Y elegí el peldaño más bajo que resuelve el problema: un diccionario de tres entradas, sin registro dinámico, sin descubrimiento, sin configuración externa. Si mañana hubiera una sola implementación, esto sobraría —de hecho es exactamente lo que estoy quitando en plugins/, en el otro cambio—."

Esa última frase, si puedes decirla con verdad, vale más que todo el resto. Nada demuestra criterio como estar quitando una abstracción con la misma mano con la que agregas otra.

Objeción 3 — "Ya que estás, ¿por qué no también…?"

Alguien propone ampliar el alcance: aprovechar para el enum de Ticket.kind, o meter el reintento de Stripe, o hacer lo mismo con las notificaciones. Casi siempre son buenas ideas y aceptarlas es un error.

"Estoy de acuerdo con las tres y las anoté. Van aparte por una razón: si meto un cambio de comportamiento en este, dejo de poder decir 'el comportamiento observable es idéntico', que es lo único que hace revisable este diff. El enum además toca datos, así que necesita su propio plan de migración."

La respuesta no es "no": es "sí, después, y aquí está la razón del orden". Y hay una versión escrita de esta respuesta que funciona todavía mejor: una lista al final del documento titulada "lo que encontré y no toqué", con lo hallado y por qué queda fuera. Esa lista convierte una posible crítica en una contribución, y es literalmente parte del entregable del proyecto final.

Errores comunes

Justificar por el nombre del patrón (de comunicación). Qué pasa: alguien titula su cambio "Refactor: aplica Strategy a las reglas de precio" y espera que eso baste. Quien revisa evalúa entonces el patrón en abstracto —"¿de verdad hace falta una Strategy aquí?"— en vez de evaluar el problema, y la conversación se va a una discusión de catálogo donde el diff original nunca se mira. Por qué pasa: el nombre es lo más nuevo que uno aprendió y produce una sensación fuerte de precisión. Y en una revisión de código entre pares que ya conocen el problema el nombre sí funciona como atajo, así que a veces refuerza el hábito. Cómo detectarlo: quita el nombre del patrón de tu mensaje. Si lo que queda no dice nada, no tenías un argumento. Cómo corregirlo: escribe primero el párrafo de la fórmula —unidad, costo, reversión— y agrega el nombre al final, como etiqueta, si ayuda. El nombre resume un argumento que ya existe; no lo sustituye.

Esconder el costo (de credibilidad). Qué pasa: alguien escribe una justificación entusiasta que solo enumera beneficios. Quien revisa encuentra el costo por su cuenta —un archivo más, dos consultas nuevas a la base— y a partir de ahí lee el resto con desconfianza, porque ya sabe que la lista estaba sesgada. El cambio se aprueba tarde o con condiciones. Por qué pasa: se confunde defender una propuesta con venderla, y en la venta los costos estorban. Pero una revisión no es una venta: es una decisión conjunta entre dos personas que quieren lo mismo. Cómo detectarlo: si tu documento no tiene ninguna frase que empiece con "el costo es" o "lo que se pierde es", está incompleto. Cómo corregirlo: escribe la sección de costos primero, antes que la de beneficios. Es más fácil y sale más honesta. Y si al escribirla te das cuenta de que el costo pesa más que el beneficio, acabas de ahorrarte una semana de trabajo, que es el mejor resultado posible de esta lección.

Escribir una condición de reversión que nadie puede comprobar (de rigor). Qué pasa: alguien cierra su documento con "revertiría esto si en el futuro el sistema evoluciona de otra manera". Suena prudente y no dice nada: no hay forma de comprobarla, así que la decisión nunca se va a revisar y la estructura se va a quedar para siempre aunque deje de tener sentido. Por qué pasa: la condición se escribe al final, con prisa, y el reflejo es cubrirse con una frase vaga en vez de comprometerse con una concreta. Cómo detectarlo: pásale la condición a otra persona y pregúntale "¿podrías contestarme sí o no el lunes?". Si duda, es vaga. Cómo corregirlo: ancla la condición a algo observable —el número de implementaciones, una línea del roadmap, un contrato, una métrica—. "Si en seis meses seguimos con un solo proveedor y no hay ninguno en el plan, esto sobra" es verificable. Y hay un beneficio extra: una condición verificable es una invitación a que alguien te contradiga con datos, que es la mejor cosa que le puede pasar a una decisión de diseño.

Ejercicios

Ejercicio 1 — Traduce cinco justificaciones malas. Cada una está escrita en términos de patrón, principio o gusto. Reescríbela con la fórmula de tres partes, inventando los datos que harían falta y marcando cuáles tendrías que ir a buscar.

(a) "Apliqué el patrón Repository para desacoplar la lógica de la persistencia." (b) "Quité la clase OrderService porque era un anti-patrón middle man." (c) "Metí un Decorator para el registro de auditoría; es más elegante que meter logs en cada método." (d) "Convertí el if de exportadores en un diccionario de estrategias, siguiendo el principio abierto-cerrado." (e) "Borré la columna seating_plugin porque ya no se usa."

Ver solución

(a) "Las consultas SQL de órdenes están escritas en siete lugares distintos y tres de ellas repiten el filtro status = 'paid'; una lo tiene mal escrito. Concentrarlas en un módulo hace que ese filtro viva en un solo sitio. El costo es un archivo más y un salto. Lo revertiría si las consultas volvieran a ser una o dos." A buscar: cuántos lugares hay de verdad (grep), y si alguna versión difiere —esa diferencia es el argumento más fuerte y quizá también un error—.

(b) "De los seis métodos de OrderService, cinco solo reenviaban al repositorio: quien llamaba no dejaba de saber nada gracias a ellos. El sexto sí definía qué cuenta como venta y se conservó como función en reports/sales.py. Se quitan cinco saltos sin perder nada. Lo revertiría si apareciera lógica de negocio que justifique una capa propia." A buscar: los puntos de llamada por módulo, y si alguien fuera del repositorio la consume.

(c) "Hoy el registro de auditoría está copiado en once métodos y en dos falta —los que agregó el cambio del mes pasado—, así que hay operaciones sin rastro. Envolviendo el objeto, el registro se aplica en un solo lugar y no se puede olvidar. El costo es que el flujo pasa por un envoltorio y el editor ya no te lleva directo a la implementación." A buscar: cuántos métodos deberían auditarse y cuántos lo hacen hoy. Ese hueco es el argumento; "más elegante" no lo es.

(d) "Agregar un formato de exportación toca tres archivos —el exportador, la validación de la petición y el menú del panel— porque la lista de formatos está escrita en los tres. Con un diccionario, la lista vive en uno y los otros dos la consultan. Agregamos XLSX el año pasado y tomó día y medio, sobre todo por encontrar los tres sitios." A buscar: el --stat del commit que agregó XLSX. Y nota que desapareció la mención al principio: el principio no era el argumento, era el adorno.

(e) Trampa: esta no se justifica, se rechaza en su forma. El código que la escribe se puede quitar hoy; la columna no se borra en el mismo trabajo. El código es reversible y los datos no: si un reporte de operaciones la leía, revertir el despliegue no trae el dato de vuelta. La versión correcta: "dejo de escribir la columna en este cambio; confirmo con operaciones y con una búsqueda en reportes que nadie la lee; la elimino dentro de dos semanas en una ventana aparte con respaldo". A buscar: quién la consulta fuera del repositorio —el panel, los reportes, cualquier proceso externo—.

Por qué funciona: cuatro de las cinco mejoran solo con cambiar el orden —problema medido, ganancia, costo, reversión— y la quinta revela que el problema no era la redacción sino el plan. Escribir la justificación es también una forma de revisar tu propio trabajo, y a veces la encuentras rota antes de que lo haga otro.

Ejercicio 2 — La justificación del otro lado. Escribe el documento para el desmontaje de plugins/, con las cinco secciones y un máximo de doce líneas. Usa los datos que ya conoces. Después señala qué sección te costó más y por qué.

Ver solución

Una versión que funciona:

Contexto. Boletia es un servicio único, desplegado por nosotros, mantenido por un equipo de seis personas. Nadie fuera de este repositorio consume este código.

Qué costaba. plugins/ son 5 archivos y 183 líneas, de las cuales 17 hacen trabajo real. Una implementación desde que existe; 0 agregadas en 2 años. supports() devuelve True sin condiciones y el raise SeatingPluginNotFound es inalcanzable en producción. La columna events.seating_plugin vale "default" en todos los eventos publicados, y el panel muestra un desplegable con una sola opción. Responder "¿cómo se elige el asiento?" cuesta 6 saltos y 4 archivos. Los 3 commits del período son mantenimiento —un renombre, un arreglo por la subida a Python 3.11, un log.debug—. En noviembre, un archivo a medias en impls/ tiró el checkout 40 minutos, porque el descubrimiento importa todo lo que encuentra.

Qué se gana y qué se pierde. Queda seating/seating.py: 34 líneas, 1 salto, 0 conceptos privados del proyecto, y las primeras 4 pruebas de comportamiento que ha tenido este rincón —antes había 4 pruebas del andamiaje y ninguna comprobaba que un asiento se asignara bien—. Lo que se pierde: el checkout pasa a depender directamente de la función de asientos. Es acoplamiento, y es real; me parece preferible a la indirección que teníamos, porque esa indirección no ocultaba nada.

Cómo se hizo. Nueve commits, todos en verde. Pruebas de comportamiento antes de tocar nada. Se cortó el uso en los cuatro usuarios de producción de a uno, empezando por la tarea programada y terminando por el checkout; el panel dejó de ofrecer un desplegable de una sola opción. Después se borró el andamiaje. La columna no se toca en este cambio: se deja de escribir, y se elimina en dos semanas en una ventana aparte con respaldo.

Cuándo lo volvería a poner. Si aparecen dos o tres algoritmos de asignación reales —"mejor asiento por visibilidad", "contiguos para el grupo"—, volvería a abstraer, y bastaría un diccionario de tres entradas, no un registro dinámico. Solo volvería a un mecanismo de carga dinámica si un organizador tuviera que conectar código propio sin que nosotros despleguemos; hoy eso no existe ni está en el roadmap.

La sección que más cuesta es la tercera, y específicamente la parte del "qué se pierde". Por dos razones. La primera es psicológica: cuando has trabajado dos días en un desmontaje, escribir en qué empeora el sistema va en contra de lo que sientes. La segunda es más sutil: exige entender que quitar indirección agrega acoplamiento, es decir, que no eliminaste un costo sino que lo cambiaste por otro. Quien no lo ve escribe la sección como si el cambio no tuviera contrapartida, y quien revisa lo nota de inmediato.

Por qué funciona: acabas de escribir el documento que se entrega en el proyecto final para uno de los dos rincones. Guárdalo. Y fíjate en que la misma fórmula sirvió para las dos direcciones sin cambiar una coma de su estructura: eso es lo que la hace confiable.

Ejercicio 3 — Contesta las tres objeciones, en frío. Tu compañero revisa el refactor de proveedores y escribe estos tres comentarios. Contesta cada uno en un máximo de cuatro líneas, sin ponerte defensivo y sin ceder si no corresponde.

(a) "Antes veía todo el flujo del cobro en un archivo. Ahora tengo que abrir cuatro. ¿No perdimos legibilidad?" (b) "Estas fábricas siempre terminan siendo un if disfrazado. ¿No es lo mismo con más pasos?" (c) "Ya que tocaste payments/, ¿por qué no aprovechaste para agregar el reintento cuando Stripe devuelve 503? Lo pedimos hace meses."

Ver solución

(a) "Sí, y es el costo que acepté: hay un salto más y el flujo ya no se ve completo en un archivo. Lo que se gana a cambio es que ese archivo dejó de tener las particularidades de tres APIs —centavos enteros, float con descripción, referencia sin cobro— mezcladas con la orquestación. Si en el checkout te falta contexto, dime dónde y le pongo un comentario que apunte al contrato."

Reconocer el costo sin retirar la propuesta es la forma correcta. La objeción es cierta —perdiste algo— y negarlo te haría perder la discusión aunque tengas razón en el conjunto.

(b) "Por dentro sí es un mapa, y ese es el punto: el if sigue existiendo pero en un solo lugar en vez de cuatro. La diferencia práctica es que la lista se puede preguntaravailable_providers()— y por eso la conciliación y la validación dejaron de tener su propia copia. Si fueran dos proveedores y un solo consumidor, estaría de acuerdo contigo y no valdría la pena."

Concede la parte técnica, señala la diferencia que sí importa, y nombra el caso en el que la objeción tendría razón. Esa última frase es la que convierte una defensa en una conversación.

(c) "De acuerdo con que hace falta, y lo anoté al final del documento. Va en un cambio aparte por una razón concreta: este promete que el comportamiento observable no cambió, y el reintento lo cambia. Si van juntos y algo falla en producción, no podemos saber cuál de los dos fue, y revertir se lleva las dos cosas. Sobre esta estructura, el reintento son diez líneas en StripeProvider."

Y nota el remate: mostrar que el trabajo nuevo quedó más barato gracias al refactor es el mejor argumento que existe para el refactor mismo. Es "haz que el cambio sea fácil, y después haz el cambio fácil" contado desde el otro extremo.

Por qué funciona: las tres objeciones son razonables y las tres tienen respuesta sin ceder ni pelear. Un equipo donde estas conversaciones se pueden tener así es un equipo que refactoriza; uno donde se viven como ataques es un equipo donde el código se pudre en paz.

Resumen y siguiente paso

En esta lección aprendiste a defender una refactorización, y la tesis central es que el nombre del patrón es la etiqueta, no el argumento. "Apliqué Strategy" no convence a nadie porque no dice qué problema tenías, qué se gana, qué cuesta ni cuándo estarías equivocado.

La fórmula tiene tres partes y ninguna sobra. La unidad de medida antes y después —"tocaba cuatro archivos y ahora toca uno"—; si falta, tu propuesta es estética. El costo aceptado, dicho por ti antes de que te lo digan —"un archivo más y un salto al leer"—; si falta, quien revisa asume que no lo viste. Y la condición de reversión verificable —"si nos quedáramos con un solo proveedor, esto sobra"—; si falta, tienes una regla y no un criterio.

Viste tres versiones del mismo mensaje y por qué la buena no menciona ningún patrón, tiene cada afirmación verificable, incluye la sección de costos —que es la que da credibilidad—, no habla de personas sino de código, y contesta directamente el miedo principal de quien revisa: ¿esto va a romper el cobro?.

Tienes el catálogo de unidades que convencen —archivos por caso nuevo, saltos por pregunta, líneas de andamiaje por línea de trabajo, implementaciones reales, frecuencia de cambio, incidentes atribuibles, tiempo de la última vez, pruebas que antes eran imposibles— y las que no: líneas sueltas, métricas de herramienta citadas solas, apelaciones a "buenas prácticas" y preferencias personales.

Y te llevas la plantilla de cinco secciones —contexto, qué costaba, qué se gana y qué se pierde, cómo se hizo, cuándo se revertiría— más las respuestas a las tres objeciones de siempre: "funcionaba bien" se contesta con el costo ya pagado; "¿no es sobre-ingeniería?" se contesta con el número de implementaciones y el peldaño elegido; y "ya que estás…" se contesta con "sí, después, y esta es la razón del orden".

Antes de avanzar deberías poder: escribir las tres partes de la fórmula para un cambio tuyo; nombrar cuatro unidades que convencen y dos que no; y detectar una condición de reversión vaga con la prueba del "¿podrías contestarme sí o no el lunes?".

Queda la lección más madura del módulo, y probablemente de la guía. Hasta aquí todo el trabajo asumió que había que hacer algo: agregar estructura, quitarla, ejecutarlo bien, defenderlo. La lección 7 se ocupa del caso que casi ninguna guía admite: el código feo que funciona, que nadie toca y que nadie lee no es una prioridad. Refactorizar cuesta tiempo, riesgo y revisión, y ese costo se paga con algo que también hacía falta. Vas a ver las tres preguntas con las que se decide —¿se toca seguido?, ¿bloquea algo?, ¿alguien tiene que entenderlo pronto?—, cómo se contesta cada una con evidencia, y cómo se escribe la nota que deja constancia de que la decisión de no tocar fue una decisión, y no un olvido.

Recursos

  • clean-code-and-code-review-guide — el oficio del review y del PR: cómo estructurar un comentario para que no genere fricción, cómo recibir una crítica y cómo llevar una discusión técnica. Esta lección te da el argumento; aquella guía te da la conversación en la que ese argumento vive.
  • How to Do Code Reviews Like a Human (Michael Lynch) — dos artículos sobre el lado social de la revisión, con especial atención a por qué las propuestas correctas se rechazan por cómo están escritas. Es el complemento directo del ejercicio 3.
  • TechnicalDebt (Martin Fowler) — la metáfora que hace comunicable el costo de no actuar frente a alguien que no lee código. Útil cuando el interlocutor no es técnico y necesitas la sección "qué costaba" en su idioma.
  • Architecture Decision Records (ADR) — el formato estándar para dejar constancia escrita de una decisión de diseño con su contexto, sus alternativas y sus consecuencias. Es la versión formal de la plantilla de cinco secciones, y vale la pena conocerla porque muchos equipos ya la usan.