Módulo 7: Patrones como vocabulario de revisión

7. El patrón como conversación, no como sentencia

Descripción

Al terminar esta lección vas a saber usar el vocabulario que construiste en los cinco capítulos anteriores sin que se convierta en un arma. Vas a tener tres cosas concretas: la técnica para ofrecer un diagnóstico en forma de pregunta cuando no estás seguro —y para notar cuándo no lo estás—, la técnica para recibir un diagnóstico sin defenderte, y un inventario claro del daño específico que hace el vocabulario mal usado, que es mayor del que hace no tenerlo.

Esta es la lección que cierra el módulo antes del proyecto, y es la que más me interesa que te lleves. La razón es incómoda y prefiero decirla de frente: este módulo te dio un arma. Alguien que sale de aquí con doce nombres y sin criterio para usarlos es peor compañero de equipo que alguien que nunca oyó la palabra "God object". No porque los nombres sean malos, sino porque un nombre técnico dicho con autoridad cierra conversaciones, y una conversación cerrada es una decisión que se tomó sin la información que tenía la otra persona.

Y hay un punto que quiero establecer desde el principio porque ordena todo lo demás. Nombrar un patrón o un olor es formular una hipótesis, no emitir un veredicto. Tú miraste el código; el autor lo escribió, habló con producto, se enteró de un incidente de hace seis meses y negoció un plazo. Sabe cosas que tú no. El objetivo de nombrar no es tener razón: es entender juntos, y con más frecuencia de la que uno espera, el resultado correcto de una buena conversación de revisión es que el revisor cambie de opinión.

Conexión con el módulo: las lecciones 2, 3 y 5 te dieron las palabras; la 4, la forma de escribirlas; la 6, el camino que proponen. Esta se ocupa de lo único que ninguna de ellas resuelve: que del otro lado hay una persona que va a leer eso un martes por la tarde. Y marca la frontera final con clean-code-and-code-review-guide: aquí cubrimos exclusivamente los riesgos que nacen de tener vocabulario; el proceso del review —cuándo aprobar, cómo escalar, cómo se acuerda una convención— vive allá. La lección 8 pone todo junto sobre un pull request real.

Dos formas de decirle a alguien que tiene espinaca en los dientes

Estás en una comida de trabajo. La persona frente a ti tiene un pedazo de espinaca entre los dientes. Hay que decírselo — no decírselo es peor.

Primera forma: esperas a que no haya nadie mirando, te tocas el diente con el dedo y haces un gesto mínimo. Ella entiende, se limpia, tú sigues hablando de otra cosa como si nada. Cero costo, problema resuelto.

Segunda forma: "perdón, tienes espinaca en los dientes", en voz normal, en la mesa. También funciona. Pero fíjate en lo que agregaste sin querer: ahora hay tres personas involucradas en algo que era entre dos, y ella tiene que gestionar no solo la espinaca sino que los demás la vieron.

Y una tercera, que también existe: "llevas toda la comida con espinaca en los dientes, ¿nadie te dijo?". Misma información. Ahora hay una acusación implícita —a ella por no notarlo, a los demás por callarse— y el asunto duró treinta segundos más de lo necesario.

Las tres transmiten el mismo dato. Lo que cambia es cuánto cuesta recibirlo. Y ese costo no es amabilidad decorativa: determina si la persona va a querer comer contigo otra vez, es decir, si va a seguir pidiéndote que revises su código.

La analogía tiene un límite que conviene marcar, porque si no se lee como "sé más suave". No es eso. La espinaca no admite discusión: está o no está. Un diagnóstico de diseño sí admite discusión, y ahí aparece la diferencia real entre las dos disciplinas. Con la espinaca, tu única decisión es cómo decirlo. Con el código, además tienes que dejar abierta la posibilidad de que estés equivocado — y la forma en que lo dices es lo que abre o cierra esa puerta.

El nombre es una hipótesis

Hay una asimetría estructural en toda revisión de código, y casi todos los problemas de esta lección salen de ignorarla.

Tú viste el código. El autor vivió el problema. Él estuvo en la conversación donde producto dijo que esto tenía que salir antes del festival. Él sabe que el proveedor de pago cambia su API en marzo y por eso no valía la pena abstraerla ahora. Él intentó la solución elegante primero y descubrió que rompía el flujo de reembolsos. Nada de eso está en el diff.

Un diagnóstico tuyo, por bien fundamentado que esté, se apoya en información parcial. Eso no lo invalida —tu ángulo también ve cosas que él no ve, precisamente por no haber estado adentro—, pero sí determina la forma correcta de decirlo:

"Esto es un God object""Esto me parece un God object; ¿hay una razón para que el cobro viva aquí?"

La segunda no es más débil. Es más precisa, porque describe correctamente tu estado epistémico: tú crees que es un God object, con evidencia, y no has descartado que haya un motivo que desconoces. Decirlo tal cual es honestidad, no timidez.

Y hay un beneficio práctico que se nota rápido: la versión en pregunta produce mejor información. Ante una sentencia, la respuesta natural del autor es defenderse o ceder — y las dos son malas, porque en ninguna de las dos te cuenta lo que sabe. Ante una pregunta, la respuesta natural es explicar. Y esa explicación es exactamente lo que te faltaba.

