En el tema anterior extraímos funciones para hacer visible la intención del código. Pero no toda variable intermedia ni toda función pequeña mejora el diseño. A veces una abstracción sobra, obliga a saltar entre lugares y hace que una operación simple parezca más compleja de lo que es.
En este tema practicaremos el refactoring inverso: aplicar inline de variables y funciones. Es decir, reemplazar una variable o función por su expresión directa cuando el nombre no agrega claridad o la abstracción dejó de tener sentido.
Aplicar inline significa tomar el contenido de una variable o función y colocarlo directamente donde se usa. Por ejemplo, si una variable solo repite una expresión obvia, podemos quitarla.
subtotal = precio * cantidad
return subtotal
Puede quedar más simple como:
return precio * cantidad
El comportamiento es el mismo, pero hay menos ruido. La clave es no eliminar nombres que sí expresan una idea importante del dominio.
El inline suele ser útil cuando:
No debemos eliminar nombres valiosos. Una variable o función puede ser corta y aun así importante si comunica intención.
Por ejemplo, esta variable agrega significado:
supera_limite_envio_gratis = total >= LIMITE_ENVIO_GRATIS
if supera_limite_envio_gratis:
return 0
Podríamos escribir la condición directamente, pero el nombre explica una regla de negocio. En ese caso, el inline puede empeorar la lectura.
Crea el archivo src/cupones.py con este código:
def obtener_porcentaje_descuento(cupon):
if cupon == "DESC10":
return 0.10
if cupon == "DESC20":
return 0.20
return 0
def calcular_descuento_porcentaje(total, porcentaje):
descuento = total * porcentaje
return descuento
def aplicar_descuento(total, cupon):
porcentaje = obtener_porcentaje_descuento(cupon)
descuento = calcular_descuento_porcentaje(total, porcentaje)
total_con_descuento = total - descuento
return total_con_descuento
def calcular_total(items, cupon):
subtotal = 0
for item in items:
importe = item["precio"] * item["cantidad"]
subtotal = subtotal + importe
total = aplicar_descuento(subtotal, cupon)
resultado = round(total, 2)
return resultado
El código funciona, pero algunas variables y funciones son intermediarios que no aportan demasiado.
Crea tests/test_cupones.py:
from cupones import calcular_total
def test_calcula_total_con_cupon_desc10():
items = [
{"precio": 1000, "cantidad": 2},
{"precio": 500, "cantidad": 1},
]
assert calcular_total(items, "DESC10") == 2250.0
def test_calcula_total_sin_cupon_valido():
items = [
{"precio": 800, "cantidad": 3},
]
assert calcular_total(items, "NO_EXISTE") == 2400
Ejecuta:
python -m pytest tests/test_cupones.py
En calcular_descuento_porcentaje, la variable descuento solo repite la expresión y se devuelve inmediatamente.
def calcular_descuento_porcentaje(total, porcentaje):
descuento = total * porcentaje
return descuento
Podemos simplificarla así:
def calcular_descuento_porcentaje(total, porcentaje):
return total * porcentaje
Después del cambio, ejecuta python -m pytest tests/test_cupones.py.
Ahora revisamos calcular_descuento_porcentaje. Su nombre no agrega mucho más que la expresión total * porcentaje. Además, solo se usa en un lugar.
Podemos reemplazar la llamada:
descuento = calcular_descuento_porcentaje(total, porcentaje)
Por la expresión directa:
descuento = total * porcentaje
Luego eliminamos la función calcular_descuento_porcentaje si no se usa en ningún otro lugar.
El archivo queda así:
def obtener_porcentaje_descuento(cupon):
if cupon == "DESC10":
return 0.10
if cupon == "DESC20":
return 0.20
return 0
def aplicar_descuento(total, cupon):
porcentaje = obtener_porcentaje_descuento(cupon)
descuento = total * porcentaje
total_con_descuento = total - descuento
return total_con_descuento
def calcular_total(items, cupon):
subtotal = 0
for item in items:
importe = item["precio"] * item["cantidad"]
subtotal = subtotal + importe
total = aplicar_descuento(subtotal, cupon)
resultado = round(total, 2)
return resultado
Ejecuta las pruebas. Si pasan, eliminamos una función innecesaria sin cambiar resultados.
En aplicar_descuento, todavía hay variables que podrían simplificarse. La variable total_con_descuento se usa solo para devolver el resultado.
def aplicar_descuento(total, cupon):
porcentaje = obtener_porcentaje_descuento(cupon)
descuento = total * porcentaje
return total - descuento
También podríamos eliminar descuento:
def aplicar_descuento(total, cupon):
porcentaje = obtener_porcentaje_descuento(cupon)
return total - total * porcentaje
En este caso la versión sigue siendo clara porque la fórmula es corta.
La variable porcentaje podría eliminarse, pero tal vez conviene conservarla:
return total - total * obtener_porcentaje_descuento(cupon)
La expresión es válida, pero puede resultar menos legible. La variable porcentaje ayuda a contar el cálculo paso a paso. El inline no es una regla automática: se aplica cuando mejora la lectura.
En calcular_total, la variable resultado tampoco agrega intención:
total = aplicar_descuento(subtotal, cupon)
resultado = round(total, 2)
return resultado
Podemos simplificar:
total = aplicar_descuento(subtotal, cupon)
return round(total, 2)
Después ejecuta python -m pytest.
Una versión equilibrada puede quedar así:
def obtener_porcentaje_descuento(cupon):
if cupon == "DESC10":
return 0.10
if cupon == "DESC20":
return 0.20
return 0
def aplicar_descuento(total, cupon):
porcentaje = obtener_porcentaje_descuento(cupon)
return total - total * porcentaje
def calcular_total(items, cupon):
subtotal = 0
for item in items:
subtotal = subtotal + item["precio"] * item["cantidad"]
total = aplicar_descuento(subtotal, cupon)
return round(total, 2)
No eliminamos todas las funciones. Conservamos obtener_porcentaje_descuento y aplicar_descuento porque expresan reglas relevantes.
Antes de borrar una función, confirma que no se use en otros archivos:
rg "calcular_descuento_porcentaje" .
Si aparece en producción, pruebas o documentación, revisa cada referencia. El inline de una función pública puede afectar a otros módulos aunque las pruebas locales pasen.
Si una función forma parte de la API pública del módulo, eliminarla puede romper código externo. En ese caso, una opción intermedia es conservarla temporalmente como función delegada y marcarla para migración posterior.
def calcular_descuento_porcentaje(total, porcentaje):
return total * porcentaje
Esto no simplifica el módulo, pero evita una ruptura inmediata. La decisión depende de quién usa esa función.
Crea el archivo src/pagos.py:
def obtener_recargo(metodo):
if metodo == "tarjeta":
return 0.08
return 0
def calcular_monto_recargo(total, recargo):
monto = total * recargo
return monto
def sumar_recargo(total, metodo):
recargo = obtener_recargo(metodo)
monto_recargo = calcular_monto_recargo(total, recargo)
total_final = total + monto_recargo
return total_final
def calcular_pago(items, metodo):
subtotal = 0
for item in items:
precio = item["precio"]
cantidad = item["cantidad"]
subtotal = subtotal + precio * cantidad
total = sumar_recargo(subtotal, metodo)
respuesta = round(total, 2)
return respuesta
Realiza estas tareas:
calcular_monto_recargo aporta claridad o puede eliminarse.python -m pytest después de cada cambio.Una versión simplificada podría quedar así:
def obtener_recargo(metodo):
if metodo == "tarjeta":
return 0.08
return 0
def sumar_recargo(total, metodo):
recargo = obtener_recargo(metodo)
return total + total * recargo
def calcular_pago(items, metodo):
subtotal = 0
for item in items:
subtotal = subtotal + item["precio"] * item["cantidad"]
total = sumar_recargo(subtotal, metodo)
return round(total, 2)
La función calcular_monto_recargo desapareció porque solo envolvía una multiplicación simple. En cambio, obtener_recargo se conserva porque expresa una regla que puede crecer.
Antes de continuar, verifica que puedes explicar estos puntos:
En este tema practicamos inline de variables y funciones para eliminar intermediarios innecesarios. Vimos que refactorizar no siempre significa agregar abstracciones: a veces el mejor diseño aparece al quitar nombres, funciones o capas que ya no aportan claridad.
En el próximo tema trabajaremos con una separación fundamental: distinguir consultas de modificaciones para reducir efectos secundarios.