27. Revisión de código: checklist práctico para detectar code smells

27.1 Objetivo del tema

Una revisión de código no debería depender solamente de la intuición. Una checklist ayuda a mirar el código con criterios repetibles: nombres, responsabilidades, duplicación, errores, pruebas, dependencias y claridad de interfaces.

En este tema construiremos una checklist práctica para detectar code smells en Python y aprenderemos a redactar observaciones útiles, concretas y priorizadas.

Objetivo práctico: revisar un fragmento de código Python, detectar smells y proponer mejoras accionables con prioridad clara.

27.2 Qué busca una revisión de calidad

Una revisión de calidad busca reducir riesgos antes de que el código se vuelva más costoso de cambiar. No se trata de imponer preferencias personales, sino de encontrar problemas que afectan mantenimiento, comportamiento o comprensión.

Una buena revisión detecta:

  • Errores probables.
  • Reglas ambiguas.
  • Code smells relevantes.
  • Falta de pruebas en zonas de riesgo.
  • Dependencias innecesarias.
  • Contratos difíciles de usar.

27.3 Preparar la revisión

Antes de revisar, ejecuta herramientas para quitar ruido:

python -m isort src tests
python -m black src tests
python -m ruff check src tests
python -m mypy src
python -m pytest

Las herramientas detectan problemas mecánicos. La revisión humana se concentra en intención, diseño y riesgos.

27.4 Checklist de nombres

Preguntas para revisar nombres:

  • ¿Las funciones comunican intención?
  • ¿Las variables usan términos del dominio?
  • ¿Hay abreviaturas innecesarias?
  • ¿Los booleanos se leen como preguntas?
  • ¿Las constantes nombran valores importantes?

Ejemplo problemático:

def p(x):
    return x["p"] * x["c"]

Mejor:

def calcular_importe(producto):
    return producto["precio"] * producto["cantidad"]

27.5 Checklist de responsabilidades

Preguntas:

  • ¿La función hace una cosa principal?
  • ¿Hay validación, cálculo, archivos y salida mezclados?
  • ¿La clase tiene más de una razón para cambiar?
  • ¿El módulo agrupa funciones relacionadas?

Una señal frecuente es encontrar una función que calcula y además imprime, guarda o envía datos.

27.6 Checklist de duplicación

Preguntas:

  • ¿Se repite una misma regla de negocio?
  • ¿Hay constantes duplicadas con el mismo significado?
  • ¿Dos funciones se parecen demasiado?
  • ¿La duplicación es accidental o representa reglas distintas?
if total < 10000:
    total += 1500

Si esta regla aparece en varios lugares, conviene extraer constantes o una función.

27.7 Checklist de condicionales

Preguntas:

  • ¿Hay demasiados niveles de anidamiento?
  • ¿Las condiciones complejas tienen nombre?
  • ¿Hay cadenas largas de elif que podrían ser tablas de reglas?
  • ¿Las negaciones dificultan la lectura?

Las cláusulas de guarda y las funciones de consulta suelen mejorar la lectura.

27.8 Checklist de errores

Preguntas:

  • ¿Se capturan excepciones demasiado genéricas?
  • ¿Hay errores silenciados?
  • ¿Los mensajes de error ayudan a corregir el problema?
  • ¿Se prueban los errores esperados?
try:
    total = calcular_total(productos)
except Exception:
    total = 0

Esta captura puede ocultar errores reales. La revisión debe pedir una estrategia más explícita.

27.9 Checklist de interfaces

Preguntas:

  • ¿La firma de la función es clara?
  • ¿Hay demasiados parámetros?
  • ¿Las banderas booleanas son ambiguas?
  • ¿El retorno es consistente?
  • ¿Los errores forman parte del contrato?

Una llamada como procesar(x, y, True, False) suele merecer revisión.

27.10 Checklist de pruebas

Preguntas:

  • ¿Hay pruebas para reglas importantes?
  • ¿Hay pruebas para casos borde?
  • ¿Hay pruebas para errores esperados?
  • ¿Las pruebas son legibles?
  • ¿Las pruebas dependen de archivos, fechas o estado global sin necesidad?

Una mejora de calidad sin pruebas puede ser riesgosa si cambia funciones compartidas.

27.11 Checklist de acoplamiento

Preguntas:

  • ¿Un módulo conoce demasiados detalles de otro?
  • ¿Hay imports circulares?
  • ¿Las reglas de negocio dependen de archivos o consola?
  • ¿Los modelos de datos están mezclados con presentación?