Ahora el matiz importante, para que esto no se convierta en un tic. No todo va en pregunta. Si el código tiene un bug, no preguntes si es un bug. Si el proveedor nuevo devuelve un dict donde el resto del sistema espera un ChargeResult, eso no es una hipótesis: es un hecho verificable, y ponerlo en pregunta —"¿no sería mejor devolver un ChargeResult?"— es peor, porque disfraza de opinión algo que no lo es y deja al autor sin saber si tiene que cambiarlo.

La regla que separa los dos casos:

SituaciónForma correcta
Un hecho verificable (bug, inconsistencia, contrato roto)Afirmación, con la evidencia
Un juicio de diseño (esto pide otra estructura)Hipótesis o pregunta, con la evidencia
No sabes si es lo uno o lo otroPregunta genuina, y averigua

Y una corrección de rumbo sobre la lección 4, porque las dos cosas conviven: la lección 4 te pidió evidencia contable. Esta te pide humildad epistémica. No se contradicen. La evidencia es sobre el síntoma; la humildad es sobre la interpretación. "Este archivo apareció en trece de los últimos doscientos commits" es un hecho y se afirma sin rodeos. "Y creo que eso es porque hace cuatro cosas distintas" es una interpretación y se ofrece.

Cuatro formas de ofrecer un diagnóstico

Cuatro registros, del más abierto al más firme. Elegir el correcto es la mitad del oficio.

La pregunta genuina

Se usa cuando de verdad no sabes. No es una técnica retórica: es una pregunta.

"¿ReconciliationReport tiene alguna razón para saber cómo se arma el correo de finanzas? Pregunto porque el método usa nueve atributos de Order y ninguno propio, y no sé si hay un motivo que no estoy viendo."

Fíjate en las dos partes: la pregunta, y la evidencia que la motiva. Sin la evidencia, la pregunta suena a examen —"¿tiene alguna razón?" a secas se lee como "no la tiene"—. Con la evidencia, queda claro que preguntas porque observaste algo, no porque quieras que él llegue solo a tu conclusión.

Y una advertencia sobre el mal uso, que es el más común de los cuatro: la pregunta socrática es la forma más irritante de dar feedback. "¿Qué crees que pasaría si agregáramos un quinto proveedor?" cuando tú ya sabes la respuesta y quieres que él la diga no es una pregunta: es un examen disfrazado, y todo el mundo lo detecta. Si sabes la respuesta, dila.

La observación con pregunta

El registro que más vas a usar. Afirmas el hecho, ofreces la interpretación, dejas la puerta abierta.

"Contando los usos, ticket.kind se consulta en pricing, refunds y transfer, y los tres deciden distinto. Eso me hace pensar que el comportamiento por tipo debería vivir junto — pero puede que haya una razón para tenerlo separado. ¿Qué opinas?"

El hecho va en indicativo. La interpretación va en "me hace pensar". La puerta queda abierta con una pregunta real. Es la forma que mejor equilibra firmeza y apertura, y es la que la lección 4 recomienda por defecto.

La propuesta con salida

Cuando tienes una dirección concreta y quieres que sea fácil decir que no.

"Se me ocurre mover la decisión a un registro en payments/ — sería un diccionario y dos funciones, unas quince líneas. Si prefieres dejarlo para otro PR, con abrir un ticket me quedo tranquilo."

La segunda frase es la que hace el trabajo. Sin ella, toda propuesta se lee como condición. Con ella, el autor puede aceptar o declinar sin que declinar tenga costo social. Y hay un efecto secundario que conviene conocer: la gente acepta más propuestas cuando puede rechazarlas sin costo. Cuando una sugerencia viene con salida, se evalúa por sus méritos; cuando viene sin salida, se evalúa por lo que significa aceptarla.

La afirmación directa

Para hechos. Y también para cuando el riesgo es alto y ablandarlo sería deshonesto.

"Esto rompe el contrato de PaymentProvider: charge() devuelve el dict crudo del SDK y el checkout hace if not result.ok, que sobre un diccionario no vacío evalúa a verdadero. Una compra fallida se va a registrar como pagada. Bloqueante."

Aquí ablandar sería un error. "¿No convendría devolver un ChargeResult?" sobre un bug que cobra mal es un fallo de comunicación, no cortesía. La suavidad no aplica a los hechos; aplica a las interpretaciones.

Un principio que resume los cuatro registros: sé duro con el problema y suave con la persona. Las dos cosas a la vez, no una en lugar de la otra. Bajarle el volumen a un problema real para no incomodar a nadie es una forma de deslealtad con el equipo.

Ejemplo trabajado: el mismo hilo, en dos versiones

Un caso real de Boletia, con el mismo hallazgo y el mismo revisor. Un compañero agregó un cuarto proveedor de pago y, de paso, registró en SeatingPluginRegistry un plugin que no asigna asientos: lo usa para mandar un evento a analytics después de cobrar.

Versión que cierra la puerta.

