2. Preparar un proyecto de práctica para analizar y mejorar código

2.1 Objetivo del tema

Para estudiar calidad de código necesitamos algo más útil que ejemplos sueltos. En este tema construiremos un pequeño proyecto Python que usaremos como laboratorio durante el curso. El proyecto tendrá código que funciona, pero que presenta problemas intencionales de legibilidad, diseño y mantenibilidad.

La idea es trabajar como ocurre muchas veces en proyectos reales: recibimos código existente, necesitamos entenderlo, proteger su comportamiento y luego mejorarlo paso a paso.

Objetivo práctico: crear un proyecto Python con estructura ordenada, código inicial con code smells y pruebas de caracterización para analizarlo con seguridad.

2.2 Crear la carpeta del curso

Abre una terminal y crea una carpeta para las prácticas:

mkdir curso-calidad-codigo
cd curso-calidad-codigo

Dentro de esta carpeta crearemos un proyecto llamado ventas_demo. Será una aplicación pequeña para calcular totales de ventas con descuentos, impuestos y comisiones.

mkdir ventas_demo
cd ventas_demo

2.3 Crear el entorno virtual

Un entorno virtual permite instalar herramientas para este proyecto sin afectar otros trabajos de Python. Desde la carpeta ventas_demo, ejecuta:

python -m venv .venv

En Windows PowerShell, actívalo con:

.venv\Scripts\Activate.ps1

En Linux o macOS:

source .venv/bin/activate

Cuando el entorno está activo, normalmente aparece (.venv) al comienzo de la línea de comandos.

2.4 Instalar herramientas mínimas

Por ahora instalaremos pytest para ejecutar pruebas de caracterización. Más adelante agregaremos herramientas de formato, linting, tipado y métricas.

python -m pip install --upgrade pip
python -m pip install pytest

Verifica la instalación:

python -m pytest --version
Una prueba de caracterización describe cómo se comporta hoy un código existente. No necesariamente afirma que el diseño sea bueno; nos ayuda a no romper el comportamiento mientras mejoramos el código.

2.5 Crear la estructura inicial

Crea estas carpetas y archivos:

mkdir src
mkdir tests
New-Item src\ventas.py
New-Item tests\test_ventas.py
New-Item README.md
New-Item pyproject.toml

En Linux o macOS puedes usar:

mkdir src tests
touch src/ventas.py tests/test_ventas.py README.md pyproject.toml

La estructura debería quedar así:

