Módulo 3: Patrones para el comportamiento que varía
7. La trampa: no todo condicional es una Strategy escondida
Descripción
Al terminar esta lección vas a tener la habilidad que hace útil a todo lo anterior: decidir cuáles condicionales merecen extraerse y cuáles no. Hasta aquí el módulo te dio herramientas —Strategy, Template Method, State, y sus versiones ligeras con funciones—. Esta lección te da el filtro. Sin él, lo que aprendiste se convierte en un martillo, y ya sabes qué pasa con los martillos nuevos.
Vas a salir con cuatro cosas. Una tesis que conviene decir de frente: un if de dos ramas que no va a crecer está bien como está, y refactorizarlo empeora el código. Las tres señales reales de que un condicional pide extracción, cada una con una forma concreta de medirla —no de intuirla—. Las señales falsas, que son las que casi todo el mundo usa y que producen la sobre-ingeniería que el módulo 2 te enseñó a temer. Y la lista de lo que un condicional puede necesitar en vez de un patrón, que casi siempre es algo mucho más barato: una guarda, una tabla, un nombre, o nada.
Esto importa porque el error de este módulo es asimétrico. Si dejas un condicional que debía extraerse, el costo es que dentro de un año alguien haga el refactor que tú no hiciste —molesto, pero acotado, y el código mientras tanto funcionó—. Si extraes uno que no debía extraerse, creas una abstracción en el eje equivocado, y esa se hereda: nadie se atreve a quitarla, cada caso nuevo se mete a la fuerza, y en tres años es el rincón que da miedo. Equivocarse hacia el lado de no abstraer es barato; hacia el otro lado, no.
Conexión con el módulo: esta es la vacuna, y cierra el arco que empezó en el módulo 2. La lección 1 te dio el eje de variación y ya te adelantó que de cuatro condicionales en cuarenta líneas solo uno lo era. La lección 2 tuvo una sección entera sobre cuándo el if es mejor. La 6 te enseñó a bajar el peso del mecanismo. Aquí juntamos todo eso en un procedimiento de decisión que vas a aplicar directamente en el proyecto de la lección 8, donde se te va a evaluar tanto por lo que extraigas como por lo que decidas dejar en paz —y por saber explicar las dos cosas—.
La alarma que suena con cada camión
Hay autos cuya alarma se dispara con todo. Pasa un camión y suena. Cierra una puerta el vecino y suena. Llueve fuerte y suena. Al principio la gente del edificio se asoma; a las dos semanas ya nadie mira. La alarma sigue funcionando perfectamente —detecta vibraciones, que es lo que le pidieron— pero dejó de servir para algo, porque un detector que se dispara con todo no distingue nada.
Lo peor de esa alarma no es el ruido. Es que el día que de verdad alguien intente forzar el auto, va a sonar igual que las otras cuarenta veces, y nadie va a mirar. La sensibilidad excesiva no solo genera falsos positivos: destruye la señal.
Un programador que ve un patrón en cada if tiene esa alarma. Y el daño es del mismo tipo: cuando todo condicional le parece una Strategy escondida, pierde la capacidad de reconocer el que sí lo era. En una revisión de código, sus comentarios dejan de tener peso —"otra vez con lo mismo"— y cuando señala el condicional que de verdad estaba pudriéndose, ya nadie mira.
Así que el trabajo de esta lección no es enseñarte a detectar condicionales. Eso lo hace un buscador de texto. El trabajo es calibrar la alarma: que se dispare con lo que importa y se quede callada con lo que no.
Un dato que ayuda a calibrar, aunque sea aproximado: en un código sano, la enorme mayoría de los condicionales no son ejes de variación. Son guardas, validaciones, valores por defecto, atajos de rendimiento, banderas temporales y decisiones binarias de negocio. Los que de verdad piden un patrón son unos pocos por módulo. Si tu instinto marca la mitad, tu alarma está demasiado sensible.
Ejemplo trabajado: cinco condicionales del checkout, uno por uno
Vamos a hacer el trabajo real. Aquí hay cinco condicionales que viven en el checkout de Boletia. Todos son if. Solo dos merecen que hagas algo, y uno de esos dos no merece un patrón. Vamos uno por uno.
Condicional 1.
# Archivo: notifications/notifier.py
def notify_purchase(customer, order):
if customer.email is None:
# Sin correo no hay confirmación posible. No es un error:
# hay clientes de taquilla que compran sin dar correo.
return
send_confirmation_email(customer.email, order)
Veredicto: dejar como está. Esto es una guarda: protege la ejecución de un caso que no se puede procesar. No modela dos formas de hacer algo; modela "aquí no hay nada que hacer". Y no puede crecer: un correo está o no está, no hay una tercera opción.
La prueba mental que conviene hacerse con cualquier condicional: ¿puedo imaginar una tercera rama? Aquí no. Cuando la respuesta es no, no tienes un eje de variación: tienes una decisión binaria, y las decisiones binarias se escriben con if.
Condicional 2.
# Archivo: checkout/checkout.py
if ticket.kind == "general":
...
elif ticket.kind == "vip":
...
elif ticket.kind == "early_bird":
...
elif ticket.kind == "courtesy":
...
Veredicto: extraer —y ya lo hiciste en la lección 3—. Las tres señales están presentes: crece con cada tipo de boleto que inventa el negocio, se repite en otros archivos, y cada rama contiene una implementación completa y no solo una decisión.
Lo incluyo aquí para que tengas el contraste al lado. Fíjate en lo distinto que se ve del condicional 1 cuando los miras juntos.
Condicional 3.
# Archivo: payments/registry.py
def build_provider(provider_name):
if settings.PAYMENT_SANDBOX:
# En ambientes de prueba no cobramos de verdad: todo va al
# proveedor falso, que aprueba siempre. Se quita cuando cerremos
# la integración de MercadoPago (ticket BOL-1180).
return FakeProvider()
return _REAL_PROVIDERS[provider_name]()
Veredicto: dejar como está, y ponerle fecha de caducidad. Es una bandera de entorno, no una regla de negocio. No representa dos formas legítimas de cobrar: representa un desvío temporal mientras se termina una integración.
Aquí hay un matiz que vale la pena. Este condicional no debería envejecer: si dentro de un año sigue ahí, el problema no es que le falte un patrón, es que nadie cerró el ticket. La respuesta correcta a una bandera vieja es borrarla, no abstraerla. Y el comentario con el número de ticket es lo que hace posible saberlo; sin él, en seis meses nadie se atreve a tocarlo por si acaso.
Un principio general que se deriva: la infraestructura de configuración no se modela con patrones de comportamiento. Si algo depende de en qué ambiente corres, eso es configuración, y las abstracciones de configuración son otra familia de problemas.
Condicional 4.
# Archivo: checkout/checkout.py
if order.total == 0:
# Órdenes de puras cortesías: no hay nada que cobrar, se salta
# el proveedor de pago y la orden queda pagada directamente.
order.mark_paid(external_id=None)
else:
result = charge(order)
if result.status == "paid":
order.mark_paid(result.external_id)
Veredicto: dejar el condicional, pero darle un nombre. Este es el caso más interesante de los cinco, porque la respuesta no es ni "extraer un patrón" ni "no hacer nada".
Es una decisión binaria de negocio —o hay que cobrar o no— y no va a tener una tercera rama. No es un eje de variación. Pero la condición no dice lo que significa: order.total == 0 es una comprobación aritmética, y lo que quiere decir es "esta orden no requiere cobro". Quien lo lea dentro de un año tiene que reconstruir esa traducción.
La mejora que sí vale la pena es de una línea:
if order.requires_no_payment: # ← una propiedad: `return self.total == 0`
order.mark_paid(external_id=None)
else:
...
Eso es extraer un nombre, que es el refactor más barato y más rentable que existe, y no es un patrón. Guarda esta distinción porque es de las más útiles del módulo: dar nombre a una condición mejora la lectura sin agregar indirección; extraer un patrón mejora la extensibilidad y sí agrega indirección. La mayoría de los condicionales que te molestan al leerlos necesitan lo primero, no lo segundo.
Condicional 5.
# Archivo: reports/sales_report.py
if order.provider == "stripe":
fee = order.total * 0.036 + 3.0
elif order.provider == "mercadopago":
fee = order.total * 0.0399
elif order.provider == "cash":
fee = 12.0
# Archivo: api/routes.py
if order.provider == "stripe":
return redirect(stripe_checkout_url(order))
elif order.provider == "mercadopago":
return redirect(mp_checkout_url(order))
elif order.provider == "cash":
return render("cash_reference.html", reference=order.reference)
# Archivo: notifications/notifier.py
if order.provider == "cash":
send_payment_instructions(customer, order)
Veredicto: extraer, y es el caso más urgente de los cinco. No por el número de ramas —tres, como el condicional 2 tiene cuatro— sino por dónde están: el mismo condicional, sobre el mismo campo, en tres archivos que no se conocen entre sí.
Agregar un cuarto proveedor obliga a encontrar los tres lugares y acertar en todos. Y no hay nada que te avise si te falta uno: el reporte de ventas simplemente no calcula la comisión del proveedor nuevo, y eso se descubre en la conciliación de fin de mes. Este síntoma tiene nombre —shotgun surgery, cirugía de escopeta— y el módulo 7 lo trata a fondo.
Fíjate en algo importante: el tercer fragmento no tiene el mismo número de ramas. Solo pregunta por efectivo. Eso es normal y es parte del problema: cuando un condicional se dispersa, cada copia evoluciona por su lado y ninguna refleja el conjunto completo.
Aquí sí hay Strategy —o mejor, hay un PaymentProvider que además de charge expone commission_for(order) y checkout_url(order)—. Y fíjate en que la razón 2 de la lección 6 aplica limpiamente: varias operaciones relacionadas sobre el mismo concepto son un objeto, no tres funciones sueltas.
Qué esperar. Este es el resumen del trabajo, y es la forma en que vas a entregar el proyecto de la lección 8:
| # | Condicional | Veredicto | Razón en una línea |
|---|---|---|---|
| 1 | customer.email is None | Dejar | Guarda binaria; no puede tener una tercera rama |
| 2 | ticket.kind == ... | Extraer (Strategy) | Crece, se repite, mezcla decidir con hacer |
| 3 | settings.PAYMENT_SANDBOX | Dejar + borrar cuando cierre BOL-1180 | Bandera de entorno, no regla de negocio |
| 4 | order.total == 0 | Dejar, pero nombrar la condición | Binaria y estable; el problema es de lectura, no de estructura |
| 5 | order.provider == ... (×3 archivos) | Extraer (Strategy con varias operaciones) | Se repite en tres lugares que no se enteran entre sí |
Cinco condicionales, dos extracciones, tres decisiones de no tocar —una de ellas con una mejora de una línea—. Esa proporción es la sana.
Y hay algo más en esa tabla que quiero que veas: la columna de la razón nunca dice "hay un if". Cada veredicto se apoya en una propiedad del condicional —si puede crecer, si se repite, si mezcla decidir con hacer, si es configuración— y no en su apariencia. Cuando puedas llenar esa columna sin repetirte, tienes el criterio.
Las tres señales reales (y las cinco falsas)
Ya usaste las tres señales varias veces en el módulo. Aquí van con la parte que faltaba: cómo medir cada una, porque una señal que solo se intuye es una opinión.
Señal 1: crece. El condicional gana una rama cada vez que el negocio agrega un caso.
Cómo medirla: el historial del repositorio. Busca los cambios que tocaron ese archivo y mira cuántos agregaron una rama al mismo condicional. Si en dos años hubo cuatro, es un eje de variación con evidencia. Si el condicional tiene las mismas tres ramas desde que se escribió, no crece —por más ramas que tenga—.
Esto es lo que la mayoría intuye mal. El número de ramas no es la señal; el ritmo de crecimiento sí. Un condicional de siete ramas escrito de una vez y nunca modificado es una tabla de casos, no un eje. Uno de tres ramas que ganó una cada semestre es un eje.
El matiz honesto: a veces el historial no está —el archivo se movió, el proyecto migró de repositorio—. Entonces pregúntale a alguien que lleve tiempo, o mira si el concepto del que dependen las ramas (ticket.kind, order.provider) aparece en el roadmap. La pregunta de fondo es: ¿el negocio inventa casos nuevos de esto?
Señal 2: se repite. El mismo condicional, sobre el mismo campo, en varios lugares.
Cómo medirla: búscalo. Literalmente: grep -rn "ticket.kind ==" . o el buscador de tu editor. Si el campo se compara en más de dos archivos, tienes la señal. Y de paso tienes el inventario de lo que hay que tocar, que es la mitad del trabajo del refactor.
Esta es la señal más objetiva de las tres y la que más subestima la gente. Un condicional feo pero solo, en un archivo, es un problema local. El mismo condicional repartido en cinco archivos es un problema estructural, porque el sistema perdió la garantía de que todas las copias estén de acuerdo.
Señal 3: mezcla decidir con hacer. Las ramas no eligen un valor: contienen la implementación completa.
Cómo medirla: cuenta las líneas del cuerpo de cada rama y mira qué hay dentro. Si cada rama es una expresión, es una tabla de datos. Si cada rama tiene consultas a base de datos, llamadas a servicios, cálculos encadenados o puede lanzar excepciones propias, entonces la decisión está cargando con la ejecución.
El umbral práctico: más de tres o cuatro líneas por rama, con efectos, empieza a contar. Menos que eso, casi nunca.
La regla de combinación. Una señal sola no alcanza. Dos de tres, sí. Con una sola, espera —la regla de tres del módulo 2 dice lo mismo desde otro ángulo: con información incompleta, elige la opción reversible—.
Y ahora las falsas, que son las que de verdad producen el daño.
Falsa 1: "hay un if". No es una señal de nada. Los condicionales son la forma normal de expresar una decisión, y un programa sin condicionales no existe. Si tu criterio empieza aquí, tienes la alarma del camión.
Falsa 2: "tiene más de N ramas". El número no dice nada por sí solo, como acabamos de ver. Un switch de doce ramas que traduce códigos de país a nombres es una tabla, y la respuesta es un diccionario, no doce clases.
Falsa 3: "el analizador se quejó de la complejidad". Las métricas de complejidad ciclomática son útiles como termómetro y pésimas como sentencia. Miden ramas, no diseño. Un condicional con la complejidad más alta del archivo puede ser una validación de entrada perfectamente sana, y hacerla pasar el umbral repartiéndola en tres funciones no mejoró nada: solo movió el problema para que la herramienta dejara de verlo. Optimizar la métrica en vez de lo que la métrica quería medir es un error clásico y caro.
Falsa 4: "acabo de aprender el patrón". Es la más honesta de admitir y la más común. Si el impulso de refactorizar llegó antes que el problema, el problema no está. Una prueba brutal pero eficaz: ¿alguien te pidió que tocaras ese código? Si no, y no está bloqueando nada, la respuesta suele ser esperar.
Falsa 5: "en el futuro va a crecer". El futuro imaginado es exactamente lo que el módulo 2 llamó abstracción prematura. La pregunta correcta no es si podría crecer —casi todo podría— sino si está creciendo, en pasado y con evidencia. Y si de verdad crece, lo vas a saber cuando llegue el tercer caso, y refactorizar en ese momento cuesta lo mismo que cuesta ahora. Esperar es gratis; adelantarse no.
Lo que un condicional puede necesitar en vez de un patrón
Cuando un condicional te molesta, la pregunta no es "¿qué patrón le pongo?". Es "¿qué le pasa?". Aquí está la escalera completa, de menos a más peso. Baja por ella y quédate en el primer escalón que resuelva tu problema.
Escalón 0: nada. El código feo que nadie lee y nadie toca no es un problema. Es feo. Refactorizarlo gasta tu tiempo y asume el riesgo de romper algo a cambio de una mejora que nadie va a percibir. El módulo 8 tiene una lección con este nombre: cuándo dejar el código feo en paz.
Escalón 1: un nombre. Es el condicional 4 del ejemplo trabajado. La condición es correcta pero no dice lo que significa. Extraerla a una propiedad o a una función con buen nombre mejora la lectura y no agrega indirección, porque el nombre está justo ahí.
if o.status == "paid" and o.paid_at > now() - timedelta(days=30) and not o.refunded_amount:
...
# ↓
if order.is_refundable: # la regla completa, con su nombre
...
Escalón 2: una guarda y salida temprana. Cuando el condicional envuelve el cuerpo de la función y produce una escalera de anidamiento. Sacar los casos que no aplican al principio aplana todo.
def notify(customer, order):
if customer.email is not None:
if order.status == "paid":
if not customer.unsubscribed:
send_email(...)
# ↓
def notify(customer, order):
if customer.email is None:
return
if order.status != "paid":
return
if customer.unsubscribed:
return
send_email(...)
Mismo número de condicionales, la mitad de carga mental. Cambiar la forma de un condicional no es lo mismo que extraerlo, y muchas veces es todo lo que hacía falta.
Escalón 3: una tabla. Cuando el cuerpo de cada rama es un valor y no un comportamiento. Ya lo viste en la lección 2 y en la 5.
STATUS_COLORS = {"paid": "green", "pending": "amber", "cancelled": "red"}
color = STATUS_COLORS.get(order.status, "gray")
Si el condicional es un mapeo, escribe el mapeo. Es más corto, más fácil de revisar de un vistazo, y se puede llegar a mover a configuración si algún día hace falta.
Escalón 4: un tipo mejor. A veces el condicional existe porque el dato está mal modelado. Un status que es texto libre invita a compararlo en todos lados; un Enum centraliza los valores válidos y hace que un error de escritura falle en vez de pasar silenciosamente. Es una mejora enorme por muy poco.
class OrderStatus(str, Enum):
PENDING = "pending"
PAID = "paid"
CANCELLED = "cancelled"
Y su hermano mayor: si dos campos siempre tienen que estar de acuerdo, hazlos uno solo. Es la lección 5 completa.
Escalón 5: la biblioteca estándar o una librería. Antes de inventar una abstracción, revisa si ya está resuelta. Un if debug: print(...) repartido es el módulo logging. Un condicional que elige entre distintas formas de comparar es sorted(key=...). Reinventar cosas resueltas es de las formas más caras de complejidad, porque además nadie más la reconoce.
Escalón 6: un patrón. Recién aquí. Y cuando llegues, aplica la lección 6: elige la forma más ligera que resuelva el problema —una función y un diccionario suelen bastar—.
La estadística aproximada, para calibrar: en el trabajo diario, la mayoría de los condicionales que te incomodan se resuelven en los escalones 0 al 3. El escalón 6 es minoría, y está bien que lo sea.
Errores comunes
Extraer por el número de ramas (de criterio). Qué pasa: alguien pone un umbral —"más de tres elif y lo refactorizo"— y lo aplica sin mirar qué hay dentro. Termina convirtiendo tablas de datos en jerarquías de clases: doce países, doce clases, cada una con un método que devuelve una tasa. Por qué pasa: el número es objetivo y fácil de aplicar, y las señales reales exigen mirar el historial y el contexto, que cuesta más. Cómo detectarlo: mira el cuerpo de las ramas. Si son expresiones, es una tabla. Si el condicional no ha cambiado en dos años, no crece por más ramas que tenga. Cómo corregirlo: reemplaza el umbral por las tres señales medibles —crece (historial), se repite (búsqueda), mezcla decidir con hacer (líneas y efectos)— y exige dos de tres.
Confundir un problema de lectura con uno de estructura (de diagnóstico). Qué pasa: un condicional cuesta trabajo entender, y la reacción es extraer clases. El resultado tiene tres archivos y sigue costando entenderlo, porque lo que fallaba no era la estructura: eran los nombres, el anidamiento o que la condición no decía lo que significaba. Y ahora además hay que saltar entre archivos para leerlo. Por qué pasa: "esto está feo" es una sensación que no distingue causas, y el patrón es la herramienta más impresionante que tienes a mano. Cómo detectarlo: pregúntate qué te costó exactamente. Si fue entender qué hace, es un problema de lectura —escalones 1 y 2—. Si fue saber dónde meter el caso nuevo o encontrar todos los lugares que hay que tocar, ahí sí es estructura. Cómo corregirlo: prueba primero el escalón más barato. Nombrar la condición cuesta un minuto y es reversible; si después del nombre el código ya se entiende, terminaste.
Refactorizar código que nadie te pidió tocar (de proceso). Qué pasa: alguien entra a arreglar un bug de dos líneas, ve un condicional que le disgusta, y sale con un cambio de doscientas líneas en cuatro archivos. La revisión se vuelve lenta porque nadie puede distinguir el arreglo del refactor, el riesgo del cambio se multiplica, y si algo se rompe en producción no se sabe qué lo rompió. Por qué pasa: la intención es buena —dejar el campamento más limpio de lo que lo encontraste es un buen principio— pero se aplica sin límite de tamaño. Cómo detectarlo: si tu cambio toca archivos que el problema original no tocaba, el refactor se desbordó. Cómo corregirlo: separa los commits, siempre. El arreglo va solo y se revisa en dos minutos; el refactor va aparte, con su propia justificación, y puede discutirse sin bloquear el arreglo. Si el refactor es grande, ni siquiera va en el mismo PR: va en un ticket, con su tradeoff escrito. Un refactor que se cuela en otro cambio es un refactor que nadie revisó de verdad.
Ejercicios
Ejercicio 1 — Emite veredicto sobre cinco condicionales. Para cada uno di: extraer o dejar, y la razón en una línea usando las señales de la lección. Si el veredicto es "dejar pero mejorar", di qué escalón aplicarías.
(a) En api/routes.py: if request.method == "POST": ... elif request.method == "GET": ...
(b) En tres archivos distintos: if event.status == "cancelled": ... con lógica diferente en cada uno.
(c) if len(order.ticket_ids) > MAX_TICKETS_PER_ORDER: raise TooManyTickets(...)
(d) if a.kind == "vip" and a.checked_in_at is not None and a.event.starts_at > now(): allow_lounge_access()
(e) Un switch de veinte ramas que traduce el código de error de una pasarela de pago a un mensaje para el usuario.
Ver solución
(a) Dejar. Es despacho de rutas y el framework ya lo resuelve mejor que tú (decoradores por método, o un método por verbo). No es un eje de variación del dominio: es infraestructura. Escalón 5: usa lo que la librería ya trae.
(b) Extraer. Señal 2 en su forma más clara: el mismo estado consultado en tres archivos, con lógica distinta en cada uno, y ninguno se entera de los otros. Es el condicional 5 del ejemplo trabajado con otro campo. Además, "lógica diferente en cada uno" sugiere que también hay señal 3.
(c) Dejar. Es una validación. Una rama, un error, no puede crecer. Si acaso, escalón 1: que el límite tenga nombre —ya lo tiene, MAX_TICKETS_PER_ORDER— y ese nombre es justamente lo que la hace legible.
(d) Dejar, escalón 1. Tres condiciones encadenadas que juntas significan una sola cosa. No es un eje de variación: es una regla de negocio con nombre propio que todavía no lo tiene. if attendee.can_access_lounge: y la regla completa vive dentro de esa propiedad. Cero indirección agregada, muchísima legibilidad ganada.
(e) Dejar, escalón 3. Veinte ramas, y ninguna es comportamiento: son mensajes. Un diccionario ERROR_MESSAGES = {...} con un valor por defecto. Es el ejemplo perfecto de por qué el número de ramas no es señal: veinte ramas y la respuesta correcta es un dato, no un patrón.
Por qué funciona: de cinco condicionales, uno se extrae. Tres se dejan tal cual o casi, y dos de esos mejoran mucho con un cambio de una línea. Si tu instinto marcó tres o cuatro para extraer, ahí está la calibración que la lección quería ajustar.
Ejercicio 2 — Mide las señales de verdad. Toma un condicional de un proyecto real al que tengas acceso —tuyo, del trabajo o de código abierto— con al menos tres ramas. Aplica las tres mediciones y escribe el resultado: (1) cuántas veces creció, mirando el historial; (2) en cuántos archivos aparece el mismo condicional, buscando el campo; (3) cuántas líneas y qué efectos tiene el cuerpo de cada rama. Emite el veredicto con dos de tres.
Ver solución
No hay respuesta única, pero sí una forma reconocible de resultado, y tres cosas que casi siempre pasan.
La primera: la mayoría de la gente descubre que el condicional que le molestaba no crece. Estaba igual desde que se escribió. Eso solo ya cambia el veredicto, y es el hallazgo más frecuente del ejercicio. La molestia era estética.
La segunda: la búsqueda del campo suele traer sorpresas en la otra dirección. Aparece en un archivo que no esperabas —un script de migración, una tarea programada, una prueba que dependía de los mismos valores—. Ese inventario es valioso incluso si al final no refactorizas: ahora sabes qué se rompe si alguien cambia el conjunto de valores válidos.
La tercera: medir el cuerpo de las ramas casi siempre revela que no son homogéneas. Dos ramas de una línea y una de treinta. Cuando pasa eso, la extracción "de todo el condicional" suele ser el movimiento equivocado; lo que pide salir es la rama gorda, y las otras dos se quedan donde están. Ese resultado —extraer una parte y no todo— es más común de lo que los libros sugieren, y es una respuesta perfectamente legítima.
Los comandos, por si te sirven:
# ¿Cuántas veces se tocó el archivo, y por qué?
git log --oneline -- ruta/al/archivo.py
# ¿Dónde más se compara este campo?
grep -rn "ticket.kind ==" --include=*.py .
Por qué funciona: la diferencia entre una opinión y un diagnóstico es que el diagnóstico se apoya en algo que se puede enseñar. Cuando en una revisión digas "este condicional creció cuatro veces en dos años y el campo se compara en cinco archivos", la conversación deja de ser sobre gustos.
Ejercicio 3 — Escribe la justificación de un "no". Un compañero abre un PR que convierte este condicional en tres clases con su interfaz. Escribe el comentario de revisión que argumenta por qué no conviene. Máximo seis líneas, con el tono de alguien que quiere colaborar y no ganar una discusión.
def shipping_cost(order):
if order.delivery == "pickup":
return 0.0
elif order.delivery == "standard":
return 89.0
else:
return 189.0 # express
Ver solución
Una respuesta que funciona:
Gracias por el detalle del PR, la idea se entiende bien. Yo aquí me inclinaría por dejarlo como está, y te cuento por qué: las tres ramas devuelven una constante, así que lo que tenemos no es comportamiento variable sino una tabla —
SHIPPING_COSTS = {"pickup": 0.0, "standard": 89.0, "express": 189.0}haría lo mismo en una línea—. Ademásorder.deliverysolo se compara aquí, y en el historial esta función no ha cambiado desde que se escribió, así que tampoco tenemos la señal de que crezca. Si en algún momento el costo pasa a depender de la distancia o del peso, ahí sí el cálculo deja de ser una constante y volvemos a esta conversación con un caso concreto. ¿Te parece si de mientras lo dejamos en el diccionario?
Fíjate en cinco cosas de ese comentario, porque son las que lo hacen efectivo.
Reconoce el trabajo del otro antes de discrepar. No cuesta nada y cambia por completo cómo se recibe.
Da evidencia, no opinión: las ramas son constantes, el campo no se compara en otro lado, el historial no muestra crecimiento. Tres hechos verificables. "Me parece sobre-ingeniería" no es un argumento; es una etiqueta.
Ofrece una alternativa concreta en vez de solo bloquear. El diccionario es más corto que el original y que la propuesta, así que el PR no se va con las manos vacías.
Nombra la condición futura que cambiaría el veredicto. Eso convierte un "no" en un "todavía no", que es lo que de verdad es, y deja al otro con algo accionable.
Y termina con una pregunta. Una revisión es una conversación, no una sentencia —el módulo 7 tiene una lección entera con ese título—.
Por qué funciona: decir "no" bien es una habilidad de ingeniería, no de diplomacia. En tu carrera vas a evitar más daño con los patrones que convenzas de no meter que con los que metas, y para eso hace falta poder explicarlo sin que suene a que le estás corrigiendo la tarea a alguien.
Resumen y siguiente paso
En esta lección calibraste la alarma. Viste con la imagen del auto que se dispara con cada camión que un detector que se activa con todo destruye la señal: quien ve una Strategy en cada if pierde la capacidad de reconocer el condicional que de verdad estaba pudriéndose, y pierde también el peso de su palabra en una revisión.
Hiciste el trabajo real sobre cinco condicionales del checkout de Boletia y emitiste veredicto uno por uno: la guarda que no puede crecer, el eje de variación que ya extrajiste, la bandera de entorno que hay que borrar y no abstraer, la decisión binaria que solo necesitaba un nombre, y el condicional repartido en tres archivos que es el caso más urgente aunque no sea el que más ramas tiene. Dos extracciones de cinco, y la columna de razones sin repetirse nunca.
Te llevas las tres señales reales, con su forma de medirlas: crece (mira el historial), se repite (busca el campo), mezcla decidir con hacer (cuenta líneas y efectos). Y la regla de combinación: dos de tres. Con una sola, espera.
Y te llevas las cinco falsas, que son las que producen el daño: "hay un if", "tiene más de N ramas", "el analizador se quejó", "acabo de aprender el patrón" y "en el futuro va a crecer". Con el argumento que las desarma a todas: el error es asimétrico —dejar un condicional que debía salir cuesta un refactor tardío; extraer uno que no debía salir crea una abstracción en el eje equivocado que nadie se atreve a quitar—.
Y tienes la escalera de lo que un condicional puede necesitar antes que un patrón: nada → un nombre → una guarda con salida temprana → una tabla → un tipo mejor → la librería estándar → un patrón. Baja por ella y quédate en el primer escalón que resuelva tu problema. La mayoría de los casos se detienen antes del cuarto.
Antes de avanzar deberías poder: emitir veredicto sobre un condicional con una razón que no sea "hay un if"; medir las tres señales con comandos concretos en vez de intuirlas; y escribir el comentario de revisión que argumenta un "todavía no" con evidencia y una alternativa.
Ya tienes todo lo del módulo: los tres patrones, sus versiones ligeras, y el filtro para decidir cuándo aplicarlos. La lección 8 te entrega el checkout completo de Boletia —con sus condicionales de todos los tipos que viste aquí— y te pide hacer el trabajo entero: inventariar, juzgar, extraer los que lo merezcan con el patrón y el peso adecuados, y entregar el código más la justificación de cada decisión, incluidas las de no tocar nada. Se evalúa por el criterio, no por cuántos patrones metiste.
Recursos
- The Grug Brained Developer — el ensayo sobre la complejidad como enemigo real. Es esta lección contada como comedia, y vale releerlo justo al terminar un módulo de catálogo.
- Martin Fowler — Decompose Conditional — la ficha del escalón 1: extraer la condición a un nombre. El refactor más barato y más rentable del catálogo.
- Martin Fowler — Yagni — el artículo que desarma la señal falsa número cinco, con el análisis del costo de construir para un futuro imaginado.
- Sandi Metz — The Wrong Abstraction — el ensayo corto que explica por qué el error hacia el lado de abstraer es más caro que el otro. Si lees uno solo de esta lista, que sea este.