Revisor: "Esto es speculative generality mal reutilizada. El registro de plugins ya era un anti-patrón y ahora lo estás empeorando. No se puede meter cualquier cosa en un registro solo porque existe."

Autor: "Lo hice así justamente para no acoplar el checkout. Si lo pongo directo, el checkout tiene que saber de analytics."

Revisor: "Ese no es el problema. El problema es que SeatingPlugin es una interfaz de asientos y tu clase no asigna asientos. Es una violación básica de sustitución."

Autor: "Bueno, si prefieres lo cambio."

(El autor lo cambia. Nadie descubrió que el plugin no se estaba ejecutando. El evento de analytics sigue sin mandarse, ahora desde otro lugar.)

Versión que abre una conversación.

Revisor: "Veo que el plugin se registra en SeatingPluginRegistry. Fui a ver cómo lo consume el checkout y encontré esto en checkout.py:31: plugin = SeatingPluginRegistry.get(settings.SEATING_PLUGIN) — o sea que solo se invoca el plugin configurado, que hoy es default_seating. Si no me equivoco, tu clase está registrada pero nunca se ejecuta, así que el evento de analytics no se está mandando. ¿Lo verificaste corriendo el checkout?"

Autor: "No lo verifiqué… acabo de poner un print y tienes razón, no entra. Pensé que el registro invocaba a todos."

Revisor: "Es un nombre confuso, a mí me pasó lo mismo la primera vez. Y hay algo que no podías saber: ese registro se puso hace dos años cuando el plan era vender la plataforma, nunca tuvo una segunda implementación y hay ticket abierto para quitarlo. Para lo que necesitas, ¿te sirve una línea al lado del analytics.track("order_paid", ...) que ya está en el bloque 4?"

Autor: "Sí, y de hecho es más fácil de encontrar ahí. Mi duda era acoplar el checkout a analytics, pero ya está acoplado dos líneas más arriba."

Revisor: "Exacto. Y si en un mes hay cinco cosas que quieren engancharse al final de una compra, ahí sí armamos un mecanismo pensado para eso — un Observer de verdad, con los casos sobre la mesa. Hoy no los tenemos."

Qué esperar de esta comparación. Lo primero: el diagnóstico técnico es idéntico en las dos versiones, y en las dos el autor termina cambiando el código. Si midieras solo por el diff resultante, las dos revisiones son la misma.

Lo segundo, que es donde está toda la diferencia: la primera versión no descubrió el bug. El revisor tenía razón en su categoría —speculative generality reutilizada— y esa razón lo llevó directo a la conclusión, saltándose la investigación. La segunda versión empezó por ir a ver quién consumía el registro, y ahí apareció el hallazgo que de verdad importaba: la funcionalidad no funcionaba. Nombrar bien y rápido puede costarte lo que ibas a encontrar leyendo.

Lo tercero: fíjate en quién habla más. En la primera, el revisor. En la segunda, el autor aporta dos datos que el revisor no tenía —por qué lo hizo así, y que creía que el registro invocaba a todos—. Ese segundo dato es información sobre el sistema, no sobre el autor: el nombre del registro engaña, y eso vale la pena anotarlo en un ticket.

Y lo cuarto, que se nota en el tono sin que nadie lo declare: en la segunda versión el revisor dice "si no me equivoco", "a mí me pasó lo mismo", "algo que no podías saber". Ninguna de las tres frases suaviza un solo hecho. Lo que hacen es dejar claro que la conversación es sobre el sistema y no sobre quién sabe más — y esa es exactamente la condición para que el autor se atreva a decir "acabo de poner un print y tienes razón" en vez de "bueno, si prefieres lo cambio".

Cómo se recibe un diagnóstico

La mitad del oficio está de este lado, y casi nunca se enseña. Vas a recibir muchos más comentarios de los que escribas.

El punto de partida: el código no eres tú. Suena a cliché y es una habilidad que se entrena. La señal de que todavía no está entrenada es física: sientes calor en la cara al leer un comentario. Cuando eso pase —y va a pasar—, la técnica que funciona es simple y aburrida: no respondas en ese momento. Levántate, haz otra cosa veinte minutos, vuelve. El comentario va a decir algo distinto de lo que dijo la primera vez. No porque cambie: porque lo vas a leer buscando información en vez de buscando juicio.

Ayuda mucho tener presente el dato estructural: el autor de un comentario casi nunca está evaluándote. Está mirando un pedazo de código durante quince minutos y escribiendo lo que ve. Tú tienes contexto de tres semanas sobre ese código; él tiene quince minutos. La asimetría, esta vez, juega a tu favor.

Las tres respuestas que sirven

"Tienes razón, lo cambio." Cuando el comentario es correcto. Y algo que sorprende a mucha gente: esta respuesta sube tu reputación técnica, no la baja. En un equipo sano, quien acepta un buen argumento rápido se lee como alguien seguro, no como alguien inseguro. Quien discute todo se lee al revés.

"Lo pensé y elegí X por Y." Cuando tienes una razón que el revisor no podía saber. Esta respuesta es información valiosa y hay que darla completa:

"Sí, es shotgun surgery y lo vi. Lo dejé así a propósito: el contrato con Klarpay está a prueba tres meses y hay una probabilidad real de que lo quitemos en abril. Meterlo en el registro y sacarlo después me pareció más trabajo que dejarlo aislado y visible. Si en abril se queda, hago el refactor con los cinco proveedores de una vez."

Fíjate en tres cosas. Reconoce el diagnóstico —no lo niega—. Da la información que faltaba. Y ofrece un compromiso futuro concreto. Después de una respuesta así, cualquier revisor razonable aprueba, y además aprendió algo del contexto del negocio.

"No entiendo, ¿me muestras?" La más infravalorada. Si el comentario menciona un término que no conoces o una estructura que no ves, preguntar es infinitamente mejor que fingir. Y el que preguntes le enseña al revisor algo útil sobre cómo escribir para el equipo:

"No termino de ver el feature envy aquí, ¿me señalas qué accesos estás contando? Quiero entenderlo bien porque creo que el mismo patrón está en otros dos archivos que escribí."

Nadie ha perdido credibilidad preguntando eso. Mucha gente la ha perdido asintiendo y no cambiando nada.

Las respuestas que no sirven

"Así lo hacemos aquí." Puede ser cierto y aun así no es un argumento; es una apelación a la costumbre. Si de verdad es una convención del equipo, dilo con su razón: "lo hacemos así porque el año pasado intentamos lo otro y nos costó X". Si no hay razón, quizá la convención merecía la pregunta.

"Es que no hay tiempo." También puede ser cierto, y como respuesta única deja el problema sin registrar. La versión útil: "tienes razón y no lo alcanzo en este PR; abro ticket y lo pongo en el sprint que viene". Reconoce, registra, compromete.

El silencio con "resuelto". Marcar un comentario como resuelto sin cambiar nada ni responder es, con diferencia, lo que más rápido hace que alguien deje de revisar tu código con cuidado. Es entendible —a veces uno no sabe qué contestar—, y el costo es alto: la próxima vez esa persona va a mirar por encima, porque aprendió que escribir con detalle no rinde.

La defensa preventiva en el PR. Poner en la descripción "ya sé que esto no es lo ideal pero no había tiempo" parece transparencia y funciona como escudo: desactiva la conversación antes de que empiece. Si de verdad hay una restricción, dila como información —"esto sale antes del festival; el registro de proveedores queda para abril, ticket #412"— no como disculpa anticipada.

El daño específico del vocabulario mal usado

Aquí está el núcleo de la lección. Hay cinco formas de hacer daño con este vocabulario, y todas son fáciles de cometer sin mala intención.

Uno: el nombre como sustituto del argumento

Ya apareció en la lección 4 y vuelve aquí por su cara humana. "Esto viola SRP", "esto es un anti-patrón", "esto es un God object" — y nada más.

El problema técnico es que no dice qué va a doler. El problema humano es peor: una etiqueta pone al otro en posición de tener que refutarla, y refutar una etiqueta es imposible, porque no hay nada concreto que refutar. La conversación se convierte en una discusión sobre si el término aplica, que es una discusión sobre definiciones y no sobre el sistema.

Lo que en cambio sí se puede discutir: "agregar un proveedor toca cinco archivos y uno de ellos falla en silencio". El autor puede responder "ese quinto ya no aplica porque quitamos el reporte", y ahí la conversación avanzó.

Dos: el nombre equivocado dicho con confianza

Este es el más caro y el más silencioso. Dices "esto pide un Factory" cuando pide un Builder, o "esto es Template Method" cuando es Strategy. Si el otro conoce menos el vocabulario que tú, te va a creer, y va a construir la estructura equivocada — con esfuerzo, con cuidado, en la dirección incorrecta.

El módulo 1 ya lo advertía y aquí tiene su versión agravada: el daño escala con tu antigüedad. Un nombre equivocado dicho por alguien nuevo se discute; dicho por la persona más senior del equipo, se ejecuta. Si estás en esa posición, el costo de equivocarte de nombre es mucho mayor, y por lo tanto el umbral para usarlo debería ser más alto, no más bajo.

El antídoto es el de la lección 4 y cuesta seis palabras: si dudas del nombre, describe la estructura. "Esto decide qué objeto construir según un campo; me suena a que la decisión debería estar en un solo lugar, aunque no estoy seguro de si eso es Factory o algo más simple." Nadie va a pensar menos de ti por esa frase. Y va a llegar a la solución correcta.

Tres: etiquetar por etiquetar

El fenómeno que en la industria se llama, sin cariño, pattern police: alguien que recorre los PRs repartiendo nombres como multas de tránsito. Ocho comentarios, ocho etiquetas, cero direcciones.

Lo que hace daño no es cada comentario aislado: es el efecto acumulado sobre el equipo. Después de tres PRs así, pasan tres cosas, en este orden. La gente empieza a evitar que esa persona sea la revisora. Los autores empiezan a escribir código defensivo —a veces peor código, con abstracciones puestas para evitar el comentario previsible—. Y lo más grave: los comentarios buenos de esa misma persona empiezan a descontarse, porque el equipo aprendió que la mitad son ruido.

