29. Métricas de mantenibilidad y límites razonables para un proyecto Python

29.1 Objetivo del tema

Las métricas ayudan a observar tendencias y detectar zonas de riesgo. Pero una métrica no reemplaza la lectura del código. Un número puede señalar una función sospechosa, pero el criterio humano decide si el diseño es adecuado.

En este tema veremos métricas útiles para mantenibilidad en Python, límites razonables y formas prácticas de incorporarlas sin convertirlas en reglas ciegas.

Objetivo práctico: definir métricas simples de calidad para un proyecto Python y usarlas como señales de revisión, no como verdades absolutas.

29.2 Qué puede medir una métrica

Una métrica puede medir características visibles del código:

  • Complejidad de funciones.
  • Cantidad de líneas por función o archivo.
  • Duplicación aproximada.
  • Imports no usados o reglas de linting.
  • Cantidad de parámetros.
  • Presencia de type hints.
  • Resultado de pruebas automatizadas.

Estas señales ayudan, pero no miden directamente claridad, intención o calidad del dominio.

29.3 Métrica no es objetivo final

El objetivo no es “tener buenos números”. El objetivo es tener código fácil de entender, modificar y probar. Una métrica debe orientar conversaciones y revisiones.

Una métrica útil provoca una pregunta concreta: ¿por qué esta función es compleja?, ¿por qué este módulo creció tanto?, ¿qué riesgo estamos aceptando?

29.4 Complejidad ciclomática

La complejidad ciclomática mide caminos de decisión. En Ruff podemos activarla con C901.

[tool.ruff.lint]
select = ["E", "F", "B", "SIM", "I", "C901"]

[tool.ruff.lint.mccabe]
max-complexity = 6

Comando:

python -m ruff check src tests

29.5 Límites razonables de complejidad

Para un proyecto de práctica, un límite entre 5 y 8 puede ser útil para detectar funciones difíciles. En proyectos reales, el límite puede ser más alto al principio si ya existe código heredado.

  • 1 a 4: normalmente simple.
  • 5 a 8: revisar si la función sigue siendo clara.
  • 9 o más: candidata fuerte a división o rediseño.

29.6 Tamaño de funciones

La cantidad de líneas no define calidad, pero una función muy larga merece revisión. Como regla práctica, si una función ocupa una pantalla completa o tiene varias etapas internas, conviene mirarla con atención.

Preguntas útiles:

  • ¿La función tiene una sola responsabilidad?
  • ¿Tiene bloques que podrían tener nombre propio?
  • ¿Mezcla cálculo, validación y salida?
  • ¿Requiere muchas pruebas para cubrir caminos?

29.7 Cantidad de parámetros

Una función con muchos parámetros suele ser difícil de usar.

def generar_reporte(usuario, fecha, total, moneda, enviar_email, guardar_archivo, formato):
    ...

Límites orientativos:

  • 1 a 3 parámetros: normalmente cómodo.
  • 4 o 5 parámetros: revisar si hay datos agrupables.
  • Más de 5: señal fuerte para rediseñar la firma.

29.8 Duplicación

La duplicación exacta puede detectarse con herramientas, pero la duplicación de conocimiento requiere revisión humana. Una regla repetida con pequeñas variaciones puede ser más peligrosa que líneas idénticas.

Señales:

  • El mismo valor de negocio aparece en varios módulos.
  • Dos funciones cambian siempre juntas.
  • Las pruebas repiten datos complejos sin helper claro.
  • Hay validaciones duplicadas con mensajes distintos.

29.9 Cobertura conceptual

No estamos midiendo cobertura de código en este curso, pero sí podemos pensar en cobertura conceptual: ¿las reglas importantes tienen pruebas?

  • Descuentos principales.
  • Impuestos por país.
  • Validaciones de productos.
  • Errores esperados.
  • Casos borde.

Una métrica de líneas cubiertas no reemplaza esta pregunta.

29.10 Type hints como señal

La presencia de type hints ayuda a entender contratos. Puedes usar mypy como señal de calidad:

python -m mypy src

Un proyecto no necesita ser estricto desde el primer día, pero las funciones públicas y modelos de datos deberían tener anotaciones claras.

29.11 Linting como métrica de higiene

Ruff ayuda a mantener una base limpia:

python -m ruff check src tests

Un proyecto con muchos warnings acumulados tiende a normalizar problemas. Conviene mantener el conteo en cero o justificar excepciones.

29.12 Formato estable

Black e isort no miden diseño, pero eliminan discusiones de formato y hacen los diffs más claros.

python -m isort --check-only src tests
python -m black --check src tests

Si fallan, el problema no suele ser conceptual, pero sí afecta consistencia del proyecto.

29.13 Crear un tablero simple de calidad

Para un proyecto pequeño, un tablero puede ser una lista de comandos y criterios:

Ruff sin errores. Black e isort sin cambios pendientes. mypy sin errores en src. pytest en verde. Ninguna función nueva con complejidad mayor a 6. Funciones públicas con type hints.

29.14 Aplicación sobre ventas_demo

Ejecuta:

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

Registra el resultado. Si algo falla, clasifica el problema:

  • Formato.
  • Linting.
  • Tipos.
  • Pruebas.
  • Diseño o mantenibilidad.

29.15 Instalar radon opcionalmente

Otra herramienta útil para explorar métricas es radon. Puedes instalarla:

python -m pip install radon

Medir complejidad:

python -m radon cc src -s

Medir mantenibilidad:

python -m radon mi src

Usa estos resultados como señales de revisión, no como sentencia automática.

29.16 Definir límites por etapa

No todos los proyectos pueden adoptar límites estrictos de inmediato. Una estrategia pragmática:

  • Primero medir y entender el estado actual.
  • Evitar que código nuevo empeore los indicadores.
  • Corregir zonas tocadas por cambios reales.
  • Subir la exigencia gradualmente.

29.17 Ejercicio guiado

Define límites para un proyecto pequeño:

  • Complejidad máxima por función: 6.
  • Funciones públicas con type hints.
  • Ruff sin errores.
  • Black e isort sin cambios pendientes.
  • Pruebas principales en verde.

Luego ejecuta los comandos de revisión y anota qué límite falla primero.

29.18 Ejercicio propuesto

En ventas_demo, realiza estas tareas:

  • Ejecuta herramientas de calidad.
  • Define tres límites razonables para el proyecto.
  • Identifica una función candidata a mejora por métrica.
  • Revisa si la métrica coincide con tu lectura humana.
  • Documenta los límites en el README.
python -m ruff check src tests
python -m mypy src
python -m pytest

29.19 Lista de verificación

Antes de continuar, verifica que puedes hacer lo siguiente:

  • Usar métricas como señales, no como objetivos ciegos.
  • Configurar límites de complejidad con Ruff.
  • Revisar tamaño de funciones y cantidad de parámetros.
  • Distinguir duplicación exacta de duplicación de conocimiento.
  • Combinar linting, tipos, formato y pruebas.
  • Definir límites razonables según la etapa del proyecto.

29.20 Conclusión

En este tema vimos que las métricas ayudan a detectar zonas de riesgo, pero no reemplazan el criterio técnico. Un buen equipo usa números para orientar preguntas, no para evitar pensar.

En el próximo tema cerraremos el curso con un caso práctico integrador: análisis y mejora de un proyecto Python con code smells.