← Todos los temas
Día 18 · Trabajar en equipo

Code review y mentoría

Qué es una revisión de código, cómo se hace bien y por qué hace crecer a los equipos.

Definición

Una revisión de código (code review) es la práctica de que otra persona lea y apruebe un cambio de código antes de integrarlo a la rama principal. Sirve para detectar errores, mantener el código legible y consistente y, sobre todo, para que el conocimiento circule: quien revisa aprende el cambio y quien lo escribió aprende del revisor. Bien hecha, cada revisión es también una sesión breve de mentoría (mentoring).

En Google, según el libro Software Engineering at Google (capítulo 9), prácticamente todo cambio se revisa antes de entrar al repositorio. El estudio Modern Code Review: A Case Study at Google (Sadowski et al., ICSE-SEIP 2018) analizó los registros de 9 millones de cambios revisados, además de 12 entrevistas y una encuesta a 44 desarrolladores, y concluyó que en Google la revisión no gira en torno a encontrar errores: se introdujo para asegurar legibilidad y mantenibilidad, y hoy los desarrolladores valoran además su papel educativo, mantener normas, evitar accidentes y dejar historial.

El flujo con pull request

La revisión moderna es ligera y se hace con herramientas, no en reuniones. En GitHub el cambio se propone con un pull request (en GitLab, merge request):

  1. El autor trabaja en una rama, hace el cambio pequeño y enfocado y abre el pull request con una descripción: qué cambia, por qué y cómo se probó (si hubo design doc, día 17, lo enlaza).
  2. La integración continua (CI, día 7) corre pruebas, linter y análisis automático. Si algo falla, el autor lo corrige antes de pedir revisión humana.
  3. Uno o más revisores comentan línea por línea y dan un veredicto: aprobar, comentar o pedir cambios.
  4. El autor responde cada comentario: corrige, explica o abre un cambio aparte para lo que no cabe en este.
  5. Con la aprobación requerida (en muchos equipos, además, la del dueño del código), el cambio se integra.

Qué revisa un revisor

La guía pública de Google para revisores (eng-practices) ordena así lo que hay que mirar:

AspectoPregunta del revisor
Diseño¿El cambio tiene sentido en este sistema y en este momento?
Funcionalidad¿Hace lo que el autor pretende y es bueno para sus usuarios? ¿Hay casos límite o concurrencia mal resuelta?
Complejidad¿Es más complejo de lo necesario? ¿Implementa cosas que «tal vez» se necesiten después?
Pruebas¿Tiene pruebas adecuadas y bien diseñadas, que fallarían si el código se rompe?
Nombres¿Los nombres dicen claramente qué es cada cosa?
Comentarios¿Son claros y explican sobre todo por qué, no qué?
Estilo y consistencia¿Sigue la guía de estilo y las convenciones del código existente?
Documentación¿Se actualizó la documentación que el cambio afecta?

La guía añade dos reglas: leer cada línea que te asignaron y decir también lo que está bien hecho.

Cambios pequeños y respuestas rápidas

CriterioReferencia
Tamaño razonableGuía de Google: unas 100 líneas suele ser razonable y 1,000 suele ser demasiado; Software Engineering at Google habla de unas 200 líneas como límite general de un cambio «pequeño»
Tamaño real en GoogleMediana de 24 líneas modificadas por cambio; más del 35 % toca un solo archivo (Sadowski et al.)
Tiempo de respuestaGuía de Google: un día hábil como máximo para responder una solicitud de revisión
Latencia real en GooglePrimera respuesta en una mediana de menos de una hora para cambios pequeños; proceso completo en menos de 4 horas de mediana (Sadowski et al.)

Un cambio pequeño se revisa antes y mejor, tiene menos errores, se revierte con facilidad y, si se rechaza, se pierde menos trabajo. Si un pull request crece, se divide: primero la refactorización, después la función nueva.

Cómo escribir comentarios útiles

Un buen comentario tiene tres partes: qué observas, por qué importa y una propuesta concreta. Además, deja claro su nivel, para que el autor sepa qué es obligatorio:

NivelSignificadoEjemplo de inicio
BloqueaDebe corregirse antes de integrar: error, riesgo de seguridad, pérdida de datos«Bloquea: si el cliente reintenta…»
SugerenciaMejora recomendable; el autor puede decidir«Sugerencia: esta función se podría…»
Nit (detalle)Menor: nombre, formato, orden. No impide aprobar«Nit: pago es una intención de pago…»
FYI (para tu información)Algo para considerar en el futuro, no en este cambio«FYI: existe una utilidad para esto en…»

La guía de Google pide comentar el código, nunca a la persona, y explicar el porqué. Compara:

Malo:  Esto está mal, obviamente el dinero no se maneja así.
Bueno: Bloquea: `total * 0.2` usa punto flotante y con 999.99 da 199.99800000000002,
       porque 0.2 no es exacto en binario. Propuesta: usar Decimal y redondear a centavos.

El primero no dice qué está mal ni cómo arreglarlo, y «obviamente» hace sentir mal a quien empieza. El segundo da el dato reproducible, la causa y la salida.

Cómo recibir una revisión

  • Los comentarios son sobre el código, no sobre ti. Agradece y responde todos, aunque sea con «hecho»; lo que no cabe en este cambio se anota y se hace en otro.
  • Si no estás de acuerdo, explica tu razonamiento con datos; si no hay acuerdo, se decide en una conversación breve, no en veinte réplicas.
  • Si un revisor no entendió algo, mejora el código o agrega un comentario en él: el siguiente lector tendrá la misma duda.

Revisión automatizada y sus límites

HerramientaQué hace bienQué no sustituye
Formateadores y linters (Black, Ruff, ESLint, Prettier)Estilo y errores comunes, sin discusiónEl diseño
Pruebas en CI (día 7)Detectar regresiones en cada cambioPruebas que nadie escribió
Análisis estático y de seguridadPatrones peligrosos, dependencias vulnerablesEl contexto del negocio
Revisión asistida por IAPrimer filtro: casos límite, nombres, pruebas faltantesLa responsabilidad de aprobar y el aprendizaje del equipo

Regla práctica: todo lo que una herramienta puede comprobar no debe ocupar el tiempo de una persona. Los comentarios de una IA se tratan como sugerencias de un revisor más: se verifican, no se aceptan a ciegas.

Mentoría y transferencia de conocimiento

El estudio de Google encontró que los desarrolladores con menos de un año en la empresa suelen recibir más del doble de comentarios por cambio, y que esa cifra baja con la experiencia: la revisión funciona como formación continua. Google además formaliza la legibilidad (readability): una certificación por lenguaje, y todo cambio necesita la aprobación de alguien que la tenga.

Para aprovecharlo: asigna a quien empieza también como revisor, no solo como autor (leer código ajeno enseña tanto como recibir comentarios); en comentarios didácticos, enlaza la guía o la documentación en lugar de solo dar la respuesta, y reconoce de forma explícita lo bien hecho.

Métricas sanas y antipatrones

Métrica sanaAntipatrón
Tiempo hasta la primera respuestaUsar el número de comentarios como medida de calidad de una persona
Tamaño de los cambiosPull requests de miles de líneas aprobados sin leer («LGTM» automático)
Rondas de revisión por cambioRetrasar la aprobación o exigir cambios por preferencias personales (abuso de poder)
Defectos encontrados después de integrarDiscutir el estilo a mano en lugar de configurar un formateador

Práctica: comprobar tus comentarios antes de enviarlos

El script lee un diff unificado de ejemplo (el pull request que implementa el design doc del día 17), calcula qué líneas cambiaron y revisa cada comentario: que apunte a una línea cambiada, que tenga nivel, porqué y propuesta, y que no use frases duras.

1. Guarda el script como revisa_comentarios.py:

"""Revisa comentarios de code review antes de enviarlos: línea cambiada, nivel (Bloquea,
Sugerencia, Nit), porqué y propuesta, sin frases duras. Uso: python revisa_comentarios.py malos|buenos
"""
import re
import sys

