5. Renombrar variables, funciones y clases para expresar intención

5.1 Objetivo del tema

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.

Objetivo práctico: mejorar nombres en código Python existente y conservar el mismo resultado observable.

5.2 Por qué nombrar bien es refactorizar

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.

5.3 Código inicial

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.

5.4 Pruebas antes de renombrar

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

5.5 Primer diagnóstico de nombres

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.

5.6 Renombrar variables locales

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.

5.7 Renombrar atributos y parámetros

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.

5.8 Renombrar función o método

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.

5.9 Renombrar una clase

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.

5.10 Renombrar claves de diccionarios con cuidado

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.

5.11 Renombrar pruebas

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.

5.12 Renombrar sin cambiar demasiadas cosas

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:

  • Renombra variables locales y ejecuta pruebas.
  • Renombra parámetros y atributos y ejecuta pruebas.
  • Renombra métodos o funciones y ejecuta pruebas.
  • Renombra clases y ejecuta pruebas.
  • Evalúa cambios de contrato solo cuando tengas claro quién usa esos datos.

5.13 Usar búsqueda para evitar referencias olvidadas

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.

5.14 Ejecutar verificación completa

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.

5.15 Ejercicio propuesto

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:

  • Escribe dos pruebas que documenten el comportamiento actual.
  • Renombra la clase para indicar qué responsabilidad tiene.
  • Renombra el método g.
  • Renombra variables locales, parámetros y atributos.
  • Conserva las claves del diccionario hasta tener claro si forman parte del contrato externo.
  • Ejecuta python -m pytest después de cada grupo de cambios.

5.16 Una posible solución

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.

5.17 Lista de verificación

Antes de continuar, verifica que puedes explicar estos puntos:

  • Por qué renombrar es una refactorización real.
  • Cómo distinguir un nombre corto pero claro de una abreviatura confusa.
  • Por qué las claves de diccionarios deben tratarse con más cuidado que variables locales.
  • Cómo renombrar métodos y clases sin olvidar imports o llamadas antiguas.
  • Por qué los nombres de las pruebas también importan.
  • Cómo usar pruebas y búsqueda de texto para confirmar un renombrado.

5.18 Conclusión

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.