7. Inline de variables y funciones cuando una abstracción sobra

7.1 Objetivo del tema

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.

Objetivo práctico: eliminar intermediarios innecesarios en código Python sin cambiar el comportamiento observado.

7.2 Qué significa aplicar inline

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.

7.3 Cuándo conviene aplicar inline

El inline suele ser útil cuando:

  • Una variable solo se usa una vez y su nombre no agrega significado.
  • Una función simplemente llama a otra función sin adaptar nada.
  • Una función extraída quedó demasiado pequeña y obliga a saltar sin beneficio.
  • Un nombre intermedio quedó desactualizado después de otros refactorings.
  • Una abstracción oculta una regla que sería más clara en el lugar de uso.

7.4 Cuándo no conviene aplicar inline

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.

7.5 Código inicial

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.

7.6 Pruebas antes de simplificar

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

7.7 Inline de una variable obvia

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.

7.8 Inline de una función que no agrega intención

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.

7.9 Resultado después del primer inline de función

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.

7.10 Inline de variables temporales

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.

7.11 Cuándo conservar una variable intermedia

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.

7.12 Inline en la función principal

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.

7.13 Código final simplificado

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.

7.14 Buscar usos antes de eliminar una función

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.

7.15 Inline y API pública

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.

7.16 Ejercicio propuesto

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:

  • Escribe dos pruebas antes de modificar el código.
  • Aplica inline a variables que solo se devuelven inmediatamente.
  • Evalúa si calcular_monto_recargo aporta claridad o puede eliminarse.
  • Conserva las funciones que expresen reglas de negocio importantes.
  • Ejecuta python -m pytest después de cada cambio.

7.17 Una posible solución

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.

7.18 Lista de verificación

Antes de continuar, verifica que puedes explicar estos puntos:

  • Qué significa aplicar inline a una variable.
  • Qué significa aplicar inline a una función.
  • Cómo distinguir una abstracción útil de una abstracción que sobra.
  • Por qué conviene buscar usos antes de eliminar una función.
  • Qué riesgos aparecen cuando la función forma parte de una API pública.
  • Por qué las pruebas deben pasar después de cada simplificación.

7.19 Conclusión

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.