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 + dprint(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} €")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_feeprint(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} €")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 = 50STANDARD_DELIVERY_FEE = 4.99EXPRESS_SURCHARGE = 7STANDARD = 1EXPRESS = 2UPPER_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 = 50FREE_DELIVERY_FROM = 60print(FREE_DELIVERY_FROM)60typing.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 = 50STANDARD_DELIVERY_FEE = 4.99EXPRESS_SURCHARGE = 7STANDARD = 1EXPRESS = 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_feeprint(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} €")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 = 50STANDARD_DELIVERY_FEE = 4.99EXPRESS_SURCHARGE = 7STANDARD = 1EXPRESS = 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
- La llamada antigua era calc(40, 2, False). Ahora dice qué calcula, y 2 es EXPRESS.
- Los parámetros: a, t, f pasan a ser subtotal, delivery_type, has_free_delivery_coupon. Los valores son los mismos.
- «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.
- 50 es FREE_DELIVERY_FROM: el umbral del envío gratis. 40 está por debajo, así que el envío no es gratis.
- «t == 2» era un acertijo. delivery_type == EXPRESS es una frase: el envío exprés suma EXPRESS_SURCHARGE.
- «if f:» no te decía nada. has_free_delivery_coupon es una pregunta, y la respuesta es False: no hay cupón.
- El pedido más el envío. El programa imprime 51.99 €.
- 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} €")La primera: subtotal >= FREE_DELIVERY_FROM es verdadero con exactamente 50, así que el envío es 0 y el total es 50,00 €. La segunda está por debajo del umbral: 4,99 € de envío y 7 € por el exprés, así que 49,90 + 11,99 = 61,89 €.
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_feeLa comparación usa el nombre de la constante, no el número. El umbral está en un solo lugar, y la línea se lee como la regla: «si la suma es al menos el umbral del envío gratis».
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.40is_paid = Truequalifies_for_free_delivery = subtotal >= FREE_DELIVERY_FROM
if is_paid and qualifies_for_free_delivery: print("Enviamos sin gastos de envío.")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. Entoncesif not is_not_paid:es una doble negación, y todos tropiezan con ella. Escribeis_paidy 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 enclienten el siguiente archivo ni enbuyeren la base de datos. Si leer esfind…, no se convierte enget…yfetch…en otro sitio. Una palabra distinta hace que el lector busque otra cosa. - Una palabra no significa dos cosas. Si
totales 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,parserestán bien cuando eso es justo la cosa.Manager,ProcessoryHelpersuelen 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)Neto: 50.00 €Total con IVA: 110.00 €¿En qué línea está el error?
Elige la línea con el error.
El error es el nombre. vat promete el impuesto, pero devuelve el precio con el impuesto. El autor de print_invoice confió en el nombre y sumó «el impuesto» al precio, así que el precio se contó dos veces. Cada función por separado calcula exactamente lo que está escrito en ella. Solo el nombre miente. La solución es una función cuyo nombre y cuerpo dicen lo mismo:
VAT_RATE = 0.20
def vat_amount(price): return price * VAT_RATECon ella, la factura es de 60,00 €.
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.
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 pprint(f"{f(1.5, 1):.2f} €")print(f"{f(5, 1):.2f} €")print(f"{f(5, 3):.2f} €")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:
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
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Funciones · Parámetros y return · Lo que comparten todos los objetos: atributos de clase · Excepciones
- Continúa en: Funciones: una tarea, pocos parámetros, sin efectos secundarios ocultos · Comentarios que mienten y comentarios que ayudan · Code smells: método largo, clase grande, duplicación, primitivos en vez de tipos, cadenas largas de if · Las pruebas como red de seguridad
- Ejercicios sobre el tema: Código limpio y refactorización