Ir al contenido
wannacode

Nombres en el código

En resumen: Los buenos nombres en el código dicen qué es algo y por qué existe: constantes en lugar de números mágicos, booleanos formulados como preguntas y las palabras del negocio en lugar de abreviaturas.

Problema

La función que nadie quiere tocar

En el código de una tienda en línea hay una función por la que pasa cada pedido:

def calc(a, t, f):
d = 4.99
if a >= 50:
d = 0
if t == 2:
d += 7
if f:
d = 0
return a + d
print(f"{calc(40, 1, False):.2f} €")
print(f"{calc(40, 2, False):.2f} €")
print(f"{calc(60, 2, False):.2f} €")
print(f"{calc(40, 2, True):.2f} €")
Salida
44.99 €
51.99 €
67.00 €
40.00 €

Funciona. Ahora di qué hace. ¿Qué es t? ¿Por qué t == 2 suma 7? ¿Qué es f, y por qué lo pone todo a cero? ¿Por qué justo 50?

El autor lo sabía. Se fue hace un año. Marketing quiere envío gratis desde 60 €, no desde 50 €. Tu compañero busca 50 en el proyecto y encuentra 14 lugares: en uno, 50 es el umbral del envío; en otro, es el peso máximo de un paquete en kilogramos; en un tercero, es el número de productos por página. ¿Cuál hay que cambiar?

La lógica aquí es correcta. El problema es que el código no le dice a quien lo lee lo que pensaba el autor.

La idea

El nombre dice qué y por qué, no cómo

La computadora lee calc(a, t, f) sin problema. Una persona no. Y el código se lee muchas más veces de las que se escribe: en cada corrección, en cada revisión, cada vez que alguien busca un error.

Un buen nombre responde las preguntas que de otro modo le harías al autor: qué es esto, por qué está aquí, cómo se usa. Esto se llama un nombre que revela la intención (intention-revealing name). Aquí está la misma función, con solo los nombres cambiados:

def total_with_delivery(subtotal, delivery_type, has_free_delivery_coupon):
delivery_fee = 4.99
if subtotal >= 50:
delivery_fee = 0
if delivery_type == 2:
delivery_fee += 7
if has_free_delivery_coupon:
delivery_fee = 0
return subtotal + delivery_fee
print(f"{total_with_delivery(40, 1, False):.2f} €")
print(f"{total_with_delivery(40, 2, False):.2f} €")
print(f"{total_with_delivery(60, 2, False):.2f} €")
print(f"{total_with_delivery(40, 2, True):.2f} €")
Salida
44.99 €
51.99 €
67.00 €
40.00 €

La salida es la misma hasta el último dígito. Ahora la función se lee como una regla: el envío cuesta 4,99 €, es gratis desde 50 €, el envío exprés cuesta 7 € más y el cupón lo hace gratis.

Nombres que no ayudan:

  • un nombre que dice cómo, no qué: float_value, list1, tmp;
  • un nombre general que sirve para todo: data, info, value, process, handle, manager;
  • una abreviatura que solo conoce el autor: calc, cnt, dlv_fee.

La longitud sigue al ámbito. i en un bucle de tres líneas está bien. El parámetro de una función que se llama desde diez lugares merece un nombre completo.

Más precisamente, para curiosos

¿Y un comentario sobre la función, t: 1 = standard, 2 = express? Ayuda hasta que alguien cambia los números y se olvida del comentario. Un nombre no puede quedar desactualizado por separado del código, porque es parte del código. Cuándo ayuda un comentario y cuándo miente es el tema de la tercera lección de este módulo.

La idea

Número mágico → constante con nombre

Los nombres de las variables ya están en su lugar, pero 4.99, 50, 7 y 2 siguen en el código. Un número sin nombre en el código se llama número mágico: solo el autor sabe qué significa, y cuando hay que cambiarlo, lo buscas por todas partes.

La solución es una constante con nombre. Escribes el valor una vez, en un solo lugar, y usas el nombre en todos los demás:

