Renombrar es una de las refactorizaciones más simples y más valiosas. Un buen nombre reduce la cantidad de comentarios necesarios, evita interpretaciones incorrectas y permite entender una regla de negocio sin leer todos los detalles de implementación.
En este tema practicaremos el renombrado de variables, funciones y clases en Python. Lo haremos en pasos pequeños, ejecutando pruebas después de cada cambio importante para confirmar que solo cambió la forma de expresar el código, no su comportamiento.
Un nombre es parte del diseño. Cuando una variable se llama x, una función se llama procesar o una clase se llama Manager, el lector debe descubrir la intención mirando el cuerpo del código. Esa carga mental se acumula y vuelve más costoso modificar el sistema.
Renombrar no agrega una funcionalidad nueva. Tampoco corrige una regla de negocio. Si se hace bien, conserva el comportamiento y mejora la comunicación del código.
Crea el archivo src/promociones.py con este código:
class Proc:
def __init__(self, d):
self.d = d
def calc(self, u):
r = 0
for x in self.d:
if x["a"] == u["t"]:
r = r + x["v"]
if u["c"] > 10:
r = r + 5
if u["p"]:
r = r * 2
return r
El código es corto, pero cuesta entenderlo. No sabemos qué representa Proc, qué contiene d, qué significa calc ni qué datos son a, v, t, c y p.
Antes de cambiar nombres, escribimos pruebas que capturen el comportamiento actual. Crea tests/test_promociones.py:
from promociones import Proc
def test_calcula_puntos_para_usuario_premium():
reglas = [
{"a": "oro", "v": 20},
{"a": "plata", "v": 10},
{"a": "oro", "v": 5},
]
usuario = {"t": "oro", "c": 12, "p": True}
assert Proc(reglas).calc(usuario) == 60
def test_calcula_puntos_para_usuario_no_premium_con_pocas_compras():
reglas = [
{"a": "oro", "v": 20},
{"a": "plata", "v": 10},
]
usuario = {"t": "plata", "c": 3, "p": False}
assert Proc(reglas).calc(usuario) == 10
Ejecuta:
python -m pytest tests/test_promociones.py
Antes de cambiar el código, conviene escribir una traducción tentativa:
Proc parece calcular puntos de una promoción.d parece ser una lista de reglas.calc calcula puntos para un usuario.a parece indicar el tipo de usuario al que aplica una regla.v parece ser la cantidad de puntos de la regla.t, c y p parecen significar tipo, compras y premium.Esta traducción debe validarse con el equipo o con el comportamiento observado. Si no estamos seguros, renombramos de manera conservadora.
El primer cambio será renombrar variables locales dentro de calc. Es un cambio pequeño y de bajo riesgo.
class Proc:
def __init__(self, d):
self.d = d
def calc(self, u):
puntos = 0
for regla in self.d:
if regla["a"] == u["t"]:
puntos = puntos + regla["v"]
if u["c"] > 10:
puntos = puntos + 5
if u["p"]:
puntos = puntos * 2
return puntos
Ejecuta las pruebas:
python -m pytest tests/test_promociones.py
Si pasan, el comportamiento se mantiene. Todavía hay nombres confusos, pero el código ya es un poco más legible.
Ahora podemos cambiar d por reglas y u por usuario. El atributo interno también debe actualizarse.
class Proc:
def __init__(self, reglas):
self.reglas = reglas
def calc(self, usuario):
puntos = 0
for regla in self.reglas:
if regla["a"] == usuario["t"]:
puntos = puntos + regla["v"]
if usuario["c"] > 10:
puntos = puntos + 5
if usuario["p"]:
puntos = puntos * 2
return puntos
Este cambio ya toca más referencias. Por eso conviene hacerlo con ayuda del editor o IDE y ejecutar pruebas inmediatamente después.
El método calc no dice qué calcula. Podemos renombrarlo a calcular_puntos. Este cambio afecta el código de producción y las pruebas.
class Proc:
def __init__(self, reglas):
self.reglas = reglas
def calcular_puntos(self, usuario):
puntos = 0
for regla in self.reglas:
if regla["a"] == usuario["t"]:
puntos = puntos + regla["v"]
if usuario["c"] > 10:
puntos = puntos + 5
if usuario["p"]:
puntos = puntos * 2
return puntos
Las pruebas deben llamar al nuevo método:
assert Proc(reglas).calcular_puntos(usuario) == 60
Luego ejecuta python -m pytest. Un renombrado incompleto suele aparecer como AttributeError o NameError.
La clase Proc tampoco expresa intención. Un nombre más claro puede ser CalculadoraPuntosPromocion. No es corto, pero comunica mejor su responsabilidad.
class CalculadoraPuntosPromocion:
def __init__(self, reglas):
self.reglas = reglas
def calcular_puntos(self, usuario):
puntos = 0
for regla in self.reglas:
if regla["a"] == usuario["t"]:
puntos = puntos + regla["v"]
if usuario["c"] > 10:
puntos = puntos + 5
if usuario["p"]:
puntos = puntos * 2
return puntos
También actualizamos las pruebas:
from promociones import CalculadoraPuntosPromocion
Después de este cambio vuelve a ejecutar las pruebas.
Las claves "a", "v", "t", "c" y "p" son el problema más delicado. Si esos diccionarios vienen de otro módulo, de una API o de archivos externos, renombrar las claves cambia el contrato de entrada.
Podemos mejorar el código interno sin cambiar todavía el contrato externo usando variables intermedias:
class CalculadoraPuntosPromocion:
def __init__(self, reglas):
self.reglas = reglas
def calcular_puntos(self, usuario):
tipo_usuario = usuario["t"]
cantidad_compras = usuario["c"]
es_premium = usuario["p"]
puntos = 0
for regla in self.reglas:
tipo_aplicable = regla["a"]
puntos_regla = regla["v"]
if tipo_aplicable == tipo_usuario:
puntos = puntos + puntos_regla
if cantidad_compras > 10:
puntos = puntos + 5
if es_premium:
puntos = puntos * 2
return puntos
Esta versión conserva las claves abreviadas de entrada, pero hace visible su significado dentro del método.
Las pruebas también forman parte del código. Si un test se llama test_calc_1, no ayuda a entender qué comportamiento protege. Conviene usar nombres que describan el caso.
def test_duplica_puntos_cuando_el_usuario_es_premium():
reglas = [
{"a": "oro", "v": 20},
{"a": "oro", "v": 5},
]
usuario = {"t": "oro", "c": 12, "p": True}
calculadora = CalculadoraPuntosPromocion(reglas)
assert calculadora.calcular_puntos(usuario) == 60
Un nombre claro permite que una falla de prueba sea más informativa durante el refactoring.
Un error frecuente es mezclar el renombrado con otros cambios: extraer funciones, modificar reglas y cambiar estructuras de datos al mismo tiempo. Para practicar, separa los pasos:
Después de renombrar, busca el nombre anterior en todo el proyecto. Por ejemplo:
rg "Proc|calc|self\.d|u\[|x\[" .
Si no tienes rg instalado, puedes usar la búsqueda del editor. La idea es confirmar que no quedaron llamadas viejas, imports antiguos o pruebas desactualizadas.
Cuando termines los renombrados principales, ejecuta:
ruff check .
black --check .
python -m pytest
ruff puede detectar imports sin usar o nombres que quedaron rotos. Las pruebas confirman que el comportamiento esperado sigue vigente.
Crea el archivo src/envios.py con este código:
class H:
def __init__(self, z):
self.z = z
def g(self, p):
a = 0
for y in p["i"]:
a = a + y["w"] * y["q"]
if p["r"] in self.z:
a = a + self.z[p["r"]]
if p["e"]:
a = a + 2000
return a
Realiza estas tareas:
g.python -m pytest después de cada grupo de cambios.Una versión más clara podría ser:
class CalculadoraCostoEnvio:
def __init__(self, costos_por_region):
self.costos_por_region = costos_por_region
def calcular_costo(self, pedido):
costo_productos = 0
for item in pedido["i"]:
peso = item["w"]
cantidad = item["q"]
costo_productos = costo_productos + peso * cantidad
region = pedido["r"]
es_envio_express = pedido["e"]
costo_total = costo_productos
if region in self.costos_por_region:
costo_total = costo_total + self.costos_por_region[region]
if es_envio_express:
costo_total = costo_total + 2000
return costo_total
Observa que las claves "i", "w", "q", "r" y "e" siguen iguales. Eso permite mejorar la lectura interna sin romper llamadas existentes.
Antes de continuar, verifica que puedes explicar estos puntos:
En este tema practicamos renombrados sobre variables, parámetros, atributos, métodos, clases y pruebas. Vimos que un buen nombre hace visible la intención del código y reduce el esfuerzo necesario para modificarlo.
En el próximo tema trabajaremos con otra refactorización central: extraer funciones a partir de bloques largos o repetidos.