La señal de alarma para uno mismo es cuantitativa y vale la pena mirarla: si tus comentarios crecieron en número y el código no está mejorando, estás etiquetando.

Cuatro: el diagnóstico como demostración

Este es sutil y muy común en gente que acaba de aprender el vocabulario, incluido yo en su momento. El comentario es técnicamente correcto, la dirección es buena, y aun así el efecto es malo, porque su función real no era ayudar al autor: era demostrar que quien lo escribe sabe.

Se reconoce por señales de forma. Nombra más patrones de los necesarios. Cita principios con sus siglas. Menciona el libro. Es más largo de lo que el problema amerita. Y sobre todo: el autor no puede hacer nada con él, porque el destinatario real no era él sino la audiencia del PR.

La prueba honesta, y hay que hacérsela en privado: "¿escribiría este comentario igual si nadie más pudiera verlo?". Si la respuesta es que sería más corto, ahí está.

Cinco: la asimetría de poder ignorada

El mismo comentario pesa distinto según quién lo escriba. "Esto es un God object" de un compañero de tu nivel es una opinión que se discute. De la persona que decide tu promoción, es una instrucción — aunque termine en signo de interrogación.

Si revisas hacia abajo en la jerarquía, tres consecuencias prácticas:

Tus preguntas no son preguntas. "¿No convendría extraer esto?" de un tech lead se lee como "extrae esto". Si de verdad quieres dejar la opción abierta, hay que decirlo explícito: "esto es genuinamente tu decisión, cualquiera de las dos me parece bien".

Tu volumen está amplificado. Ocho comentarios tuyos se sienten como veinte. Prioriza más de lo que crees necesario: tres comentarios buenos rinden más que ocho correctos.

El peso importa el doble. Sin la etiqueta de "no bloqueante", todo lo que digas se va a tratar como bloqueante.

Y si revisas hacia arriba, dos cosas. La primera: hazlo. Un equipo donde nadie revisa el código de la persona senior tiene un problema. La segunda: apóyate más en evidencia y menos en juicio. "Conté cinco archivos" funciona en cualquier dirección de la jerarquía; "esto me parece mal diseñado" solo funciona hacia abajo, y por razones que no tienen que ver con la técnica.

Cuándo dejar de escribir y empezar a hablar

Un dato de oficio que ahorra días: si un hilo de comentarios llega al tercer intercambio, el problema no es el contenido, es el medio.

El texto asíncrono es excelente para señalar cosas y pésimo para resolver desacuerdos de diseño. No tiene tono, tiene mucha latencia, y cada respuesta se escribe con más cuidado que la anterior —lo que se lee, paradójicamente, como más rigidez—. Un desacuerdo de diseño que en una llamada de diez minutos se resuelve, en un hilo de comentarios puede consumir tres días y terminar con las dos personas incómodas.

La regla práctica: al tercer intercambio, "¿tienes diez minutos para verlo juntos?". Y después de la llamada, escribe el resumen en el PR: qué se decidió y por qué. No es burocracia; es lo que hace que dentro de un año alguien entienda la decisión, y lo que impide que el acuerdo se evapore.

Esto se toca con la guía de code review y por eso lo dejo aquí en su versión mínima: lo que es de esta guía es reconocer qué tipo de desacuerdo estás teniendo. Un desacuerdo sobre un hecho —cuántos archivos toca un cambio— se resuelve contando, en el hilo, en dos mensajes. Un desacuerdo sobre un juicio de diseño —si vale la pena la indirección— no se resuelve contando, y por eso no cabe en un hilo. La mecánica de cuándo escalar, a quién y cómo se documenta el acuerdo es de clean-code-and-code-review-guide.

Errores comunes

Convertir todo en pregunta hasta que nadie entiende qué quieres (de estilo). Qué pasa: alguien aprende que las preguntas funcionan mejor y empieza a escribir todo así, incluidos los bugs. "¿Sería posible que aquí devolviéramos un ChargeResult?" sobre un error que registra compras fallidas como pagadas. El autor, razonablemente, lo lee como una preferencia estética, lo deja para después, y el bug entra a producción. Por qué pasa: la pregunta se siente segura socialmente, y en un equipo donde disentir cuesta, uno aprende a envolverlo todo. Cómo detectarlo: revisa tus últimos diez comentarios y cuenta cuántos terminan en signo de interrogación. Si son más de siete, estás envolviendo hechos. Cómo corregirlo: la tabla de esta lección. Hechos en afirmación, juicios en hipótesis. Y para los hechos con riesgo, la afirmación además de directa tiene que ser explícita en la consecuencia — es la única forma de que el autor entienda por qué no puede posponerlo.