FREE_DELIVERY_FROM = 50
STANDARD_DELIVERY_FEE = 4.99
EXPRESS_SURCHARGE = 7
STANDARD = 1
EXPRESS = 2
Python no tiene constantes. Las mayúsculas, UPPER_SNAKE_CASE, son una convención de PEP 8: «este valor no cambia». El lenguaje no te lo impide, pero todos los que leen el código saben lo que significan.

Marketing quiere 60 €: cambias una línea, y cada lugar que pregunta «desde cuánto es gratis el envío» ve el nuevo valor. Y los 50 que son pesos quedan intactos, porque no son FREE_DELIVERY_FROM.

Más precisamente, para curiosos

No todo número es mágico. 0 para «sin tarifa», 1 en i + 1 y 100 en un porcentaje se entienden por su lugar. Un número es mágico cuando lleva una regla del negocio (un umbral, una tarifa, un límite, un código de estado) o puede cambiar. Una constante con el nombre de su valor (FIFTY = 50) no ayuda: el nombre dice el papel, no el número.

Nada impide una segunda asignación:

FREE_DELIVERY_FROM = 50
FREE_DELIVERY_FROM = 60
print(FREE_DELIVERY_FROM)
Salida
60

typing.Final (FREE_DELIVERY_FROM: Final = 50) hace que una herramienta como mypy se queje de la segunda asignación. El programa en sí la sigue aceptando.

Míralo

El mismo cálculo, leído a través de los nombres

Los nombres y las constantes juntos:

FREE_DELIVERY_FROM = 50
STANDARD_DELIVERY_FEE = 4.99
EXPRESS_SURCHARGE = 7
STANDARD = 1
EXPRESS = 2
def total_with_delivery(subtotal, delivery_type, has_free_delivery_coupon):
delivery_fee = STANDARD_DELIVERY_FEE
if subtotal >= FREE_DELIVERY_FROM:
delivery_fee = 0
if delivery_type == EXPRESS:
delivery_fee += EXPRESS_SURCHARGE
if has_free_delivery_coupon:
delivery_fee = 0
return subtotal + delivery_fee
print(f"{total_with_delivery(40, STANDARD, False):.2f} €")
print(f"{total_with_delivery(40, EXPRESS, False):.2f} €")
print(f"{total_with_delivery(60, EXPRESS, False):.2f} €")
print(f"{total_with_delivery(40, EXPRESS, True):.2f} €")
Salida
44.99 €
51.99 €
67.00 €
40.00 €

Mira cómo cada número y cada letra de la primera versión recibe un significado a medida que avanza el cálculo:

FREE_DELIVERY_FROM = 50
STANDARD_DELIVERY_FEE = 4.99
EXPRESS_SURCHARGE = 7
STANDARD = 1
EXPRESS = 2
def total_with_delivery(subtotal, delivery_type, has_free_delivery_coupon):
delivery_fee = STANDARD_DELIVERY_FEE
if subtotal >= FREE_DELIVERY_FROM:
delivery_fee = 0
if delivery_type == EXPRESS:
delivery_fee += EXPRESS_SURCHARGE
if has_free_delivery_coupon:
delivery_fee = 0
return subtotal + delivery_fee
print(f"{total_with_delivery(40, EXPRESS, False):.2f} €")
Paso a paso, en palabras
  1. La llamada antigua era calc(40, 2, False). Ahora dice qué calcula, y 2 es EXPRESS.
  2. Los parámetros: a, t, f pasan a ser subtotal, delivery_type, has_free_delivery_coupon. Los valores son los mismos.
  3. «d = 4.99» pasa a ser «delivery_fee = STANDARD_DELIVERY_FEE». Los mismos 4.99, pero ahora sabes qué son: la tarifa del envío estándar.
  4. 50 es FREE_DELIVERY_FROM: el umbral del envío gratis. 40 está por debajo, así que el envío no es gratis.
  5. «t == 2» era un acertijo. delivery_type == EXPRESS es una frase: el envío exprés suma EXPRESS_SURCHARGE.
  6. «if f:» no te decía nada. has_free_delivery_coupon es una pregunta, y la respuesta es False: no hay cupón.
  7. El pedido más el envío. El programa imprime 51.99 €.
  8. Ida y vuelta: los mismos valores, el mismo cálculo y la misma salida que calc(40, 2, False). Solo cambiaron los nombres.

