6. Análisis estático con Ruff: detectar problemas antes de ejecutar

6.1 Objetivo del tema

El análisis estático revisa el código sin ejecutar el programa. Sirve para detectar problemas de estilo, imports sin usar, variables no utilizadas, errores comunes, complejidad innecesaria y muchas señales tempranas de baja calidad.

En este tema usaremos Ruff, una herramienta rápida para linting en Python. La instalaremos, la configuraremos en pyproject.toml y la aplicaremos sobre el proyecto ventas_demo.

Objetivo práctico: usar Ruff para detectar y corregir problemas antes de ejecutar el programa o enviar cambios a revisión.

6.2 Qué detecta una herramienta de linting

Un linter analiza el código y marca situaciones sospechosas. Algunas son simples problemas de estilo; otras pueden indicar errores reales.

Por ejemplo, Ruff puede detectar:

  • Imports no utilizados.
  • Variables definidas y nunca usadas.
  • Comparaciones innecesarias con True o False.
  • Funciones demasiado complejas, según la configuración.
  • Errores comunes de Python.
  • Problemas de formato que conviene unificar.

6.3 Ruff no reemplaza las pruebas

Ruff puede encontrar problemas sin ejecutar el programa, pero no comprueba que las reglas de negocio sean correctas. Las pruebas siguen siendo necesarias para verificar comportamiento.

Por ejemplo, Ruff puede detectar que descuento no se usa:

def calcular_total(precio, cantidad):
    descuento = 0.10
    return precio * cantidad

Pero no puede saber por sí solo si el cálculo de una promoción responde a la regla comercial esperada. Para eso necesitamos pruebas y revisión humana.

6.4 Activar el entorno virtual

Desde la carpeta del proyecto ventas_demo, activa el entorno virtual.

En Windows PowerShell:

.venv\Scripts\Activate.ps1

En Linux o macOS:

source .venv/bin/activate

Comprueba que estás en la carpeta que contiene pyproject.toml.

6.5 Instalar Ruff

Instala Ruff dentro del entorno virtual:

python -m pip install ruff

Verifica la instalación:

python -m ruff --version

Al igual que con otras herramientas, usamos python -m para ejecutar la versión instalada en el entorno activo.

6.6 Ejecutar Ruff por primera vez

Ejecuta Ruff sobre las carpetas principales del proyecto:

python -m ruff check src tests

Si no encuentra problemas, mostrará una salida breve. Si encuentra advertencias o errores, indicará el archivo, la línea y un código de regla.

Una salida típica puede mencionar reglas como F401 para imports sin usar o F841 para variables locales asignadas y no utilizadas.

6.7 Crear un archivo con problemas

Para practicar, crea src/problemas_ruff.py con este contenido:

import os
import math


def obtener_emails(usuarios):
    resultado = []
    contador = 0
    for usuario in usuarios:
        if usuario["activo"] == True:
            resultado.append(usuario["email"])
    return resultado

Este archivo tiene varios problemas: un import no usado, una variable no usada y una comparación booleana innecesaria.

6.8 Analizar el archivo problemático

Ejecuta Ruff sobre el archivo:

python -m ruff check src/problemas_ruff.py

Ruff debería señalar problemas similares a estos:

  • F401: import no utilizado.
  • F841: variable local asignada y no utilizada.
  • E712: comparación con True o False.

Los códigos pueden variar según la configuración activa, pero el objetivo es aprender a leer el diagnóstico.

6.9 Corrección manual

Una versión corregida del archivo sería:

def obtener_emails(usuarios):
    resultado = []
    for usuario in usuarios:
        if usuario["activo"]:
            resultado.append(usuario["email"])
    return resultado

Eliminamos imports que no se usan, quitamos la variable innecesaria y simplificamos la comparación booleana.

Vuelve a ejecutar:

python -m ruff check src/problemas_ruff.py

6.10 Corrección automática con --fix

Algunos problemas pueden corregirse automáticamente. Para probarlo, vuelve a dejar el archivo con imports sin usar y ejecuta:

python -m ruff check src/problemas_ruff.py --fix

Ruff puede eliminar imports no utilizados y aplicar algunas correcciones seguras. Aun así, siempre conviene revisar los cambios.

Usa --fix como ayuda, no como reemplazo de criterio. Después de una corrección automática, revisa el diff y ejecuta las pruebas.

6.11 Configurar Ruff en pyproject.toml

