1. Qué significa calidad de código en un proyecto Python

1.1 Objetivo del tema

La calidad de código no significa que el programa solamente funcione. Un programa puede dar el resultado correcto y, al mismo tiempo, ser difícil de leer, modificar, probar o extender. En proyectos reales, esa dificultad se convierte en tiempo perdido, errores repetidos y miedo a cambiar el código.

En este primer tema aprenderemos a mirar un archivo Python con criterio de calidad. Vamos a identificar señales tempranas de problemas, comparar una solución confusa con una versión más clara y construir una pequeña lista de verificación para usar durante el resto del curso.

Objetivo práctico: analizar un fragmento de código Python, detectar problemas de calidad y proponer una versión más legible y mantenible.

1.2 Qué entendemos por calidad de código

La calidad de código es el conjunto de características que hacen que un programa sea fácil de comprender, cambiar y verificar. No depende solo de la sintaxis ni de las herramientas. También depende de las decisiones de diseño que tomamos al nombrar variables, dividir funciones, separar responsabilidades y manejar errores.

En Python, un código de buena calidad suele tener estas propiedades:

  • Legibilidad: se puede entender la intención sin leerlo diez veces.
  • Simplicidad: resuelve el problema sin estructuras innecesarias.
  • Mantenibilidad: permite cambios pequeños sin romper muchas partes.
  • Claridad de responsabilidades: cada función, clase o módulo tiene un propósito reconocible.
  • Facilidad de prueba: el comportamiento puede verificarse con pruebas automatizadas sin demasiados obstáculos.
  • Consistencia: el estilo, los nombres y la organización siguen criterios uniformes.

1.3 Código que funciona, pero no es de buena calidad

Observa el siguiente ejemplo. La función calcula el total de una compra aplicando descuento, impuesto y costo de envío. El resultado puede ser correcto, pero el código tiene varios problemas de calidad.

def calc(c, t):
    x = 0
    for i in c:
        x = x + i["p"] * i["q"]
    if t == "vip":
        x = x - x * 0.15
    else:
        if t == "regular":
            x = x - x * 0.05
    x = x + x * 0.21
    if x < 10000:
        x = x + 1200
    return x

Si ya conocemos el contexto, tal vez podamos interpretarlo. Pero un compañero que vea esta función por primera vez tendrá que adivinar qué significan c, t, x, p y q. Además, la función mezcla varias reglas de negocio en un solo bloque.

1.4 Primer diagnóstico

Antes de cambiar el código, conviene describir los problemas con precisión. En el ejemplo anterior podemos observar:

  • Nombres poco expresivos: las variables no comunican intención.
  • Valores mágicos: 0.15, 0.05, 0.21, 10000 y 1200 aparecen sin explicación.
  • Responsabilidades mezcladas: la función calcula subtotal, descuento, impuesto y envío.
  • Condicionales mejorables: el else anidado agrega ruido visual.
  • Estructura de datos poco clara: los diccionarios usan claves abreviadas.
Diagnosticar no es criticar por gusto. Diagnosticar es encontrar qué parte del código puede generar errores, confusión o costo de mantenimiento.

1.5 Una versión más clara

Podemos mejorar la misma lógica usando nombres explícitos, constantes y funciones pequeñas. Todavía no estamos haciendo un refactoring avanzado; solo estamos ordenando el código para que la intención sea visible.

IVA = 0.21
DESCUENTO_VIP = 0.15
DESCUENTO_REGULAR = 0.05
LIMITE_ENVIO_GRATIS = 10000
COSTO_ENVIO = 1200


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


def obtener_descuento(tipo_cliente):
    if tipo_cliente == "vip":
        return DESCUENTO_VIP
    if tipo_cliente == "regular":
        return DESCUENTO_REGULAR
    return 0


def calcular_total(productos, tipo_cliente):
    subtotal = calcular_subtotal(productos)
    descuento = obtener_descuento(tipo_cliente)
    total_con_descuento = subtotal * (1 - descuento)
    total_con_iva = total_con_descuento * (1 + IVA)

    if total_con_iva < LIMITE_ENVIO_GRATIS:
        return total_con_iva + COSTO_ENVIO

    return total_con_iva

La versión mejorada tiene más líneas, pero es más fácil de leer. La calidad no consiste siempre en escribir menos código; consiste en escribir código cuya intención sea clara y cuyo cambio sea razonable.

1.6 Ejecutar el ejemplo

Para practicar, crea un archivo llamado calidad_demo.py y copia la versión mejorada. Al final del archivo agrega:

productos = [
    {"precio": 3000, "cantidad": 2},
    {"precio": 1500, "cantidad": 1},
]

total = calcular_total(productos, "vip")
print(f"Total: {total:.2f}")

Ejecuta el archivo desde la terminal:

python calidad_demo.py

El objetivo de este ejercicio no es memorizar el cálculo, sino observar cómo cambia la lectura del programa cuando los nombres y las responsabilidades están mejor definidos.

1.7 Qué es un code smell

Un code smell, u olor de código, es una señal de que podría existir un problema de diseño, legibilidad o mantenibilidad. No siempre indica un error directo. Muchas veces el programa funciona, pero el código sugiere que será difícil modificarlo o entenderlo en el futuro.

Algunos ejemplos frecuentes son:

  • Funciones demasiado largas.
  • Nombres ambiguos o abreviados sin necesidad.
  • Código duplicado.
  • Condicionales muy anidados.
  • Clases que hacen demasiadas cosas.
  • Datos globales modificados desde distintos lugares.
  • Excepciones capturadas y silenciadas sin explicación.

1.8 Code smell no significa bug