ventas_demo/
|-- .venv/
|-- README.md
|-- pyproject.toml
|-- src/
|   `-- ventas.py
`-- tests/
    `-- test_ventas.py

2.6 Configurar pyproject.toml

El archivo pyproject.toml permite centralizar configuración del proyecto. Por ahora lo usaremos para indicar datos básicos y configurar pytest.

[project]
name = "ventas-demo"
version = "0.1.0"
description = "Proyecto de práctica para calidad de código y code smells"
requires-python = ">=3.10"

[tool.pytest.ini_options]
pythonpath = ["src"]
testpaths = ["tests"]

La opción pythonpath = ["src"] permite importar módulos desde la carpeta src durante las pruebas.

2.7 Crear código con problemas intencionales

Ahora escribiremos una primera versión de src/ventas.py. El código tiene varios problemas de calidad a propósito. No lo corrijas todavía; lo usaremos para practicar análisis.

def proc(items, cliente, pais):
    t = 0
    for x in items:
        if x["cant"] > 0:
            t = t + x["precio"] * x["cant"]

    if cliente == "vip":
        t = t - (t * 0.15)
    else:
        if cliente == "regular":
            t = t - (t * 0.05)

    if pais == "AR":
        t = t + (t * 0.21)
    else:
        if pais == "UY":
            t = t + (t * 0.22)
        else:
            t = t + (t * 0.19)

    if t < 10000:
        t = t + 1500

    return round(t, 2)

Este código calcula un total, pero presenta nombres pobres, valores mágicos, condicionales anidados y demasiadas responsabilidades en una sola función.

2.8 Probar manualmente el código

Agrega temporalmente estas líneas al final de src/ventas.py para ver el resultado:

items = [
    {"precio": 3000, "cant": 2},
    {"precio": 1500, "cant": 1},
]

print(proc(items, "vip", "AR"))

Ejecuta:

python src/ventas.py

Luego elimina esas líneas temporales. En un proyecto ordenado evitaremos dejar ejemplos manuales mezclados con la lógica principal.

2.9 Crear pruebas de caracterización

En tests/test_ventas.py escribiremos pruebas que registren el comportamiento actual de la función. Estas pruebas nos servirán como red de seguridad cuando empecemos a mejorar el código.

from ventas import proc


def test_calcula_total_para_cliente_vip_en_argentina():
    items = [
        {"precio": 3000, "cant": 2},
        {"precio": 1500, "cant": 1},
    ]

    assert proc(items, "vip", "AR") == 9213.75


def test_agrega_envio_cuando_el_total_es_bajo():
    items = [
        {"precio": 1000, "cant": 1},
    ]

    assert proc(items, "regular", "UY") == 2659.0


def test_ignora_items_con_cantidad_cero_o_negativa():
    items = [
        {"precio": 3000, "cant": 1},
        {"precio": 9000, "cant": 0},
        {"precio": 9000, "cant": -2},
    ]

    assert proc(items, "nuevo", "CL") == 5070.0

Ejecuta las pruebas:

python -m pytest

Si todo está bien, las tres pruebas deben pasar.

2.10 Registrar el propósito del proyecto

En README.md escribe una descripción breve. No hace falta documentar todo, pero sí conviene dejar claro para qué existe el proyecto.

# Ventas Demo

Proyecto de práctica para analizar calidad de código y code smells en Python.

El módulo inicial contiene código que funciona, pero tiene problemas intencionales
de legibilidad, nombres, responsabilidades, condicionales y valores mágicos.

Durante el curso se irá mejorando el diseño sin cambiar el comportamiento esperado.

2.11 Primer análisis del proyecto

Antes de usar herramientas automáticas, realiza una lectura manual de src/ventas.py. Anota los problemas que encuentres. Por ejemplo:

  • La función proc no comunica qué proceso realiza.
  • Los nombres t y x obligan a deducir su significado.
  • La clave cant es una abreviatura que puede confundirse.
  • Los porcentajes de descuento e impuestos aparecen como números sueltos.
  • La función calcula subtotal, descuento, impuesto, envío y redondeo.
  • Los condicionales anidados dificultan seguir el flujo.

Este análisis manual es importante. Las herramientas ayudan, pero no reemplazan el criterio del programador.

2.12 Qué no vamos a corregir todavía

Puede ser tentador mejorar el código de inmediato, pero en este tema solo estamos preparando el laboratorio. No cambiaremos aún la función proc, porque primero queremos tener una base común para practicar los próximos temas.

Regla de trabajo: antes de modificar código existente, entender su comportamiento actual y dejar una forma simple de comprobar que sigue funcionando.

2.13 Ejercicio propuesto

Agrega una cuarta prueba de caracterización para el caso de un cliente "regular" en Uruguay con dos productos. Puedes usar estos datos:

items = [
    {"precio": 2000, "cant": 2},
    {"precio": 1000, "cant": 3},
]

Calcula el resultado esperado manualmente o ejecutando la función una vez. Luego deja ese resultado fijo en la prueba. Después ejecuta:

python -m pytest

La prueba debe pasar junto con las anteriores.

2.14 Lista de verificación

Antes de continuar con el próximo tema, verifica lo siguiente:

  • El proyecto ventas_demo tiene carpetas src y tests.
  • El entorno virtual está creado y se puede activar.
  • pytest está instalado en el entorno virtual.
  • El archivo pyproject.toml configura pythonpath y testpaths.
  • El archivo src/ventas.py contiene código con problemas intencionales.
  • Las pruebas de tests/test_ventas.py pasan correctamente.
  • Puedes enumerar al menos cinco code smells del archivo ventas.py.

2.15 Conclusión

En este tema preparamos un proyecto de práctica para trabajar calidad de código de manera concreta. Creamos una estructura simple, configuramos el entorno, agregamos una función con problemas intencionales y escribimos pruebas de caracterización para proteger el comportamiento actual.

En el próximo tema nos enfocaremos en la legibilidad: nombres, intención y estructura visual del código. Usaremos este proyecto para empezar a detectar mejoras pequeñas que hacen que un programa sea más fácil de entender.