Agrega esta configuración al archivo pyproject.toml:

[tool.ruff]
line-length = 88
target-version = "py310"

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

Las reglas seleccionadas cubren estilo básico, errores frecuentes, recomendaciones de buenas prácticas, simplificaciones e imports.

6.12 Ruff y orden de imports

Ruff también puede revisar y ordenar imports si se habilita la familia de reglas I. Por ejemplo:

python -m ruff check src tests --select I

Y para corregir imports automáticamente:

python -m ruff check src tests --select I --fix

Si ya usas isort, puedes conservarlo o migrar gradualmente a Ruff. Lo importante es no tener dos configuraciones contradictorias.

6.13 Ruff como formateador

Ruff también incluye un formateador. En este curso venimos usando Black, por lo que mantendremos Black para formato e incorporaremos Ruff para análisis estático.

Si quisieras probar el formateador de Ruff, los comandos serían:

python -m ruff format src tests
python -m ruff format --check src tests

En un proyecto real conviene elegir una estrategia clara: Black para formato o Ruff para formato, pero no alternar sin criterio.

6.14 Aplicar Ruff al proyecto ventas_demo

Ejecuta el análisis sobre el proyecto completo:

python -m ruff check src tests

Corrige los problemas que Ruff señale. Luego ejecuta las herramientas en este orden:

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

Este flujo permite detectar problemas, ordenar imports, formatear y finalmente comprobar comportamiento.

6.15 Ejemplo de problema real: variable no usada

Supón que en ventas.py aparece una variable que quedó de un intento anterior:

def calcular_total_venta(productos, cliente, pais):
    subtotal = 0
    cantidad_productos = len(productos)
    for producto in productos:
        if producto["cantidad"] > 0:
            subtotal += producto["precio"] * producto["cantidad"]

    return subtotal

Si cantidad_productos no se usa, Ruff puede señalarlo. Esa advertencia ayuda a eliminar ruido y a detectar código incompleto.

6.16 Ejemplo de problema real: import no usado

Los imports sin usar pueden indicar código abandonado o dependencias innecesarias:

import os


def calcular_subtotal(productos):
    return sum(
        producto["precio"] * producto["cantidad"]
        for producto in productos
    )

Si os no se usa, conviene eliminarlo. Un archivo con imports mínimos es más fácil de entender.

6.17 Ejemplo de simplificación

Algunas reglas ayudan a simplificar expresiones innecesariamente verbosas.

Versión menos clara:

if usuario["activo"] == True:
    emails.append(usuario["email"])

Versión preferida:

if usuario["activo"]:
    emails.append(usuario["email"])

La segunda versión expresa la misma condición con menos ruido.

6.18 Ejercicio guiado

Crea el archivo src/usuarios.py con este contenido:

import os
import json


def filtrar_usuarios_activos(usuarios):
    usuarios_activos = []
    cantidad = 0
    for usuario in usuarios:
        if usuario["activo"] == True:
            usuarios_activos.append(usuario)
    return usuarios_activos

Ejecuta:

python -m ruff check src/usuarios.py

Corrige los problemas señalados y vuelve a ejecutar Ruff hasta que no informe advertencias.

6.19 Ejercicio propuesto

Agrega Ruff al flujo habitual del proyecto. Ejecuta:

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

Luego responde:

  • ¿Qué problemas detectó Ruff que Black no detecta?
  • ¿Qué cambios pudo corregir automáticamente?
  • ¿Qué advertencias requieren decisión humana?
  • ¿Las pruebas siguieron pasando después de las correcciones?

6.20 Lista de verificación

Antes de continuar, verifica que puedes hacer lo siguiente:

  • Instalar Ruff en un entorno virtual.
  • Ejecutar ruff check sobre archivos y carpetas.
  • Interpretar códigos como imports no usados y variables no usadas.
  • Aplicar correcciones automáticas con --fix.
  • Configurar Ruff en pyproject.toml.
  • Diferenciar linting, formateo y pruebas.
  • Ejecutar pruebas después de corregir advertencias.

6.21 Conclusión

En este tema incorporamos Ruff como herramienta de análisis estático. Aprendimos a detectar problemas antes de ejecutar el programa, corregir algunos automáticamente y configurar reglas básicas desde pyproject.toml.

En el próximo tema estudiaremos los code smells como concepto central: cómo reconocer señales de mala calidad, cuándo actuar y cuándo evitar cambios innecesarios.