DIFF = """\
--- a/reservas/pagos.py
+++ b/reservas/pagos.py
@@ -12,4 +12,7 @@ def crear_reserva(cliente_id, total):
     validar_cliente(cliente_id)
-    reserva = Reserva(cliente_id=cliente_id, total=total, estado="confirmada")
+    anticipo = total * 0.2
+    reserva = Reserva(cliente_id=cliente_id, total=total, estado="pendiente_pago")
+    pago = proveedor.crear_pago(monto=anticipo)
+    reserva.pago_id = pago["id"]
     guardar(reserva)
     return reserva
"""

COMENTARIOS = {
    "malos": [
        ("reservas/pagos.py", 13, "Esto está mal, obviamente el dinero no se maneja así."),
        ("reservas/pagos.py", 15, "Falta la idempotencia."),
        ("reservas/pagos.py", 17, "Nit: ¿por qué hiciste esto aquí?"),
    ],
    "buenos": [
        ("reservas/pagos.py", 13, "Bloquea: `total * 0.2` usa punto flotante y con 999.99 da 199.99800000000002, "
                                  "porque 0.2 no es exacto en binario. Propuesta: usar Decimal y redondear a centavos."),
        ("reservas/pagos.py", 15, "Bloquea: si el cliente reintenta tras un error de red se cobra dos veces, porque "
                                  "crear_pago no lleva llave de idempotencia. Propuesta: pasar idempotency_key=reserva.id."),
        ("reservas/pagos.py", 16, "Nit: `pago` es una intención de pago, no un pago aprobado; para no confundirlo, "
                                  "propuesta: renombrarlo a `intento_pago`."),
    ],
}
NIVELES = re.compile(r"^(bloquea|sugerencia|nit):", re.IGNORECASE)
PORQUE = re.compile(r"\b(porque|ya que|para)\b", re.IGNORECASE)
DURAS = ["esto está mal", "obviamente", "es obvio", "cualquiera sabe", "por qué hiciste", "no sirve", "horrible"]


def lineas_cambiadas(diff: str) -> dict[str, set[int]]:
    # Números de línea (en la versión nueva) de cada línea agregada o modificada.
    cambiadas, archivo, n = {}, None, 0
    for linea in diff.splitlines():
        if linea.startswith("+++ "):
            archivo = linea[4:].removeprefix("b/")
            cambiadas[archivo] = set()
        elif linea.startswith("--- "):
            continue
        elif m := re.match(r"@@ -\d+(?:,\d+)? \+(\d+)", linea):
            n = int(m[1])
        elif linea.startswith("+"):
            cambiadas[archivo].add(n)
            n += 1
        elif not linea.startswith("-"):
            n += 1  # línea de contexto: existe en la versión nueva pero no cambió
    return cambiadas


def revisar(archivo: str, linea: int, texto: str, cambiadas: dict[str, set[int]]) -> list[str]:
    t = texto.lower()
    reglas = [(linea in cambiadas.get(archivo, set()), "no apunta a una línea cambiada"),
              (NIVELES.match(texto), "sin nivel (Bloquea, Sugerencia o Nit)"),
              (PORQUE.search(texto), "no explica el porqué"),
              ("propuesta:" in t, "sin propuesta")]
    return [msg for ok, msg in reglas if not ok] + [f"frase dura: «{f}»" for f in DURAS if f in t]


if __name__ == "__main__":
    cambiadas = lineas_cambiadas(DIFF)
    print("Líneas cambiadas:", {a: sorted(n) for a, n in cambiadas.items()})
    total = 0
    for archivo, linea, texto in COMENTARIOS[sys.argv[1]]:
        problemas = revisar(archivo, linea, texto, cambiadas)
        total += len(problemas)
        print(f"{'OK   ' if not problemas else 'FALLA'} {archivo}:{linea}  {'; '.join(problemas)}".rstrip())
    print(f"Resultado: {total} problema(s)" if total else "Resultado: comentarios listos para enviar")
    sys.exit(1 if total else 0)

2. Ejecútalo con los comentarios malos: python revisa_comentarios.py malos. Salida real (Python 3.12, Windows), con código de salida 1:

Líneas cambiadas: {'reservas/pagos.py': [13, 14, 15, 16]}
FALLA reservas/pagos.py:13  sin nivel (Bloquea, Sugerencia o Nit); no explica el porqué; sin propuesta; frase dura: «esto está mal»; frase dura: «obviamente»
FALLA reservas/pagos.py:15  sin nivel (Bloquea, Sugerencia o Nit); no explica el porqué; sin propuesta
FALLA reservas/pagos.py:17  no apunta a una línea cambiada; no explica el porqué; sin propuesta; frase dura: «por qué hiciste»
Resultado: 12 problema(s)

3. Ejecútalo con los buenos: python revisa_comentarios.py buenos. Salida real, con código de salida 0:

Líneas cambiadas: {'reservas/pagos.py': [13, 14, 15, 16]}
OK    reservas/pagos.py:13
OK    reservas/pagos.py:15
OK    reservas/pagos.py:16
Resultado: comentarios listos para enviar

4. Interpreta el resultado. El diff solo cambió las líneas 13 a 16; la línea 17 (guardar(reserva)) es contexto, así que un comentario ahí no corresponde a este cambio. Los comentarios buenos señalan dos defectos reales que bloquean (redondeo de dinero y cobro duplicado) y un detalle de nombre marcado como «Nit». Para usarlo con un pull request real, sustituye DIFF por la salida de git diff main...HEAD.

Skill gratis para Claude: revisa-mi-codigo revisa tus cambios como un mentor: corre tus pruebas y tu linter, comenta línea por línea con el porqué y una propuesta, y comprueba con un script que cada comentario apunte a una línea real del cambio.

Preguntas frecuentes

¿Qué es un code review?

Es cuando otra persona lee tus cambios de código antes de integrarlos para detectar errores, proponer mejoras y mantener el código legible. Se hace con un pull request: una solicitud para integrar los cambios de tu rama que muestra las diferencias línea por línea.

¿Qué significa «Nit» en una revisión de código?

Marca un comentario menor (un nombre, un formato) que conviene corregir pero no impide aprobar el cambio. Etiquetar el nivel evita confundir detalles con problemas graves.

¿La IA puede sustituir la revisión de código?

No. Sirve como primer filtro y detecta problemas comunes, pero no conoce todo el contexto del negocio ni asume la responsabilidad de aprobar, y no transmite conocimiento entre las personas del equipo.

En el reel lo explicamos así

En los buenos equipos nadie publica código sin que otra persona lo revise. Es como un editor de libros: no reescribe tu libro, te ayuda a que quede mejor. Se hace con un pull request: pides integrar tus cambios y alguien los lee línea por línea. Un mal comentario dice «esto está mal»; uno bueno dice qué, por qué y propone cómo. En Google prácticamente todo cambio se revisa antes de entrar. Así aprenden los que empiezan: cada revisión es una mini clase (mentoría). Con esto terminan los 18 días.

Lo que vimos en los 18 días

  1. ¿Qué es una API?
  2. Cómo se construye una API (Python y FastAPI)
  3. Bases de datos: SQL y NoSQL
  4. Monolito o microservicios
  5. Docker y contenedores
  6. Eventos y mensajería
  7. CI/CD: publicar sin romper nada
  8. Kubernetes
  9. Modelos de lenguaje: cómo «piensa» ChatGPT
  10. Prompts: cómo hablarle a una IA
  11. RAG: IA que lee tus documentos
  12. LangChain, LlamaIndex y LangGraph
  13. Agentes de IA
  14. Observabilidad: ver qué hace tu IA por dentro
  15. Velocidad y costo de una app de IA
  16. MLOps: mantener viva una IA
  17. PRDs, RFCs y design docs: los planos del software
  18. Code review y mentoría

Referencias

En el reel lo explicamos así

Un editor de libros: no reescribe tu libro, te ayuda a que quede mejor.

Glosario: del inglés al español

Code reviewrevisión de código
Pull requestsolicitud para integrar cambios
Feedbackretroalimentación
Mentoringmentoría