StackPractices
beginner Por Mathias Paulenko

Plantilla de Checklist para Code Review

Checklist estructurada para revisiones de código consistentes y exhaustivas que detectan bugs, mejoran la legibilidad y comparten conocimiento en el equipo.

Descripción General

Una checklist de code review es un conjunto estandarizado de verificaciones que un revisor aplica a cada pull request, cubriendo las dimensiones que le importan al equipo: correctitud, seguridad, rendimiento, tests, estilo, documentación y operaciones. Es el filtro de calidad más útil en la entrega de software, pero solo cuando es consistente. Sin una checklist, los revisores se enfocan en lo que les importa personalmente: un ingeniero verifica inyección SQL, otro se obsesiona con nombres de variables y un tercero solo mira cobertura de tests. Una checklist compartida garantiza que cada revisión cubra lo que le importa al equipo, dejando espacio para el juicio humano en diseño y arquitectura.

Esta plantilla cubre el lado del revisor en el flujo de trabajo. Combínala con una plantilla de pull request para que los autores entreguen PRs listos para revisión, y consulta la guía de buenas prácticas de code review para el proceso alrededor: carga de revisores, tiempos de respuesta esperados y cómo dar feedback que funcione. Para cambios que tocan autenticación, pagos o datos de usuario, súmale la checklist de revisión de seguridad para PRs.

Cuándo Usar

Usa esta plantilla cuando:

  • Quieras estandarizar qué significa “listo” para la revisión de código en el equipo
  • Los nuevos revisores no estén seguros de qué buscar en un pull request
  • Las revisiones tarden demasiado porque los revisores no conocen el alcance
  • Estés integrando un equipo nuevo y necesites establecer estándares compartidos rápidamente
  • Quieras reducir defectos post-merge e incidentes en producción

Omite la checklist completa cuando solo serviría para que la gente la ignore: repos con un único maintainer que revisa sus propios PRs, cambios de typos o solo comentarios, y PRs de configuración que CI ya valida de punta a punta. Un proceso pesado sobre cambios triviales es exactamente cómo mueren las checklists.

Prerrequisitos

Antes de adoptar esta checklist:

  • El equipo acuerda qué ítems son obligatorios vs. opcionales
  • CI está configurado para detectar problemas automatizables (linting, formato, verificaciones de tipos)
  • Los revisores entienden que las checklists complementan el juicio; no lo reemplazan
  • Existe una ruta documentada para escalar desacuerdos entre autor y revisor
  • La checklist está guardada donde los revisores puedan consultarla fácilmente (plantilla de PR, wiki o mensaje fijado)

Solución

# Checklist de Code Review

> Revisor: ______ | Autor: ______ | PR: ______ | Fecha: ______

## 1. Lógica y Correctitud
- [ ] El código hace lo que la descripción del PR dice que hace
- [ ] Los casos borde están manejados (entradas vacías, nulos, timeouts, fallos)
- [ ] No hay errores off-by-one, condiciones de carrera ni bucles infinitos
- [ ] Los caminos de error son explícitos y no silencian excepciones
- [ ] La lógica de negocio coincide con la especificación o el requerimiento del ticket

## 2. Seguridad
- [ ] No hay secretos, tokens ni credenciales en el código
- [ ] Las entradas de usuario están validadas y sanitizadas
- [ ] Existen verificaciones de autorización para operaciones protegidas
- [ ] No hay vulnerabilidades de inyección SQL, XSS ni command injection
- [ ] Las dependencias agregadas están verificadas y provienen de fuentes confiables

## 3. Rendimiento
- [ ] No hay consultas N+1 ni ineficiencias algorítmicas evidentes
- [ ] Los conjuntos grandes de datos están paginados o transmitidos por streaming
- [ ] No hay llamadas de red innecesarias ni I/O bloqueante en rutas críticas
- [ ] El caching se utiliza apropiadamente donde corresponde
- [ ] Se previenen fugas de recursos (conexiones, descriptores de archivo, memoria)

## 4. Testing
- [ ] Los tests unitarios cubren la lógica nueva o modificada
- [ ] Los tests de integración cubren dependencias externas y límites
- [ ] Los casos borde y caminos de fallo están probados, no solo caminos felices
- [ ] Los tests son deterministas y no dependen de tiempo ni orden
- [ ] Los nombres de los tests describen comportamiento, no implementación