Es importante separar dos ideas. Un bug es un comportamiento incorrecto: el programa no hace lo que debería hacer. Un code smell es una señal de riesgo: el código puede funcionar hoy, pero su forma aumenta la probabilidad de errores futuros.

Por ejemplo, esta función puede funcionar correctamente:

def procesar(d):
    return d["a"] * d["b"] - d["c"]

El problema es que no sabemos qué representan a, b y c. Si mañana cambia una regla de negocio, será fácil equivocarse. El olor está en la falta de intención visible.

1.9 Calidad y pruebas automatizadas

Las pruebas automatizadas ayudan a comprobar que el comportamiento sigue siendo correcto, pero no reemplazan la calidad de código. Un proyecto puede tener pruebas y, aun así, tener funciones enormes, duplicación y nombres confusos.

Al mismo tiempo, el código de buena calidad suele ser más fácil de probar. Observa esta diferencia:

# Difícil de probar: lee datos, calcula y muestra resultados en la misma función.
def generar_reporte():
    archivo = open("ventas.txt", encoding="utf-8")
    ventas = [float(linea) for linea in archivo]
    total = sum(ventas)
    print(f"Total vendido: {total}")
# Más fácil de probar: el cálculo está separado de la entrada y la salida.
def calcular_total_ventas(ventas):
    return sum(ventas)


def mostrar_total_ventas(total):
    print(f"Total vendido: {total}")

Separar responsabilidades mejora la comprensión del código y también facilita verificar el comportamiento con pruebas.

1.10 Preguntas prácticas para evaluar calidad

Cuando revises un fragmento de código Python, puedes hacerte estas preguntas:

  • ¿Entiendo qué hace la función por su nombre?
  • ¿Los nombres de variables explican el dominio del problema?
  • ¿La función tiene una sola responsabilidad principal?
  • ¿Hay números o textos importantes sin nombre propio?
  • ¿Puedo probar la lógica sin depender de archivos, red, reloj o consola?
  • ¿Puedo cambiar una regla sin modificar muchas partes del programa?
  • ¿El código evita duplicar decisiones de negocio?

1.11 Ejercicio guiado

Analiza el siguiente código y anota al menos cinco problemas de calidad antes de mirar una posible mejora.

def r(u):
    if u["e"] == "":
        return "error"
    if "@" not in u["e"]:
        return "error"
    if u["a"] < 18:
        return "error"
    if u["p"] == "":
        return "error"
    return "ok"

Una versión más clara podría ser:

EDAD_MINIMA = 18


def validar_usuario(usuario):
    email = usuario["email"]
    edad = usuario["edad"]
    password = usuario["password"]

    if not email:
        return "El email es obligatorio"
    if "@" not in email:
        return "El email no tiene un formato válido"
    if edad < EDAD_MINIMA:
        return "El usuario debe ser mayor de edad"
    if not password:
        return "La contraseña es obligatoria"

    return "ok"

La mejora principal está en la intención: ahora sabemos qué se valida, qué datos se esperan y cuál es el motivo de cada error.

1.12 Ejercicio propuesto

Crea un archivo llamado ejercicio_tema1.py con el siguiente código:

def f(items):
    s = 0
    for x in items:
        if x["st"] == "ok":
            s = s + x["v"]
    if s > 5000:
        s = s - 300
    return s

Realiza estas tareas:

  • Cambia los nombres para que expresen la intención del código.
  • Reemplaza los valores mágicos por constantes.
  • Divide la lógica si encuentras más de una responsabilidad.
  • Agrega un pequeño ejemplo de uso con una lista de datos.
  • Comprueba que la nueva versión devuelve el mismo resultado que la original.

1.13 Una posible solución

Una forma de mejorar el ejercicio es la siguiente:

ESTADO_APROBADO = "ok"
LIMITE_DESCUENTO = 5000
DESCUENTO = 300


def sumar_valores_aprobados(items):
    total = 0
    for item in items:
        if item["estado"] == ESTADO_APROBADO:
            total += item["valor"]
    return total


def aplicar_descuento_si_corresponde(total):
    if total > LIMITE_DESCUENTO:
        return total - DESCUENTO
    return total


def calcular_total(items):
    total_aprobado = sumar_valores_aprobados(items)
    return aplicar_descuento_si_corresponde(total_aprobado)


items = [
    {"estado": "ok", "valor": 3000},
    {"estado": "pendiente", "valor": 2000},
    {"estado": "ok", "valor": 2500},
]

print(calcular_total(items))

Esta no es la única solución correcta. En calidad de código suele haber varias opciones válidas. Lo importante es justificar por qué una versión comunica mejor la intención y reduce el riesgo de errores.

1.14 Lista de verificación

Antes de continuar con el próximo tema, verifica que puedes explicar estos puntos:

  • La diferencia entre código que funciona y código mantenible.
  • Por qué los nombres expresivos reducen errores.
  • Qué es un code smell y por qué no siempre es un bug.
  • Cómo detectar valores mágicos en una función Python.
  • Por qué dividir responsabilidades facilita leer y probar el código.
  • Qué preguntas hacer durante una revisión básica de calidad.

1.15 Conclusión

En este tema vimos que la calidad de código se relaciona con la facilidad para entender, cambiar y verificar un programa. También analizamos los primeros code smells: nombres confusos, valores mágicos, responsabilidades mezcladas y estructuras difíciles de seguir.

A partir del próximo tema construiremos un proyecto de práctica para aplicar estas ideas de manera progresiva. Ese proyecto nos permitirá observar problemas reales, medirlos con herramientas y mejorar el código con cambios pequeños y controlados.