Introducción
En el mundo del desarrollo de software, existe un debate sobre las prácticas de código limpio que parece no tener fin. Este artículo no busca cerrar esa discusión, sino enriquecerla desde la perspectiva de un desarrollador que, tras escuchar a diversos mentores, leer clásicos esenciales como Clean Code de Robert C. Martin, y enfrentar los desafíos cotidianos del oficio, desea compartir algunos puntos clave sobre la escritura consciente de código. La experiencia de mantener código legado, colaborar con equipos bajo acuerdos y convenciones establecidas, y buscar mejorar continuamente las prácticas de código limpio, me ha llevado a reflexionar sobre la importancia de cada palabra en el código y su impacto en la interpretación futura por parte de otros desarrolladores, e incluso de uno mismo, durante el proceso de desarrollo o al realizar mantenimiento o actualizaciones semanas o meses después de haberlo escrito.
Dedico este artículo a mi equipo Kranio, con el que día a día escribimos juntos la obra de nuestra colaboración: un código que refleja nuestros aprendizajes individuales y colectivos, y que en el futuro será seguramente legado a nuevos desarrolladores que deberán interpretar nuestra intención de claridad funcional y excelencia, aspectos en los que enfocamos nuestros esfuerzos.
Si han notado el uso recurrente de la palabra "obra" hasta este punto, sepan que no es casualidad. Muchos estaremos de acuerdo en que, como desarrolladores, somos los "autores" de cada línea y bloque de código escrito. Desde esta perspectiva, podemos acercarnos a la escritura de código como lo que es en un sentido literario: la narración de una historia, una que tiene un dilema a resolver, personajes principales, secundarios, eventos y un descenlace que deja todo en su lugar o finales alternativos en caso de ser necesario.
Como en cualquier lenguaje, el código está lleno de adjetivos, sustantivos y verbos, es crucial utilizarlos de manera consciente para que nuestra narración tenga sentido y se entienda por sí misma, sin necesidad de una documentación excesivamente elaborada. Si está bien escrito, el código será claro y evidente, conformando párrafos que se integren de manera natural en una composición perfectamente legible.
Nombrando variables
El darle nombre a una variable en el día a día de la escritura de código puede parecer una cosa trivial, pero la realidad no podría estar más alejada de este supuesto, esta tarea es en realidad algo que merece de sobra nuestra total atención e incluso reflexión antes de bautizar a los distintos personajes de nuestra narración. ¿Cómo puede ser algo tan simple como dar nombre a nuestras variables tener tal importancia? Y sin embargo, si nos ponemos en el lugar de nuestros lectores, es crucial tener una descripción adecuada de cada elemento (personaje primario o secundario de la historia contada) para interpretar de manera correcta nuestra intención.
En el siguiente código de ejemplo habrá múltiples puntos de reflexión, pero intentemos primero interpretar el objetivo de esta función después de una lectura rápida.
Si bien es un código bastante simple, nos obliga a inferir su objetivo guiados por el nombre de la función y los procesos que realiza, siendo ofuscado por sus enigmaticas variables, entre otras malas prácticas.
Ahora bien, veámosla de nuevo.
Sigue sin ser una función particularmente agradable, sin embargo, su intención es mucho más clara, es fácil reconocer al protagonista del proceso descrito y su fin, que a pesar de tener efectos secundarios, resulta evidente.
Aquí listo algunos de los puntos que considero más relevantes a la hora de dar nombres a nuestras variables:
Evitar desinformación
Los programadores deben evitar nombres de variables confusos o engañosos que dificulten la comprensión del código. Es importante usar términos que reflejen con precisión su propósito, evitando abreviaturas ambiguas o nombres que puedan llevar a interpretaciones incorrectas. Además, nombres con variaciones mínimas pueden generar confusión y deben ser claros y distintivos. Nombres engañosos, como "l" y "O" que se asemejan a "1" y "0", deben evitarse para mejorar la legibilidad y evitar errores.
Evitar acrónimos, usar palabras pronunciables
Los nombres de variables en la programación deben ser pronunciables, ya que el lenguaje hablado es una parte fundamental de nuestra capacidad cognitiva y la programación es una actividad social. Usar nombres impronunciables, como "gPstbUsr", puede llevar a situaciones absurdas y dificultar la comunicación efectiva entre los desarrolladores. Nombres claros y pronunciables permiten conversaciones más fluidas y evitan malentendidos al explicar el código a otros. En a lo que nomenclatura respecta, no siempre “más corto” es “mejor”, por el contrario “más claro” si que lo es.
Usa nombres de dominio de negocio o de dominio de la solución
Para mejorar la claridad y comprensión del código, es crucial utilizar una palabra consistente para cada concepto abstracto y evitar juegos de palabras que puedan generar confusión. Los nombres de funciones y variables deben ser coherentes y reflejar su propósito, evitando términos ambiguos o sin contexto claro. Usar nombres técnicos reconocidos facilita la comprensión por parte de otros programadores, mientras que, en ausencia de términos técnicos, se deben emplear nombres del dominio del problema. Añadir contexto a los nombres, a través de clases o prefijos, también ayuda a clarificar su función en el código.
Funciones
Si como yo, iniciaron su carrera de desarrollo de software en los tiempos modernos de la programación orientada a objetos y de la programación estructurada, entonces los términos de “rutinas” y “subrutinas” es algo que sólo conocemos de la literatura “clásica”, de ahí entendemos que los tamaños aceptables para una función han ido variando desde varios cientos de líneas hasta el minimalismo moderno que busca simplificar la vida de todos con funciones concisas y claras.
Las funciones deben ser breves, centradas en una sola tarea y mantener un nivel de abstracción coherente. Para lograrlo, es recomendable que los bloques de código en estructuras condicionales o de bucle contengan solo la invocación de una función con un nombre descriptivo, reduciendo así la complejidad y mejorando la legibilidad. Una función que hace "una sola cosa" descompone un concepto más amplio en pasos del mismo nivel de abstracción. La mezcla de niveles de abstracción en una función o la división de esta en secciones indican que está realizando más de una tarea, lo que dificulta su comprensión.
En las palabras de Robert C. Martin: “LAS FUNCIONES SÓLO DEBEN HACER UNA COSA. DEBEN HACERLO BIEN Y DEBE SER LO ÚNICO QUE HAGAN.”
Analicemos de nuevo la función para obtener las publicaciones de un usuario:
¿Te puedes percatar del “efecto secundario” de la misma? La función dice obtener las publicaciones de un usuario, pero en el camino realiza un filtro por periodo de tiempo. De acuerdo a la premisa de “única responsabilidad” este bloque debe separarse en su propia función. Así:
Evitar duplicaciones
“Las innovaciones en desarrollo de software han sido un intento continuado por eliminar la duplicación de nuestro código fuente”. La duplicación de código, como la repetición de un algoritmo en múltiples lugares, aumenta el tamaño del código y el riesgo de errores, además de complicar futuras modificaciones. Este problema se puede mitigar mediante técnicas como la creación de métodos reutilizables, lo que mejora la legibilidad y reduce riesgos.
Nomenclatura
Debe buscarse la consistencia en los verbos y sustantivos desde los módulos hasta las variables. Usar sinónimos para nombrar funciones de una misma secuencia es como cambiar de nombre a nuestros personajes a mitad de una historia.
Elegir nombres descriptivos y precisos para funciones y métodos es crucial para mejorar la claridad y el diseño del código. Un nombre bien elegido refleja el propósito de la función, facilitando su comprensión y mantenimiento. No hay que temer a los nombres largos si estos son más descriptivos, ya que ayudan a narrar mejor la lógica del código. Además, usar una convención de nombres coherente en todo el código contribuye a contar una historia clara y consistente. Experimentar con diferentes nombres hasta encontrar el más adecuado puede llevar a mejoras en la estructura del código.
Las funciones son los verbos del lenguaje y las clases los sustantivos.
Parámetros
El número ideal de argumentos para una función es cero, y se debe evitar superar dos, ya que más argumentos complican la comprensión, pruebas y uso del código. Los argumentos de salida y los indicadores (como Booleanos) son especialmente problemáticos. En funciones con múltiples argumentos, a menudo es mejor agruparlos en objetos para simplificar. Además, el uso de nombres claros y coherentes para los parámetros es clave para facilitar la lectura y mantenimiento del código. Cuando se necesitan listas de argumentos, deben ser tratadas como un solo argumento, siguiendo las mismas reglas.
Tomemos como ejemplo nuestra función para filtrar las publicaciones por rango de tiempo. Si analizamos los parámetros vemos que dos de ellos son tan altamente relacionados que en realidad forman parte del mismo concepto y por ende su agrupación por clase nos permitiría una lectura más rápida de la misma. De esta manera:
Orden
Queremos que nuestras funciones puedan leerse de arriba hacia abajo, como párrafos de nuestra narración, el contenido de cada uso se entiende por el contexto del anterior y así hasta llegar al descenlace.
Comentarios
Como muchos de los temas de discusión sobre código limpio, los comentarios son de gran controversia. Vemos muchos artículos hablando de cómo escribir comentarios “buenos” o “significativos”, sin embargo, creo que todos podemos estar de acuerdo en que el mejor comentario es aquel que no fue necesario escribir.
Recapitulando sobre el concepto la narración que cuenta el código, podemos recapacitar en que, un buen código se explica por sí mismo. En el mismo sentido, cada vez que sintamos la necesidad de escribir un comentario, primero analicemos si en realidad lo que deberíamos hacer es refactorizar el código de una manera en la que este comentario no sea necesario debido a la claridad del código per se, esta inversión de prioridades siempre rendirá mejores frutos.
Utilidad
Los comentarios bien colocados pueden ser útiles, pero los comentarios dogmáticos y antiguos pueden ser perjudiciales. Mientras que en un mundo ideal, los lenguajes de programación serían tan expresivos que no necesitaríamos comentarios, la realidad es que los comentarios a menudo se utilizan para suplir nuestra incapacidad de expresarnos completamente a través del código. A pesar de esto, los comentarios no deberían ser celebrados como una solución ideal, sino vistos como un mal necesario.
Tendencia a desinformar
Los comentarios pueden ser engañosos y desactualizados. A medida que el código cambia y evoluciona, los comentarios tienden a quedarse atrás y pueden volverse incorrectos. Un ejemplo común es un comentario que ya no coincide con el código al que se refiere, especialmente cuando se añaden nuevas variables u otros cambios en el código. Los comentarios imprecisos pueden causar confusión y generar expectativas incorrectas, por lo que es mejor minimizar su uso y concentrarse en escribir código claro y expresivo.
Comentarios versus código claro
Un motivo frecuente para crear comentarios es para explicar código desorganizado o incorrecto. Sin embargo, es más efectivo solucionar el problema en lugar de depender de comentarios para clarificar un código confuso. El código claro y bien estructurado es preferible a un código complejo lleno de comentarios explicativos.
Pues bien, así llegamos al final de esta reflexión, quedan aún muchos temas a tratar en relación a prácticas importantes a tomar en cuenta durante la escritura de código limpio. !Hasta una próxima edición!