Una revisión debe buscar límites claros entre datos, reglas y salida.

27.12 Priorizar hallazgos

No todos los comentarios tienen la misma importancia. Prioriza por riesgo:

  • Alta: posible bug, pérdida de datos, error silenciado o comportamiento no probado.
  • Media: duplicación de reglas, función compleja o interfaz ambigua.
  • Baja: mejora de nombre o estilo que no afecta comprensión crítica.

Una revisión útil no bloquea por detalles menores si hay problemas importantes sin resolver.

27.13 Redactar comentarios útiles

Un comentario de revisión debe incluir problema, riesgo y sugerencia.

Poco útil: "Esto está mal".
Mejor: "Esta función captura Exception y devuelve 0. Eso puede ocultar errores de datos. Conviene capturar ValueError o validar productos antes de calcular."

27.14 Ejemplo de revisión

Revisa este código:

def hacer(items, c, pais):
    t = 0
    for x in items:
        try:
            t += x["p"] * x["q"]
        except Exception:
            pass
    if c == "vip":
        t *= 0.85
    if pais == "AR":
        t *= 1.21
    if t < 10000:
        t += 1500
    print(t)
    return t

Hallazgos posibles:

  • Nombres poco claros: hacer, c, t, x.
  • Excepción genérica silenciada.
  • Valores mágicos sin constantes.
  • Cálculo y salida por consola mezclados.
  • Estructura de productos basada en claves abreviadas.

27.15 Propuesta de mejora

DESCUENTO_VIP = 0.15
IVA_ARGENTINA = 0.21
LIMITE_ENVIO_GRATIS = 10000
COSTO_ENVIO = 1500


def calcular_subtotal(productos):
    subtotal = 0
    for producto in productos:
        subtotal += producto["precio"] * producto["cantidad"]
    return subtotal


def calcular_total(productos, tipo_cliente, pais):
    total = calcular_subtotal(productos)
    if tipo_cliente == "vip":
        total *= 1 - DESCUENTO_VIP
    if pais == "AR":
        total *= 1 + IVA_ARGENTINA
    if total < LIMITE_ENVIO_GRATIS:
        total += COSTO_ENVIO
    return total

La mejora no resuelve todo, pero reduce riesgos principales y deja la salida por consola fuera del cálculo.

27.16 Checklist compacta para usar

1. Nombres: ¿expresan intención? 2. Responsabilidades: ¿cada función hace una cosa principal? 3. Duplicación: ¿hay reglas repetidas? 4. Condicionales: ¿son simples y legibles? 5. Errores: ¿se manejan explícitamente? 6. Interfaces: ¿son fáciles de usar correctamente? 7. Pruebas: ¿cubren reglas y casos borde? 8. Acoplamiento: ¿los módulos tienen límites claros?

27.17 Ejercicio guiado

Aplica la checklist al siguiente fragmento y escribe cinco observaciones priorizadas:

def procesar(u, datos):
    r = []
    for d in datos:
        if d["ok"] == True:
            valor = d["a"] * d["b"]
            if valor > 5000:
                valor = valor - 300
            r.append({"u": u["email"], "v": valor})
    return r

Revisa nombres, booleanos, valores mágicos, estructura de datos y responsabilidades.

27.18 Ejercicio propuesto

En ventas_demo, realiza una revisión completa:

  • Ejecuta herramientas de calidad.
  • Elige un módulo para revisar manualmente.
  • Registra al menos cinco hallazgos.
  • Prioriza cada hallazgo como alto, medio o bajo.
  • Corrige un hallazgo de prioridad alta o media.
  • Ejecuta pruebas después del cambio.
python -m ruff check src tests
python -m mypy src
python -m pytest

27.19 Lista de verificación

Antes de continuar, verifica que puedes hacer lo siguiente:

  • Usar una checklist para revisar código Python.
  • Separar hallazgos de estilo, diseño y comportamiento.
  • Priorizar observaciones por riesgo.
  • Redactar comentarios con problema, riesgo y sugerencia.
  • Combinar herramientas automáticas con revisión humana.
  • Proponer cambios pequeños y verificables.

27.20 Conclusión

En este tema construimos una checklist práctica para revisar código y detectar code smells. Una buena revisión no busca demostrar conocimiento: busca reducir riesgo y mejorar la mantenibilidad del proyecto.

En el próximo tema trabajaremos con mejoras pequeñas y seguras guiadas por pruebas existentes.