Las tres versiones imprimen lo mismo. La computadora no ve ninguna diferencia. La diferencia es para la persona que cambia la regla mañana. Un lugar todavía no dice nada: False en la llamada. De eso nos ocupamos en la próxima lección.

Pruébalo

Predice la salida

Un pedido de exactamente 50 € con envío estándar, y un pedido de 49,90 € con envío exprés. Lee la regla a través de los nombres. ¿Qué imprime el programa?

print(f"{total_with_delivery(50, STANDARD, False):.2f} €")
print(f"{total_with_delivery(49.90, EXPRESS, False):.2f} €")
Respuesta

Pruébalo

Rellena el hueco

El umbral del envío gratis ahora es una constante. ¿Qué va en el hueco?

def total_with_delivery(subtotal, delivery_type, has_free_delivery_coupon):
    delivery_fee = STANDARD_DELIVERY_FEE
    if subtotal >= :
        delivery_fee = 0
    if delivery_type == EXPRESS:
        delivery_fee += EXPRESS_SURCHARGE
    if has_free_delivery_coupon:
        delivery_fee = 0
    return subtotal + delivery_fee

La idea

Un booleano es una pregunta

Un booleano responde sí o no, así que su nombre es una pregunta: is_paid, has_coupon, can_ship. Entonces el if se lee como una frase:

subtotal = 62.40
is_paid = True
qualifies_for_free_delivery = subtotal >= FREE_DELIVERY_FROM
if is_paid and qualifies_for_free_delivery:
print("Enviamos sin gastos de envío.")
Salida
Enviamos sin gastos de envío.

Lo que no funciona:

  • flag, status, check, ok: no dicen cuál es la pregunta;
  • una negación en el nombre: is_not_paid. Entonces if not is_not_paid: es una doble negación, y todos tropiezan con ella. Escribe is_paid y pon la negación en la comprobación cuando la necesites.

Lo mismo vale para los demás nombres. Una clase y una variable son sustantivos (Order, delivery_fee). Una función hace algo, así que es un verbo (send_invoice). Una función con nombre de pregunta (is_in_stock) promete que solo pregunta. En la próxima lección verás qué pasa cuando rompe esa promesa.

La idea

Las palabras de la tienda en el código

Los mejores nombres no los inventas. Son las palabras de las personas para las que se hace el programa: paquete, mensajero, contra reembolso, devolución, factura. Cuando el código dice parcel, courier, refund, el programador y la persona del almacén hablan de lo mismo. Son nombres de dominio (domain names).

  • Una palabra para un concepto. Si el cliente es customer, no se convierte en client en el siguiente archivo ni en buyer en la base de datos. Si leer es find…, no se convierte en get… y fetch… en otro sitio. Una palabra distinta hace que el lector busque otra cosa.
  • Una palabra no significa dos cosas. Si total es la suma con envío, la suma sin él tiene su propio nombre, subtotal.
  • Las palabras técnicas son para cosas técnicas. queue, cache, parser están bien cuando eso es justo la cosa. Manager, Processor y Helper suelen significar «no sé qué hace exactamente».

Pruébalo

Encuentra el error

Contabilidad se queja: una factura de 50 € sin IVA sale en 110 €. El IVA es del 20%, así que debería ser 60 €.

def vat(price):
return price * 1.20
def print_invoice(net):
total = net + vat(net)
print(f"Neto: {net:.2f} €")
print(f"Total con IVA: {total:.2f} €")
print_invoice(50)
Salida
Neto: 50.00 €
Total con IVA: 110.00 €

¿En qué línea está el error?

Elige la línea con el error.

La idea

Cómo escribe los nombres tu lenguaje