## 5. Mantenibilidad y Estilo
- [ ] El código es legible sin necesidad de preguntarle al autor
- [ ] Los nombres de variables y funciones son descriptivos y consistentes
- [ ] No hay lógica duplicada que podría extraerse o reutilizarse
- [ ] Los comentarios explican el "por qué", no el "qué" (el código muestra el qué)
- [ ] La complejidad es apropiada; se señala la sobre-ingeniería

## 6. Documentación
- [ ] Las APIs públicas tienen documentación actualizada o specs OpenAPI
- [ ] El README o los runbooks se actualizan si cambió el comportamiento
- [ ] Los breaking changes se destacan explícitamente en la descripción del PR
- [ ] Los pasos de migración están documentados si cambió el esquema o la config

## 7. Despliegue y Operaciones
- [ ] Los feature flags se usan para cambios riesgosos o irreversibles
- [ ] El monitoreo y las alertas se agregan o actualizan para los nuevos caminos
- [ ] El procedimiento de rollback se entiende y está probado
- [ ] Las migraciones de base de datos son compatibles hacia atrás o tienen un plan

---

## Notas de Revisión

| Línea / Archivo | Problema | Severidad | Acción |
|-----------------|----------|-----------|--------|
| ______ | ______ | Nit / Sugerencia / Bloqueador | ______ |

**Veredicto final:** Aprobar / Solicitar cambios / Comentar
**Preparación para merge:** Listo / Requiere cambios / Bloqueado

## Confirmación del Revisor

- [ ] Leí cada línea modificada
- [ ] Ejecuté los tests localmente
- [ ] Verifiqué que la descripción del PR coincide con los cambios
- [ ] Revisé breaking changes más allá de lo que declara el autor

Archivos complementarios: el repo companion de code-review-checklist-template tiene esta checklist como archivo independiente más el workflow de automatización, el validador y el script de métricas de la sección Soluciones Avanzadas.

Cómo Encaja la Checklist en el Flujo de Revisión

La checklist se ubica entre las verificaciones automatizadas y la decisión de merge. CI y el análisis estático corren primero y filtran los problemas mecánicos: lint, tipos, tests rotos y patrones de vulnerabilidad conocidos. El revisor después trabaja la checklist sobre lo que queda: lo que necesita juicio. Si el veredicto es “solicitar cambios”, el ciclo se repite en el próximo push.

Flujo de code review: se abre un PR, corren las verificaciones automáticas (CI, lint, CodeQL, tamaño del PR), el revisor aplica la checklist y aprueba o solicita cambios; los cambios solicitados vuelven al autor, y la checklist misma se revisa trimestralmente

Dos propiedades de esta división importan. Primero, la checklist es para humanos: todo lo que una máquina puede verificar pertenece a CI, no a la lista — por eso el ítem 4 de “Lo que funciona” retira verificaciones a medida que las herramientas avanzan. Segundo, el ciclo trimestral es lo que mantiene honesta a la checklist: los ítems que nunca se disparan son ruido, y los huecos que sigues encontrando en producción pertenecen a ella.

Cómo Adoptarla

La checklist solo funciona si los revisores la ven sin tener que ir a buscarla. Tres ubicaciones cubren a la mayoría de los equipos:

  1. Plantilla de PR (recomendado). Pega la checklist en .github/pull_request_template.md para que cada PR la muestre al revisor. Mantén los ítems obligatorios arriba y recorta los opcionales: una plantilla que nadie termina termina omitiéndose. GitHub además permite mantener plantillas de PR separadas en .github/PULL_REQUEST_TEMPLATE/ si quieres una variante corta para docs o cambios de configuración.
  2. Wiki o handbook de ingeniería. Si tu plantilla de PR ya está cargada, enlaza la checklist desde ella y guarda la versión canónica en el handbook. El costo es real: los revisores tienen que hacer clic para llegar, así que espera menor adopción.
  3. CODEOWNERS para secciones con restricción. Para la variante crítica de seguridad de la tabla de abajo, usa CODEOWNERS para dirigir los cambios en auth, criptografía o pagos a revisores que realmente puedan aprobarlos — la checklist sola no puede imponer eso.

Cualquiera sea la ubicación, acuerda las expectativas de tiempo de respuesta de antemano. Una base común es primera revisión dentro del día hábil para PRs de menos de ~400 líneas modificadas; los PRs más grandes reciben una revisión por etapas o un pedido amable de dividirlos. Combinar la checklist con una rotación de revisores reparte la carga y evita que una persona senior se convierta en el guardián permanente.

Ejemplo: Una Revisión Completada

La tabla de Notas de Revisión es lo que convierte una checklist en una conversación. Así se ve una entrada real en un PR que agrega un endpoint de usuarios:

Línea / ArchivoProblemaSeveridadAcción
users.py:87email escrito en logs en texto planoBloqueadorEliminar o enmascarar antes del merge
UserRepository.find()`LIKE ’%‘name
get_user():34El comentario repite lo que dice el códigoNitLimpieza opcional

Veredicto final: Solicitar cambios. Preparación para merge: Bloqueado hasta corregir los dos bloqueadores. Los nits se corrigen o se descartan explícitamente — de cualquier forma no frenan el merge.

Nota la disciplina: cada bloqueador nombra un archivo y una corrección concreta, así que el autor nunca tiene que adivinar qué significa “solicitar cambios”. Esa es la diferencia entre una checklist que hace avanzar el código y un hilo de comentarios que se estanca dos días.

Explicación

La checklist está organizada por preocupación en lugar de por tipo de archivo. Los revisores tienden a perderse leyendo un diff línea por línea; evaluar el cambio contra dimensiones fijas — correctitud, seguridad, rendimiento, testing, mantenibilidad, documentación, operaciones — obliga a una cobertura completa y produce veredictos comparables entre revisores.

La otra decisión que importa es separar lo que verifican las máquinas de lo que verifican los humanos. El linting, el formato y los errores de tipos son trabajo de CI; ponerlos en una checklist humana desperdicia minutos de revisión y enseña a los revisores a leer por encima. La checklist se mantiene enfocada en lo que los humanos hacen mejor que las herramientas: si la lógica coincide con la especificación, si los tests verifican lo correcto, si el próximo lector va a entender el código.

Variantes

ContextoAjustesNotas
Código frontend / UIAgregar accesibilidad, diseño responsivo y compatibilidad de navegadorLos cambios visuales necesitan verificación manual
Pipelines de datosAgregar verificaciones de calidad de datos, compatibilidad de esquema y consideraciones de backfillLos cambios de datos son más difíciles de deshacer que los de código
Infraestructura / TerraformAgregar seguridad de estado, revisión del plan y evaluación del radio de impactoUna mala configuración puede tumbar producción
Código crítico de seguridadHacer obligatoria la sección de seguridad y exigir aprobación del equipo de seguridadFinanzas, salud y sistemas de autenticación
Hotfix / respuesta a incidenteAcortar la checklist a seguridad, correctitud y rollback solamenteLa velocidad importa; documenta lo que se omitió y por qué

Trata la tabla como punto de partida, no como un fork: a los equipos les va mejor con una checklist más secciones por contexto que con cinco plantillas paralelas que divergen con el tiempo.

Lo que funciona

  1. Mantén la checklist visible. Incrústala en la plantilla de PR para que los revisores la vean automáticamente
  2. Distingue nits de bloqueadores. No todo problema impide el merge; usa etiquetas de severidad para mantener las revisiones fluidas
  3. Rota los revisores. Las checklists reducen la brecha de experiencia y facilitan distribuir la carga de revisión
  4. Revisa la checklist trimestralmente. Elimina los ítems que ya están automatizados; agrega los que se siguen escapando
  5. Limita el tiempo de revisión. Si una revisión toma más de 30 minutos, el PR probablemente es demasiado grande
  6. Mide adopción, no solo presencia. Si menos de la mitad de los PRs mergeados muestran una checklist completada, el proceso es decorativo: averigua dónde se está saltando antes de agregar más ítems

Errores Comunes

  1. Tratar la checklist como sustituto del pensamiento. La checklist atrapa omisiones comunes, no fallas de diseño
  2. Hacer cada ítem obligatorio. Esto ralentiza las revisiones sin mejorar la calidad; bloquea solo en correctitud, seguridad y tests
  3. No actualizar la checklist. A medida que las herramientas mejoran, las verificaciones manuales deberían automatizarse
  4. Revisar en soledad. Las revisiones en pareja sobre cambios críticos atrapan problemas que los revisores solitarios omiten
  5. Enfocarse solo en el diff. Los revisores también deberían verificar que la descripción del PR, los tests y la documentación sean consistentes
  6. Revisar el código de test con menos rigor que el de producción. Tests frágiles, no deterministas o que no verifican lo correcto dan falsa confianza — aplica la misma checklist a los archivos de test:
# Anti-patrón: test que siempre pasa
def test_user_creation():
    user = create_user(email="test@example.com")
    assert user is not None  # No verifica email, id ni status

# Mejor: test que verifica comportamiento
def test_user_creation_sets_correct_fields():
    user = create_user(email="test@example.com")
    assert user.email == "test@example.com"
    assert user.id is not None
    assert user.status == "active"
    assert user.created_at is not None

Soluciones Avanzadas

Revisión de código automatizada con GitHub Actions y CodeQL

Combina la checklist manual con análisis automatizado para detectar problemas que los humanos pasan por alto. Este workflow corre CodeQL para patrones de vulnerabilidad, exige lint y verificaciones de tipos, y avisa en PRs demasiado grandes: los tres ítems automatizables que liberan al revisor para enfocarse en la checklist misma:

# .github/workflows/code-review-automation.yml
name: Automated Code Review
on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  codeql-analysis:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - uses: github/codeql-action/init@v3
        with:
          languages: python, javascript
      # autobuild solo es necesario para lenguajes compilados
      - uses: github/codeql-action/analyze@v3

  lint-and-type-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - name: Comment PR with lint results
        if: failure()
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.payload.pull_request.number,
              body: "Linting or type checking failed. Please fix before requesting review."
            })

  pr-size-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Check PR size
        uses: actions/github-script@v7
        with:
          script: |
            // paginate: listFiles devuelve 30 archivos por página por defecto
            const files = await github.paginate(github.rest.pulls.listFiles, {
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: context.payload.pull_request.number,
              per_page: 100,
            });
            const totalChanges = files.reduce(
              (sum, file) => sum + file.changes, 0
            );
            if (totalChanges > 500) {
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.payload.pull_request.number,
                body: `This PR has ${totalChanges} changes. Consider splitting into smaller PRs for easier review.`
              });
            }

Bot de revisión para hacer cumplir la checklist

Verifica automáticamente que los PRs cumplan los requisitos de la checklist antes de la revisión. El validador de abajo chequea tres reglas mecánicas — tests agregados junto al código nuevo, ausencia de strings que parecen secretos en las líneas añadidas y una descripción de PR completa — que son exactamente el tipo de verificaciones que desperdician atención del revisor cuando se hacen a mano:

import re
import subprocess
from typing import Dict, List

class PRChecklistValidator:
    def __init__(self, pr_diff: str, pr_description: str):
        self.diff = pr_diff
        self.description = pr_description

    def check_tests_added(self) -> bool:
        """Verify that new code includes tests."""
        has_source = bool(re.search(r'\+\+\+ b/src/.*\.py', self.diff))
        has_tests = bool(re.search(r'\+\+\+ b/tests/.*\.py', self.diff))
        if has_source and not has_tests:
            return False
        return True

    def check_no_secrets(self) -> bool:
        """Check for common secret patterns in the diff."""
        secret_patterns = [
            r'(?i)api[_-]?key\s*=\s*["\'][^"\']+["\']',
            r'(?i)password\s*=\s*["\'][^"\']+["\']',
            r'(?i)secret\s*=\s*["\'][^"\']+["\']',
            r'-----BEGIN (RSA |EC )?PRIVATE KEY-----',
            r'(?i)aws_secret_access_key\s*=\s*["\'][^"\']+["\']',
        ]
        added_lines = [line for line in self.diff.split('\n') if line.startswith('+')]
        for line in added_lines:
            for pattern in secret_patterns:
                if re.search(pattern, line):
                    return False
        return True

    def check_pr_description(self) -> bool:
        """Verify PR description has required sections."""
        required_sections = ["## Summary", "## Testing", "## Breaking Changes"]
        return all(section in self.description for section in required_sections)

    def run_all_checks(self) -> List[Dict]:
        """Run all checklist validations and return results."""
        checks = [
            ("Tests added for new code", self.check_tests_added),
            ("No secrets in diff", self.check_no_secrets),
            ("PR description complete", self.check_pr_description),
        ]
        results = []
        for name, check_fn in checks:
            passed = check_fn()
            results.append({
                "check": name,
                "passed": passed,
                "status": "PASS" if passed else "FAIL",
            })
        return results

# Ejemplo de uso: diff y descripción de la rama actual vs main
diff = subprocess.run(
    ["git", "diff", "origin/main...HEAD"],
    capture_output=True, text=True
).stdout
pr_description = subprocess.run(
    ["git", "log", "-1", "--pretty=%B"],
    capture_output=True, text=True
).stdout

validator = PRChecklistValidator(diff, pr_description)
results = validator.run_all_checks()
for r in results:
    print(f"{r['status']}: {r['check']}")

Dashboard de métricas de revisión

Rastrea tiempo de ciclo de revisión, calidad de comentarios y tasa de escape de defectos. El tiempo de ciclo te dice si las revisiones son un cuello de botella; la tasa de escape te dice si la checklist realmente está atrapando cosas o solo agregando ceremonia:

from collections import defaultdict
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Dict, List

@dataclass
class ReviewMetric:
    pr_number: int
    author: str
    reviewer: str
    created_at: datetime
    merged_at: datetime
    comments_count: int
    blockers_count: int
    post_merge_defects: int

def calculate_review_metrics(metrics: List[ReviewMetric]) -> Dict:
    """Calculate aggregate review metrics for the team."""
    if not metrics:
        return {}

    cycle_times = [(m.merged_at - m.created_at).total_seconds() / 3600 for m in metrics]
    comment_counts = [m.comments_count for m in metrics]
    defect_rate = sum(1 for m in metrics if m.post_merge_defects > 0) / len(metrics)

    by_reviewer = defaultdict(list)
    for m in metrics:
        by_reviewer[m.reviewer].append(m)

    reviewer_stats = {}
    for reviewer, reviews in by_reviewer.items():
        reviewer_stats[reviewer] = {
            "total_reviews": len(reviews),
            "avg_comments": sum(r.comments_count for r in reviews) / len(reviews),
            "avg_blockers": sum(r.blockers_count for r in reviews) / len(reviews),
            "defects_missed": sum(r.post_merge_defects for r in reviews),
        }

    return {
        "avg_cycle_time_hours": sum(cycle_times) / len(cycle_times),
        "avg_comments_per_pr": sum(comment_counts) / len(comment_counts),
        "defect_escape_rate": defect_rate,
        "by_reviewer": reviewer_stats,
    }

# Ejemplo de uso
metrics = [
    ReviewMetric(101, "alice", "bob", datetime.now() - timedelta(hours=5), datetime.now(), 3, 1, 0),
    ReviewMetric(102, "charlie", "bob", datetime.now() - timedelta(hours=2), datetime.now(), 5, 2, 1),
    ReviewMetric(103, "alice", "dave", datetime.now() - timedelta(hours=8), datetime.now(), 1, 0, 0),
]

results = calculate_review_metrics(metrics)
print(f"Average cycle time: {results['avg_cycle_time_hours']:.1f}h")
print(f"Defect escape rate: {results['defect_escape_rate']:.0%}")

Dos lecturas que vale la pena seguir trimestralmente: una defect_escape_rate a la baja significa que la checklist está funcionando, y una gran dispersión en avg_cycle_time_hours entre revisores suele indicar que la checklist se aplica de forma desigual — un problema de capacitación, no de herramientas.

Lectura Recomendada

Preguntas frecuentes

¿Cuánto debería durar una revisión de código?

Los PRs pequeños (menos de 200 líneas) deberían revisarse en pocas horas. Los PRs grandes deberían dividirse o revisarse por etapas. Si una revisión consistentemente toma más de 30 minutos, el equipo debería investigar si los PRs son demasiado grandes o la checklist es demasiado amplia.

¿Deberían los ingenieros junior revisar código de ingenieros senior?

Sí. La revisión de código es una oportunidad de aprendizaje tanto como un filtro de calidad. Los revisores junior detectan problemas de claridad que los senior pasan por alto porque estos ya comparten las suposiciones del autor. La checklist pone a todos en igualdad de condiciones al indicarle a cada uno qué verificar.

¿Qué pasa si el autor no está de acuerdo con un comentario de revisión?

Que lo discutan. Si la conversación se estanca, escala a un tech lead o al criterio de desempate documentado del equipo. La checklist existe para reducir debates subjetivos haciendo las expectativas explícitas, pero no puede eliminar todo desacuerdo.

¿Cuál es la diferencia entre un nit y un bloqueador?

Un nit es una mejora estilística o menor que el autor puede tomar o dejar: preferencias de nombres, redacción de comentarios, un idioma alternativo. Un bloqueador es un problema de correctitud, seguridad o integridad de datos que debe corregirse antes del merge. Marca la severidad explícitamente en cada comentario; mezclar los dos es lo que hace que las revisiones se sientan arbitrarias.

¿No cubre CI ya esta checklist?

Solo el tercio mecánico. Los linters, los verificadores de tipos, los tests y CodeQL atrapan violaciones de estilo, builds rotos y patrones de vulnerabilidad conocidos. No pueden decir si el código coincide con el ticket, si un test verifica el comportamiento correcto o si una migración es realmente reversible. La checklist cubre las decisiones de juicio — por eso el primer prerrequisito es llevar lo automatizable a CI.