jueves, 6 de abril de 2017

El arte de nombrar (parte 1)

En general, ponerle nombres a las cosas no suele ser tarea simple. En particular, hablando de programación, nombrar variables, clases, funciones, etc, no constituyen excepciones sino todo lo contrario. ¿Cuál es la verdadera importancia de un buen nombre en el código? ¿Qué haríamos sin "foo" y "bar"?

No cabe duda de que un nombre descriptivo es mejor que uno genérico. Obviamente "vel", "pos" y "acel" son mejores identificadores para una terna de variables que guardan el estado de un cuerpo rídigo que "a", "b" y "c". Pero... ¿alcanza? He aquí algunas consideraciones mayormente prácticas al respecto.


Nombres largos vs. cortos

Cuanto más información me de un nombre, mejor. Más fácil me será interpretar el código. Ya no es excusa que un nombre corto es más rápido/fácil de escribir, porque hoy en día casi nunca escribimos más de 3, 4 o 5 letras, el autocompletado hace el resto. Pero, en pos de querer dar mucha información puedo terminar con un nombre demasiado largo. Y sí puede valer como excusa que si es muy largo, el código ocupa (visualmente) demasiado y entonces me obliga a partir una instrucción en muchas lineas o a hacer scroll. En ambos casos se pierde legibilidad. A mi me gusta poder ver la estructura de una clase o función de un pantallazo, minimizando el scrolleo necesario.

¿Pero dónde está exactamente la barrera del "muy largo"? ¿10 caracteres? ¿5? ¿20? Cada uno tendrá el suyo, y además variará por contexto. En una función de 10 lineas con 5 variables, 3 letras pueden ser más que suficiente. En un algoritmo con 35 variables seguramente necesitaré más para evitar confusiones.


¿Qué información debe brindar?

Si solo digo "vel", en muchos contextos puedo imaginar fácilmente que viene de "velocidad"... pero todavía no se la velocidad de qué/quién, ni en qué unidades está medida. Y ambas cosas son básicas para poder usar correctamente el dato, ¿o no? Algunos, por ejemplo, acostumbran a codificar unidades y/o tipos en el nombre. Creo que algo como "vel_int_kmh" tendría sentido en JavaScript, o algún otro lenguaje flojo de papeles. Pero en lenguajes con sistemas de tipos más estrictos, el tipo de dato me da esta información. Y aunque no tenga la declaración del tipo a mano, el IDE podría acercármela, aún cuando no es explícita, como el "auto" de C++. el IDE podría motrarme el tipo real, declarado o deducido, con solo pasar el mouse sobre el nombre (cosa que tengo que mejorar ZinjaI, no siempre anda :( ).

Así, en un mundo ideal, tengo esa información a mano y no necesito recargar tanto el nombre. Además, una confusión en tipos o unidades suele resultar en un error en tiempo de compilación, imposible que pase desapercibido. Peeero, no siempre es fácil o simple expresar todas las restricciones del tipo de forma que el compilador pueda verificarlas.


Colisiones y sobrecargas

Otra molestia es el tema de las colisiones. Si nos mantenemos en un rango de nombre razonablemente largos (¿más de 3 letras, menos de 15?), tarde o temprano tendremos colisiones, entre nuestras cosas, o tal vez contra nombre de terceros. Aún cuando el compilador tenga mecanismos para desambigüar, como las reglas de resolución de sobrecargas, no es buena idea dejar la interpretación humana librada a la pericia del lector. Las personas no son tan rigurosas ni perfectas. Y entonces, en algunos lenguajes las soluciones pasan por alargar los nombres, poniéndole prefijos a todo (ej: todas las funciones de OpenGL empiezan con gl).

Por suerte C++ tiene algo mejor, que son los namespaces, grandes bolsas de símbolos. Entonces es buena idea usar nombres sin prefijos, pero poner todo dentro de un namespace. Así, luego podemos usar las cosas como si tuvieran prefijo ("std::cout"), o avisar que hay prefijos por defecto que podemos omitir ("using namespace std" y luego "cout" a secas). Pocos tienen presente que, además, podemos usar "using" para un solo símbolo ("using std::cout"), podemos poner un "using" dentro de algún scope (abrir la bolsa solo una función o clase), o hasta podemos hacer variables y funciones "static" o namespaces anónimos para que un conjunto de símbolos "no se vea" fuera del cpp donde se declaran.


Convenciones y reglas de estilo

Finalmente, para cerrar la parte bien "práctica", es bueno en cada proyecto definir reglas de estilo generales. Por ejemplo, en mis proyectos nuevos, todas las variables van con minúscula, los tipos (clases, enums, structs) empiezan con mayúsculas, y las constantes son todo mayúsculas. Esto no es solo por estética o prolijidad, sino que me permite decir bastante de un identificador sin agregar ni un caracter. Las funciones (libres o miembro) también empiezan con mayúsculas (costumbre copiada de wxWidgets). Algunos argumentan que así es fácil diferenciar cosas de la biblioteca estándar (todo minúscula) de cosas mías. Y en algunos casos, uso prefijos. Por ejemplo, todos los atributos privados de un objeto empiezan con "m_" ("m" de "member"), para que sea fácil distinguir las variables miembro de las locales o de los argumentos de una función.

Así, hay varias cosas a definir, que no pasan esencialmente por hacer más largos los nombres, sino son más bien de forma. Mi estilo actual es el que pueden ver muy resumido en la documentación del nuevo MotoGT.


Esta lista no es para nada exhaustiva (googleen si quieren más reglas prácticas). En particular, quedan algunas implicancias que a veces son más importantes y menos obvias, relacionadas a la elección de las palabras y no a la forma de escribirlas o abreviarlas. Pero como esto ya se hizo largo, lo dejo para una segunda parte.

No hay comentarios:

Publicar un comentario