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:
function calc(a, t, f) { let d = 4.99; if (a >= 50) { d = 0; } if (t === 2) { d += 7; } if (f) { d = 0; } return a + d;}console.log(`${calc(40, 1, false).toFixed(2)} €`);console.log(`${calc(40, 2, false).toFixed(2)} €`);console.log(`${calc(60, 2, false).toFixed(2)} €`);console.log(`${calc(40, 2, true).toFixed(2)} €`);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:
function totalWithDelivery(subtotal, deliveryType, hasFreeDeliveryCoupon) { let deliveryFee = 4.99; if (subtotal >= 50) { deliveryFee = 0; } if (deliveryType === 2) { deliveryFee += 7; } if (hasFreeDeliveryCoupon) { deliveryFee = 0; } return subtotal + deliveryFee;}console.log(`${totalWithDelivery(40, 1, false).toFixed(2)} €`);console.log(`${totalWithDelivery(40, 2, false).toFixed(2)} €`);console.log(`${totalWithDelivery(60, 2, false).toFixed(2)} €`);console.log(`${totalWithDelivery(40, 2, true).toFixed(2)} €`);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é:
numberValue,list1,tmp; - un nombre general que sirve para todo:
data,info,value,process,handle,manager; - una abreviatura que solo conoce el autor:
calc,cnt,dlvFee.
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:
const FREE_DELIVERY_FROM = 50;const STANDARD_DELIVERY_FEE = 4.99;const EXPRESS_SURCHARGE = 7;const STANDARD = 1;const EXPRESS = 2;const prohíbe volver a asignar el nombre. Las mayúsculas, UPPER_SNAKE_CASE, se usan para valores como estos: ajustes del programa que no dependen de nada. Por lo demás, const es para toda variable que no se reasigna, y let solo cuando el valor cambia.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.
const protege el nombre, no el objeto. Una nueva asignación lanza un error, pero un campo del objeto puede cambiar:
const FREE_DELIVERY_FROM = 50;try { FREE_DELIVERY_FROM = 60;} catch (error) { console.log(error.message);}
const SHIPPING = { freeFrom: 50 };SHIPPING.freeFrom = 60;console.log(SHIPPING.freeFrom);Assignment to constant variable.60Para un objeto que de verdad no cambia, existe Object.freeze(…).
Míralo
El mismo cálculo, leído a través de los nombres
Los nombres y las constantes juntos:
const FREE_DELIVERY_FROM = 50;const STANDARD_DELIVERY_FEE = 4.99;const EXPRESS_SURCHARGE = 7;const STANDARD = 1;const EXPRESS = 2;
function totalWithDelivery(subtotal, deliveryType, hasFreeDeliveryCoupon) { let deliveryFee = STANDARD_DELIVERY_FEE; if (subtotal >= FREE_DELIVERY_FROM) { deliveryFee = 0; } if (deliveryType === EXPRESS) { deliveryFee += EXPRESS_SURCHARGE; } if (hasFreeDeliveryCoupon) { deliveryFee = 0; } return subtotal + deliveryFee;}console.log(`${totalWithDelivery(40, STANDARD, false).toFixed(2)} €`);console.log(`${totalWithDelivery(40, EXPRESS, false).toFixed(2)} €`);console.log(`${totalWithDelivery(60, EXPRESS, false).toFixed(2)} €`);console.log(`${totalWithDelivery(40, EXPRESS, true).toFixed(2)} €`);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:
const FREE_DELIVERY_FROM = 50;const STANDARD_DELIVERY_FEE = 4.99;const EXPRESS_SURCHARGE = 7;const STANDARD = 1;const EXPRESS = 2;
function totalWithDelivery(subtotal, deliveryType, hasFreeDeliveryCoupon) { let deliveryFee = STANDARD_DELIVERY_FEE; if (subtotal >= FREE_DELIVERY_FROM) { deliveryFee = 0; } if (deliveryType === EXPRESS) { deliveryFee += EXPRESS_SURCHARGE; } if (hasFreeDeliveryCoupon) { deliveryFee = 0; } return subtotal + deliveryFee;}
console.log(`${totalWithDelivery(40, EXPRESS, false).toFixed(2)} €`);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, deliveryType, hasFreeDeliveryCoupon. Los valores son los mismos.
- «d = 4.99» pasa a ser «deliveryFee = 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. deliveryType === EXPRESS es una frase: el envío exprés suma EXPRESS_SURCHARGE.
- «if (f)» no te decía nada. hasFreeDeliveryCoupon 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?
console.log(`${totalWithDelivery(50, STANDARD, false).toFixed(2)} €`);console.log(`${totalWithDelivery(49.90, EXPRESS, false).toFixed(2)} €`);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?
function totalWithDelivery(subtotal, deliveryType, hasFreeDeliveryCoupon) {
let deliveryFee = STANDARD_DELIVERY_FEE;
if (subtotal >= ) {
deliveryFee = 0;
}
if (deliveryType === EXPRESS) {
deliveryFee += EXPRESS_SURCHARGE;
}
if (hasFreeDeliveryCoupon) {
deliveryFee = 0;
}
return subtotal + deliveryFee;
}La 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: isPaid, hasCoupon, canShip. Entonces el if se lee como una frase:
const subtotal = 62.4;const isPaid = true;const qualifiesForFreeDelivery = subtotal >= FREE_DELIVERY_FROM;
if (isPaid && qualifiesForFreeDelivery) { console.log("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:
isNotPaid. Entoncesif (!isNotPaid)es una doble negación, y todos tropiezan con ella. EscribeisPaidy 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, deliveryFee). Una función hace algo, así que es un verbo (sendInvoice). Una función con nombre de pregunta (isInStock) 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 €.
function vat(price) { return price * 1.2;}
function printInvoice(net) { const total = net + vat(net); console.log(`Neto: ${net.toFixed(2)} €`); console.log(`Total con IVA: ${total.toFixed(2)} €`);}printInvoice(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 printInvoice 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:
const VAT_RATE = 0.2;
function vatAmount(price) { return price * VAT_RATE;}Con 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.
- variables y funciones:
camelCase(totalWithDelivery,deliveryFee); - clases:
PascalCase(DeliveryCalculator); - constantes que son ajustes del programa:
UPPER_SNAKE_CASE(FREE_DELIVERY_FROM); - campos privados de una clase: un
#inicial (#stock). Esto no es una convención sino parte del lenguaje: el campo no se ve desde fuera.
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.
ReferenceError) 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 (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:
function f(w, z) { let p = 3.5; if (w > 2) { p += (w - 2) * 0.8; } if (z === 3) { p *= 1.5; } return p;}console.log(`${f(1.5, 1).toFixed(2)} €`);console.log(`${f(5, 1).toFixed(2)} €`);console.log(`${f(5, 3).toFixed(2)} €`);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 · static: lo que comparten todos los objetos · 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