Aceptar todo para evitar la fricción (de recepción). Qué pasa: alguien recibe un comentario, no está de acuerdo, y lo implementa igual porque discutir cuesta. El código termina con una estructura que su autor no defiende y que nadie va a mantener con convicción; y peor, la información que esa persona tenía —el plazo, el contrato a prueba, el intento anterior que falló— nunca llegó al equipo. Por qué pasa: ceder es más barato a corto plazo, sobre todo cuando el comentario viene de arriba. Cómo detectarlo: si al implementar un cambio pedido piensas "esto no me parece pero bueno", ahí está. Cómo corregirlo: la respuesta "lo pensé y elegí X por Y" existe justamente para esto y no es confrontativa; es información. Y si el revisor insiste con un argumento mejor, cambiar de opinión ahí sí es lo correcto — la diferencia entre ceder y ser convencido es si hubo un argumento nuevo.

Creer que suavizar el tono es lo mismo que suavizar el problema (conceptual). Qué pasa: alguien entiende "sé amable" como "no seas categórico", y empieza a matizar los hallazgos serios: un bug se convierte en "quizá valdría la pena revisar", una inconsistencia en "no sé si es intencional". El equipo pierde la señal de qué es grave. Y hay una consecuencia peor: cuando haya que decir algo con firmeza, esa persona ya no tiene el registro disponible, porque lleva meses usando el mismo volumen para todo. Por qué pasa: se confunde el eje "duro/suave con la persona" con el eje "firme/tibio con el problema", que son independientes. Cómo detectarlo: si tus comentarios sobre un bug y sobre un detalle de estilo suenan igual, colapsaste los dos ejes. Cómo corregirlo: el principio de esta lección —duro con el problema, suave con la persona— más el peso de la lección 4, que es el mecanismo concreto que permite las dos cosas. "Bloqueante" dicho con calidez sigue siendo bloqueante.

Ejercicios

Ejercicio 1 — Elige el registro. Para cada uno de estos cinco hallazgos sobre el mismo PR, elige el registro (pregunta genuina, observación con pregunta, propuesta con salida, afirmación directa) y escribe el comentario.

(a) El proveedor nuevo devuelve el dict del SDK en vez de un ChargeResult; el checkout hace if not result.ok. (b) La clase nueva tiene un método que usa siete atributos de Order y ninguno propio. (c) El timeout del proveedor nuevo es de 30 segundos y el de los otros de 10. (d) El archivo nuevo repite el esqueleto de los tres exportadores. (e) La prueba nueva hace Settings._instance = None para poder configurar el entorno.

Ver solución

(a) Afirmación directa. Es un hecho verificable con consecuencia grave.

Bloqueante — payments/klarpay_provider.py:31. charge() devuelve el dict del SDK, pero checkout.py:52 hace if not result.ok. Sobre un diccionario, .ok no existe: va a lanzar AttributeError en el mejor caso, y si alguien lo cambia a result["ok"] sin revisar, un cobro fallido con un dict no vacío se va a registrar como pagado. Los otros tres proveedores devuelven ChargeResult; este tiene que hacer lo mismo.

(b) Observación con pregunta. Hecho contable, interpretación ofrecida.

Sugerencia — reports/reconciliation.py:88. notify_finance usa siete atributos de Order y ninguno de la propia clase. Eso me hace pensar que el armado del resumen pertenece a Order y que aquí solo debería quedar el envío del correo — pero puede que haya una razón para tenerlo junto. ¿Qué opinas?

(c) Pregunta genuina. De verdad no sabes.

Pregunta — payments/klarpay_provider.py:12. ¿30 segundos es a propósito? Los otros tres están en 10. Si el SDK de Klarpay de verdad es más lento vale la pena dejarlo escrito en un comentario, porque en tres meses nadie se va a acordar.

(d) Propuesta con salida. Tienes una dirección y no urge.

Nota, no bloqueante — reports/reconciliation.py:24-48. Esta secuencia (fetchsortformatwrite) es la misma de los otros tres exportadores; con este son cuatro copias y solo cambia el paso del formato. Se me ocurre subir el esqueleto a una base común —Template Method, unas veinte líneas menos por archivo—. Si prefieres no meterlo aquí, con abrir un ticket me quedo tranquilo.

(e) Pregunta genuina que probablemente se vuelva nota. Y con cuidado especial, porque el autor no es el culpable.

Pregunta — tests/test_klarpay.py:14. ¿El Settings._instance = None es porque Settings no deja construir una configuración de prueba? Si es eso, no es tuyo el problema: es el Singleton de config.py, y este test es el mejor argumento que hemos tenido para pasarlo a inyección. Abro ticket con el enlace a esta línea.

Lo que hay que ver en el conjunto. Cinco hallazgos, cinco registros distintos, una sola afirmación categórica. Y esa única categórica es la que más importa, así que se distingue del resto por contraste. Si los cinco estuvieran escritos con el mismo volumen, el bug se perdería entre las sugerencias.

Por qué funciona: elegir registro no es cortesía, es transmitir tu grado de certeza. Un comentario en afirmación dice "esto lo verifiqué"; uno en pregunta dice "esto no lo sé". Cuando el registro coincide con la certeza real, el autor puede priorizar solo. Cuando no coincide, tiene que adivinar.

Ejercicio 2 — Responde a tres comentarios. Eres el autor del PR. Escribe la respuesta a cada uno.

