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.
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:
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.
Antes de cambiar el código, conviene describir los problemas con precisión. En el ejemplo anterior podemos observar:
0.15, 0.05, 0.21, 10000 y 1200 aparecen sin explicación.else anidado agrega ruido visual.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.
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.
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:
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.
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.
Cuando revises un fragmento de código Python, puedes hacerte estas preguntas:
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.
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:
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.
Antes de continuar con el próximo tema, verifica que puedes explicar estos puntos:
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.