Un buen nombre en un lenguaje se ve raro en otro. Cada lenguaje tiene su propia convención, y el código que la sigue se lee más rápido, porque la forma del nombre te dice qué es.

La convención viene de PEP 8:

  • funciones, métodos, variables y módulos: snake_case (total_with_delivery, delivery_fee);
  • clases: PascalCase (DeliveryCalculator);
  • constantes a nivel de módulo: UPPER_SNAKE_CASE (FREE_DELIVERY_FROM);
  • un _ inicial (_cache) significa «interno, no lo uses desde fuera». El lenguaje no lo prohíbe, es una convención.
Más precisamente, para curiosos

El mismo nombre en los cuatro lenguajes:

Java C# Python JavaScript
función totalWithDelivery TotalWithDelivery total_with_delivery totalWithDelivery
variable deliveryFee deliveryFee delivery_fee deliveryFee
constante FREE_DELIVERY_FROM FreeDeliveryFrom FREE_DELIVERY_FROM FREE_DELIVERY_FROM
clase Order Order Order Order
booleano isPaid isPaid (propiedad: IsPaid) is_paid isPaid

La idea

Rename: la refactorización más barata

Cambiar un nombre sin cambiar el comportamiento se llama Rename. Es la refactorización más pequeña y más barata: no cambias ni un valor ni una comparación, y el programa imprime lo mismo.

No lo hagas a mano con buscar y reemplazar. El editor sabe cuál d es tu variable y cuál es la letra de "delivery": en IntelliJ IDEA y PyCharm es Mayús+F6, en VS Code es F2, en Visual Studio es Ctrl+R, Ctrl+R. Cambia todos los lugares a la vez.

Aquí no hay compilador que atrape un lugar olvidado. El nombre antiguo da un error (NameError) solo cuando se ejecuta esa línea. Así que después de un Rename ejecutas el programa, o mejor, las pruebas. Y un nombre que aparece como texto (getattr(order, "fee"), una clave en JSON) es invisible para el editor.

Cómo refactorizar en pasos pequeños, de modo que el programa funcione después de cada uno, es el tema de la sexta lección de este módulo. Rename es el primer paso.

Ejercicio

El mensajero

Resuelve la tarea en tu editor.

El mensajero. Esta función calcula el precio de un paquete:

def f(w, z):
p = 3.50
if w > 2:
p += (w - 2) * 0.80
if z == 3:
p *= 1.5
return p
print(f"{f(1.5, 1):.2f} €")
print(f"{f(5, 1):.2f} €")
print(f"{f(5, 3):.2f} €")
Salida
3.50 €
5.90 €
8.85 €

La regla es esta: un paquete de hasta 2 kg cuesta 3,50 €, cada kilogramo por encima son 0,80 €, y la zona 3 es remota, así que allí el precio es un 50% más alto. Ponle a la función, a los parámetros y a la variable nombres que digan esto, y convierte los números en constantes. La salida debe seguir igual:

Salida
3.50 €
5.90 €
8.85 €

Más tareas

  • El booleano. Añade un parámetro que diga si el paquete es frágil (una tarifa de 1,20 €). ¿Cómo lo llamarás? ¿Cómo se verá la llamada?
  • Tu código. Abre un ejercicio antiguo tuyo. Encuentra tres nombres que hoy no entiendes a primera vista y cámbialos con el Rename del editor. Ejecuta el programa: ¿la salida es la misma?
  • El diccionario de la tienda. Anota diez palabras que usa la persona del almacén (paquete, mensajero, zona…) y cómo las nombrarías en el código de tu lenguaje.

Test

Ponte a prueba

1. Una variable guarda la suma de los productos después del descuento, pero sin el envío. ¿Qué nombre es el mejor?
2. ¿Qué tiene de malo if not is_not_paid:?
3. El envío gratis ahora empieza en 60 € en lugar de 50 €. ¿Cómo ayuda la constante?
4. get_user(email) busca al usuario, y si no existe, crea uno nuevo y lo guarda en la base de datos. ¿Cuál es el problema?
5. ¿Por qué Rename es la refactorización más barata?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·