(a) Un comentario correcto sobre algo que efectivamente hiciste mal. (b) Un comentario correcto en el diagnóstico, pero que ignora una restricción que tú sí conoces: el contrato con Klarpay está a prueba tres meses y es probable que se quite en abril. (c) Un comentario que usa un término que no conoces ("esto es primitive obsession").

Ver solución

(a) Corta y sin ceremonia.

"Tienes razón, no vi que charge tenía que devolver ChargeResult. Lo cambio y agrego el caso de fallo a la prueba."

Sin disculpas largas, sin explicaciones defensivas. Y fíjate en el detalle del final: mencionar la prueba muestra que entendiste por qué importaba, no solo qué había que cambiar.

(b) Reconoce, informa, compromete.

"Sí, es shotgun surgery y lo vi mientras lo hacía. Lo dejé así a propósito por algo que no está en el PR: el contrato con Klarpay es a prueba por tres meses y hay una probabilidad real de que en abril lo quitemos. Meterlo en el registro ahora y sacarlo después me pareció más trabajo —y más riesgo— que dejarlo aislado y visible.

Si en abril se queda, hago el refactor con los cinco proveedores de una vez, que además es cuando de verdad se paga solo. Voy a dejar un comentario en el elif con esta razón y la fecha, para que quien lo lea en marzo sepa por qué está así. ¿Te parece?"

Las cuatro cosas que hace: reconoce el diagnóstico —no lo niega, que es lo que arruinaría la conversación—, da la información que faltaba, ofrece un compromiso concreto con fecha, y —lo mejor— propone dejar la razón escrita en el código. Esa última parte convierte una excepción en documentación.

(c) Preguntar, sin rodeos.

"No conozco el término, ¿me lo explicas? Por lo que busqué entiendo que es usar un str donde debería haber un tipo propio — ¿te refieres a que order.provider sea texto libre? Si es eso, quiero entenderlo bien porque creo que lo mismo pasa con ticket.kind y con order.status, y prefiero hacer los tres juntos que uno suelto."

Esta respuesta hace tres cosas que valen mucho. Admite el desconocimiento sin drama. Muestra que hizo el esfuerzo de buscar antes de preguntar. Y generaliza el aprendizaje al resto del sistema, que es exactamente lo que un revisor espera cuando escribe un comentario de estructura.

Lo que las tres tienen en común: ninguna es defensiva y ninguna es sumisa. En las tres el autor sigue siendo el dueño de su código y trata el comentario como información. Ese es el tono que hace que la gente quiera revisar tu código.

Por qué funciona: recibir bien es la mitad del oficio y es la mitad que nadie practica. Un equipo donde la gente recibe bien produce revisiones más honestas, porque escribir un comentario incómodo deja de ser costoso.

Ejercicio 3 — Encuentra qué hace daño. Este comentario es técnicamente correcto en todo. Identifica al menos cuatro cosas que lo hacen dañino y reescríbelo.

"Esto es un God object de manual. Viola SRP, viola OCP, y de paso metiste feature envy hacia Order en el método de conciliación. Cualquiera que haya leído a Fowler lo ve. Además el registro de plugins que usaste para el checkout es speculative generality —el módulo 2 de cualquier curso decente lo cubre— y ahora lo empeoraste registrando ahí algo que no son asientos. Esto necesita un rediseño serio antes de entrar. Ver el capítulo 3 de Refactoring."

Ver solución

Al menos seis cosas.

Uno: los nombres sustituyen al argumento. Cinco términos técnicos y dos siglas, y ni una sola consecuencia concreta. No dice qué va a doler, cuándo, ni a quién. El autor no puede hacer nada con esto salvo sentirse mal.

Dos: sin ubicación. "El método de conciliación", "el checkout". El autor tiene que adivinar dónde mirar en cada uno de los cuatro hallazgos.

Tres: el diagnóstico como demostración. "Cualquiera que haya leído a Fowler lo ve", "el módulo 2 de cualquier curso decente", "ver el capítulo 3". Estas frases no le sirven al autor: sirven para establecer quién sabe más. Y la segunda es directamente un insulto disfrazado de referencia bibliográfica.

Cuatro: culpa al autor por lo que heredó. El God object de checkout.py y el registro de plugins existían desde antes; el autor solo agregó un caso. Decir "metiste" y "usaste" le atribuye decisiones que no tomó. La única parte que sí es suya —haber registrado algo que no son asientos en el registro de plugins— queda sepultada entre las que no.

Cinco: sin peso ni prioridad. Cuatro hallazgos de gravedad muy distinta presentados como un bloque, y todo termina en "necesita un rediseño serio antes de entrar". El autor no sabe por dónde empezar ni cuánto se le está pidiendo.

Seis: sin dirección y sin salida. "Rediseño serio" no es una dirección. No hay un primer paso, no hay estimación, no hay opción de posponer nada.

La reescritura, en cuatro comentarios separados:

Bloqueante — plugins/impls/klarpay_seating.py. Este plugin se registra en SeatingPluginRegistry, pero no asigna asientos: extiende el checkout. Con eso, el registro pasa a tener dos implementaciones de cosas distintas, y quien lea registry.available() va a asumir que las dos asignan lugares. Concretamente, checkout.py:31 itera los plugins registrados llamando assign(), así que esto se va a invocar en cada compra con asientos numerados.

