La mayor parte de mi tiempo cuando programo la invierto en leer código, por lo que la legibilidad del código es una parte fundamental para que mi yo del futuro (y el resto de compis que trabajan conmigo) puedan entenderlo, mantenerlo y por supuesto mejorarlo en el futuro. Por tanto el naming de variables, funciones, clases,… es uno de los aspectos más importantes a la hora de escribir software.
En el blog ya hemos hablando de este tema hace tiempo en post «Clean Code» sin escribir una linea de código donde comentábamos que la importancia del naming, los mensajes de commit, el code style,… de manera muy concisa y breve. Por otro lado en Mejorando el naming de nuestro código estuvimos escribiendo sobre el proceso del naming: (Missing, Nonsense, Honest,Honest and Complete,…) y los pasos a seguir para intentar dar buenos nombres:
- Mirar: no trato de entenderlo todo al 100%, ya que eso me tomaría mucho tiempo e incluso puede que se me fría el cerebro.
- Idea: no es la mejor, pero al menos es algo, inspecciono la idea.
- Escribir: Escribo ese nombre, veo como queda, utilizo thesaurus.com para los sinónimos
- Chequear: Compruebo como queda y si es consistente con las decisiones que he tomado antes o las que creo que tomaré. Inspecciono el impacto ¿Es fácil de buscar, sabré dentro de 2 semanas que es esto, el equipo está de acuerdo?
- Commit: Hacer un commit, con un mensaje bueno sobre el renaming. Intentar no poner «refactor»
Proceso para crear buenos nombres extraído del post «Mejorando el naming de nuestro código»
En el post de hoy vamos a ir al grano y vamos a dar ejemplos de que es para mi un mal nombre y como podemos mejorarlo.
¿Qué es un buen naming?
Un buen nombre debe ser intuitivo, conciso y tener significado (descriptivo). Además debe indicar claramente la intención y el propósito de la variable, la función, la clase. Los nombres de variable deben ser fáciles de leer y pronunciar para que otros programadores puedan entender rápidamente lo que representa la variable, por lo que introducir abreviaturas no es una buena idea.
- Conciso/Corto: Un nombre no debe tardar en teclearse y, por tanto, recordarse;
- Intuitivo. Un nombre debe leerse naturalmente, lo más cerca posible del habla común;
- Descriptivo. Un nombre debe reflejar lo que hace/posee de la manera más eficiente;
/* Mal nombrado */
const a = 5 // "a" ¿Qué significa a?
const isPaginatable = a > 10 // "Paginatable" Suena un poco raro
const should_Paginatize = a > 10 // No inventar palabras
/* algo mejor */
const postCount = 5
const hasPagination = postCount > 10
const shouldPaginate = postCount > 10 // alternatively
¿Qué podemos sacar de aquí? Lo primero es que nos guste o no, el inglés es el idioma predominante en programación. Así que escribir código en inglés debería ser casi obligatorio. Por otro lado también está el tema de la convención, podemos escribir en: camelCase, PascalCase, snake_case
no importa cuál escojamos, lo único es que lo sigamos siempre y seamos consistentes. SI el lenguaje ya tiene «predilección» por un estilo u otro lo mejor es seguirlo y no mezclar.
¿Qué evitar a la hora de nombrar?
Lo ideal sería evitar el uso de abreviaturas, la duplicación de contexto y las condiciones en negativo. Ahora lo explicamos mejor.
El uso de abreviatura y nombres cortos puede ahorrar tiempo al escribir código, pero esto puede ser perjudicial para la legibilidad del código. Es necesario estar familiarizado con el dominio para entender las abreviatures y eso hace que el onboarding de nuevas personas al equipo sea más complejo. Del mismomodo son dificiles de pronunciar lo que hace que a veces pueda haber malosentendidos a la hora de saber de qué estamos hablando. Es mejor utilizar nombres descriptivos que indiquen claramente el propósito de la variable, función o clase
/* Algo a mejorar */
const onItmClk = () => {}
/* Algo mejor */
const onItemClick = () => {}
Otra de las cosas que nos encontramos a la hora de nombrar una variable es su excesivo contexto. Si no incrementamos. lalegibilidad es mucho mejor no añadir el contexto. Imagina que estamos dentro de una función, pues no tiene sentido llamar a las variables de esa función con el nombre de la clase+el nombre de la variable, veámoslo con un ejemplo:
class MenuItem {
/* Este método tiene duplicado el contexto MenuItem */
handleMenuItemClick = (event) => { ... }
/* Es más sencillo de leer `MenuItem.handleClick()` */
handleClick = (event) => { ... }
}
Poner la condiciones positivas, es decir, la mayoría de las veces nuestro cerebro funciona mejor en positivo. Además ver el lado bueno de las cosas, el vaso medio lleno,… nos ayuda en eso del karma. Por eso cuando nos ponemos a programar tambien debemos tenerlo en mente e intentar crear guardas de if positivas.
/* Ejemplos para repensar el nombre */
isDisabled //Negative
isNotActive // Solo imagina como sería un if con !isNotActive 🤯
hasNoBillingAddress // 😞
/* Ejemplos para repensar el nombre */
isEnabled / isActive / hasBillingAddres // Usado en negativo !isActive hacen que el código sea sencillo de seguir😁
¿Por qué es importante el refactorizado de código legado?
La refactorización de código legado es importante porque puede mejorar la legibilidad, la eficiencia y la seguridad (no solo seguridad en el sentido de estricto sino en el de tranquilidad de saber que ya entendemos algo mejor) del código. Por ello, que seamos capaces de nombrar variables y métodos d manera que cumplan las premisas de conciso, intuitivo y descriptivo nos hace que nuestro código sea más sencillo de mantener y actualizar en el futuro.
Del mismo modo el buen naming hace que nos acerquemos más a cumplir los principios SOLID, haciendo que nuestro código sea más facil de mantener.
A/HC/LC Pattern
Hablar de naming, de las premisas conciso, intuitivo y descriptivo, de los principios SOLID está genial, pero en nuestro día a día ¿Cómo nos enfrentamos a nombrar bien las variables, funciones,…? Es cierto que a medida que tenemos más experiencia y conocemos más el dominio, resulta un poco más sencillo dar nombre. Pero hay veces que nos quedamos bloqueados o que es mejor tener una pequeña «regla» para desatascarnos. Aquí vamos a contar un poco el patrón A/HC/LC
prefix? + action (A) + high context (HC) + low context? (LC)
Name | Prefix | Action (A) | High context (HC) | Low context (LC) |
getUser | get | User | ||
getUserMessages | get | User | Messages | |
handleClickOutside | handle | Click | Outside | |
shouldDisplayMessage | should | Display | Message |
Nota: El orden del contexto afecta el significado de una variable. En otras palabras, el alto contexto enfatiza el significado de una variable.
Pongamos un ejemplo: Si tenemos un método llamado shouldUpdateComponent
significa que está a punto de actualizar un componente, mientras que shouldComponentUpdate
le dice que el componente se actualizará por sí mismo y nosotros solo controlamos cuándo debe actualizarse.
Acciones
Es el verbo que forma parte de nuestra función. Es la parte responsable de describir lo que hace la función.
- Get: para acceso a algún dato inmediato.
function getFruitCount() {return this.fruits.length}
- Set: establece el valor de una manera declarativa.
function setFruits(nextFruits) {this.fruits = nextFruits}
- Remove*: borra algo de algún lugar. Por ejemplo, si tenemos una colección elimina un elemento de la misma
function removeFilter(filterName, filters) { return filters.filter((name) => name !== filterName)}
- Delete*: borra completamente algo, deja de existir.
function deletePost(id) { return database.find({ id }).delete()}
*¿Hay diferencia entre delete y remove? En español la diferencia no es muy obvia, pero si pensamos en sus contrarios: add y create. La sutil diferencia es que añadir (add) necesita un «lugar» y añade un elemento ya creado, es decir no crea nada. Por lo que si emparejamos remove/add con create/delete quizás no sea más sencillo - handle, manager: son nombres de variables algo ambiguos, así que yo al menos tendría cuidado usándolos
Prefijos
Los prefijos realzan el signoficado de una variable o método.
- is: describe una característica. o estado, generalmente booleano.
const isBlue = color === 'blue' // characteristic
const isPresent = true // state - has: describe si el contexto actual posee un cierto valor o estado (generalmente booleano).
/* Mejorable */
const isProductsExist = productsCount > 0
const areProductsPresent = productsCount > 0
/* Mejor */
const hasProducts = productsCount > 0 - Should: refleja una declaración condicional positiva junto con una acción. Se usa normalmente en los tests
function shouldUpdateUrl(url, expectedUrl) {return url !== expectedUrl}
Conclusión
En resumen, el nombrado de variables es una parte esencial de la programación. Las variables, funciones y clases con buenos nombres hacen que el código sea más fácil de leer, mantener y actualizar en el futuro. Lo ideal sería que nuestros nombres fuesen concisos, intuitivos y descriptivos; del mismo modo sería bueno evitar abreviaturas y nombres cortos. Si aún así nos cuesta poner un nombre siempre podemos recurrir al patrón A/HC/LC Pattern. Si nos tomamos el suficiente tiempo para nombrar las variables correctamente, podemos ahorrar horas de trabajo a mi yo del futuro y evitar bugs debido a la dificultad de entender el código.
¿Y si no encuentro un buen nombre? Pues no pasa nada, yo intento no frustrarme y seguir hacia delante. Si aun así necesito poner un nombre a algo llamo a esa variable/método Manolo, Joselito, Amalia, nombres que llamen la atención, que «choquen, para así cuando tenga más conocimiento del problema ir a ellos y poder cambiarlos de manera sencilla.
Manolo al rescate
Este texto está basado en una traducción/interpretación de https://github.com/kettanaito/naming-cheatsheet