Sé de dónde viene: el registro está ahí y parece el lugar "extensible". El problema es que ese mecanismo se puso hace dos años para asientos y nunca tuvo una segunda implementación (hay un ticket abierto para quitarlo). Para lo que necesitas aquí, ¿alcanzaría con una llamada directa en el checkout? Si hay una razón para que sea un plugin, cuéntamela y lo vemos.

Bloqueante — reports/reconciliation.py:88. notify_finance usa siete atributos de Order y ninguno propio (feature envy). Lo marco bloqueante por una razón concreta y no por la estructura: la línea 91 hace order.customer.event.organizer.email, y con órdenes sin evento asociado —las de prueba, por ejemplo— eso lanza AttributeError dentro de un envío de correo. Lo mínimo sería un order.finance_summary() en Order que haga el recorrido en un solo lugar y devuelva None en vez de explotar.

Nota, no bloqueante — checkout/checkout.py. Este archivo es un God object desde antes de tu PR: cuatro responsabilidades y trece de los últimos doscientos commits. Tu cambio le suma veinte líneas de cálculo de comisión, que es la quinta. No te pido que lo arregles aquí; lo dejo escrito porque es la tercera vez este mes que un cambio de otra área tiene que tocarlo. Abro ticket para sacar el bloque de cobro a payments/, que es el corte que más rinde.

Nota — la comisión. El cálculo de la comisión de Klarpay que agregaste en el checkout es lógica de ese proveedor. Si vive en KlarpayProvider, el checkout no se entera y el día que Klarpay cambie sus tarifas se toca un archivo en vez de dos. Son unas quince líneas movidas, sin cambio de comportamiento. Tu decisión si entra aquí o en otro PR.

Lo que cambió, medido: cuatro hallazgos separados con su peso —dos bloqueantes, dos notas—, ubicación exacta en los cuatro, consecuencia concreta en los cuatro, dirección con estimación de tamaño, y una distinción explícita entre lo que el autor introdujo y lo que heredó. Cero referencias bibliográficas y cero siglas.

Y no es más suave. Sigue habiendo dos bloqueantes, y uno de ellos —el del registro de plugins— dice sin ablandar que la decisión principal del PR fue equivocada. Lo que cambió no es la firmeza: es que ahora el autor puede actuar.

Por qué funciona: el comentario original es el retrato del riesgo de este módulo. Todo lo que dice es correcto. Y aun así es peor que no haber comentado nada, porque el código no va a mejorar y la relación de trabajo sí va a empeorar. Tener el vocabulario y no tener esta lección es exactamente esa combinación.

Resumen y siguiente paso

En esta lección viste que nombrar un patrón o un olor es formular una hipótesis, no emitir un veredicto — porque tú viste el código y el autor vivió el problema, y él sabe cosas que no están en el diff. De ahí sale la regla que ordena todo: evidencia firme sobre el síntoma, humildad sobre la interpretación. El hecho se afirma; el juicio se ofrece.

Tienes cuatro registros para ofrecer un diagnóstico: la pregunta genuina (cuando de verdad no sabes, nunca como examen disfrazado), la observación con pregunta (el registro por defecto: hecho en indicativo, interpretación en "me hace pensar"), la propuesta con salida (donde la frase que hace el trabajo es la que permite declinar sin costo) y la afirmación directa (para hechos y para riesgos altos, donde ablandar sería deshonesto). Y el principio que los une: duro con el problema, suave con la persona — las dos cosas a la vez.

Del lado de recibir, tres respuestas que sirven —"tienes razón, lo cambio", "lo pensé y elegí X por Y", "no entiendo, ¿me muestras?"— y cuatro que no: la apelación a la costumbre, la falta de tiempo sin registro, el "resuelto" en silencio y la defensa preventiva.

Y el inventario del daño, que es el corazón de la lección: el nombre como sustituto del argumento, el nombre equivocado dicho con confianza —cuyo costo escala con tu antigüedad—, etiquetar por etiquetar, el diagnóstico como demostración, y la asimetría de poder ignorada, donde tus preguntas dejan de ser preguntas. Más el dato de oficio: al tercer intercambio en un hilo, el problema es el medio.

Antes de avanzar deberías poder: elegir el registro correcto para un hallazgo según tu grado de certeza; responder a un comentario con el que no estás de acuerdo sin ceder ni defenderte; y explicar por qué un comentario técnicamente correcto puede ser peor que ninguno.

Lo que sigue es el proyecto, y junta las siete lecciones. Vas a recibir un pull request real sobre Boletia —seis archivos, funcionalidad nueva, varios problemas de estructura de gravedad muy distinta— y vas a escribir la revisión completa: cada comentario con su olor nombrado, su ubicación, su consecuencia, una dirección y su peso. Y se va a juzgar con una sola prueba, la misma que abrió la lección 4: si el autor podría actuar sobre cada comentario sin volver a preguntar.

Recursos