Código limpio: Un enfoque práctico

Clean Code

¿Cómo generar y mantener un buen código?

La mayoría de los programadores alguna vez nos hemos encontrado en la situación de tener que trabajar con código que podríamos considerar desprolijo, donde resulta muy difícil comprender qué función cumplen las diversas líneas que estamos leyendo, en ocasiones preguntándonos por qué se están realizando ciertas modificaciones a variables o llamados, pero con miedo de modificarlo por riesgo a romper el código productivo. 

Código productivo: es aquel que es ejecutado en las versiones productivas de nuestros servidores o productos, es decir, que actualmente está siendo utilizado por la verdadera audiencia de nuestro producto o servicio en un entorno real. 

A su vez, debemos admitir que en ciertas ocasiones fuimos nosotros mismos los autores de aquello que hoy nos trae problemas y que prefeririamos desarrollar de nuevo en lugar de agregarle más funcionalidades.

Esos momentos son los que deben hacer que nos replanteemos que dejar los arreglos para más adelante tal vez no sea una gran idea y que, en su lugar,  podríamos considerar aplicar pequeños cambios o diversas prácticas que nos ayuden a construir un código más entendible y sobre el que será más fácil trabajar en un futuro.

En etermax constantemente apuntamos a generar código limpio, es decir, aquel que resulte fácil de entender, cambiar y extender en caso que sea necesario. De esta manera, si tenemos que continuar una feature que nuestros compañeros realizaron podremos comprender fácilmente cómo funciona y con seguridad de que nuestras modificaciones no afectarán a otras partes del sistema sin darnos cuenta. En muchas ocasiones no será necesario consultar con nuestros compañeros sobre cómo fue construida dicha feature, ya que el código mismo será quien nos explique línea por línea qué es lo que está sucediendo.

Empecemos con un ejemplo no tan limpio

Una de las prácticas que optamos por aplicar al momento de construir métodos es mantenerlos cortos y concisos, orientados a cumplir con un único propósito.

Tomemos de ejemplo esta función:



A simple vista no podemos comprender que es lo que sucede por dentro de la misma, tenemos que detenernos en cada una de sus líneas, sobre todo si tenemos variables que no tengan nombres muy intuitivos, para poder distinguir cual es el propósito de dicho método. Si bien tenemos algunos comentarios que intentan ayudarnos en nuestro propósito, podemos ver como en este ejemplo no todas las líneas han sido comentadas y algunos comentarios como “Add permanent injuries” y “Lose 5 coins” no han sido actualizados con los últimos cambios que se realizaron, obligándonos a discernir cuáles de estos comentarios realmente explican un fragmento y cuáles solo prestan a confusión. Por ello, podríamos considerar reducir las líneas de las mismas y centrarnos en que cada uno de nuestros métodos cumpla con el Single Level of Abstraction, es decir, que cada función deberá tratar conceptos que se mantengan en el mismo nivel de abstracción. La manera en que podemos lograr esto es extrayendo nuevos métodos que ocultan los detalles o implementaciones que resultan irrelevantes para el contexto que estamos analizando actualmente. Por ejemplo, si hiciéramos este proceso sobre el código que vimos anteriormente podemos obtener:

Durante esta extracción de métodos también solemos buscar disminuir lo más posible la repetición de código, si por ejemplo, en varios lugares encontramos el mismo fragmento para realizar los mismos cálculos, comprobaciones o ejecutar cierto comportamiento, es ideal extraerlo en un único método y reemplazar todas sus apariciones con el llamado del mismo. De esta manera, estos métodos estarán disponibles la próxima vez que necesitemos replicar dicho comportamiento o comprobación agilizando nuestro trabajo al evitar tener que buscar donde se encontraba dicho fragmento para copiarlo y pegarlo en un nuevo lugar. Pero aún más importante, también evitamos que surjan errores cuando debamos realizar cambios sobre cómo funcionan los mismos en caso de cambiar las necesidades del negocio. Si, por ejemplo, nosotros no tuviéramos funciones extraídas, correremos un gran riesgo de modificar solo algunos de los lugares donde este código se encontraba y generando que sólo partes de nuestra aplicación se comporten de la forma esperada.

Este mismo tipo de prácticas no las limitamos solo a funciones, también las aplicamos a las clases en general. En este caso, un concepto importante que aplicamos son los principios S.O.L.I.D., siguiendo el ejemplo estaríamos haciendo hincapié en la S que significa Single Responsibility. Según este principio, buscaríamos que la clase posea una única responsabilidad y que todos sus métodos se encuentren relacionados con la misma. Por eso, al momento de construir dichas clases y encontrarnos con funciones que no estén alineadas con dicho propósito, buscaremos extraerlas en nuevas clases. De esta manera evitamos tener largos scripts donde es difícil encontrar los fragmentos que necesitamos modificar, a la vez que nos aporta seguridad al momento de elegir en cuál clase deberíamos ubicar código que deseemos agregar si es que no corresponde a una nueva.

Nombres y comentarios de los métodos

Retomando el ejemplo anterior, podemos observar que si bien logramos resumir nuestras funciones y separar mejor los diferentes contextos que se presentaban, aún seguimos sin tener un código completamente limpio y entendible. Esto nos lleva a otro punto que podríamos debatir, los nombres de las mismas funciones. Como vemos en el ejemplo, si nuestros métodos no tienen nombres intuitivos seguimos requiriendo ingresar en cada uno de ellos y leer sus líneas para comprender la funcionalidad que cumple dicho fragmento de código. Si por el contrario, nos tomamos el tiempo de darle a nuestras funciones nombres más explicativos podríamos obtener algo como:



Con estos pequeños cambios que solo llevaron un par de minutos, logramos un código que podemos entender a simple vista y que solo en caso de querer conocer las implementaciones particulares o querer realizar modificaciones de las mismas resultaría necesario navegar a las definiciones de estos nuevos métodos.

A su vez, notamos que los comentarios que se utilizaban para explicar dicho código ya no resultan necesarios y por eso optamos por removerlos. Normalmente consideramos que los comentarios son un síntoma de que la forma en que hemos estructurado el código puede no ser la correcta. Por ejemplo, podría ser un indicador de que podemos extraer métodos en un determinado lugar o que deberíamos plantearnos cuál es la manera de volver dicho fragmento más intuitivo o simple.

El mantener estas prácticas constantemente nos ahorra la generación y mantenimiento de comentarios que, como vimos anteriormente, cuando no son actualizados prestan a confusión de que es lo que realmente ejecuta nuestro código.

Magic numbers

Vamos a ingresar a una de estas funciones para continuar evaluando otras mejoras que podríamos hacer, por ejemplo si tenemos en cuenta:

Es difícil determinar qué significan 0.5f o 0.6f, por qué razones fueron agregados estos números y si se repiten en diversos lugares del código lo que nos llevaría a que, si queremos cambiar dichos valores debamos buscar cada una de sus ocurrencias. En estos casos optamos por introducir una constante con un nombre que indique que representa dicho valor, en este caso podríamos tener algo similar a:

Code smells: How to recognize bad code?

En general hay varios indicadores de que el código que hemos generado no podría considerarse como “limpio”, entre ellos podemos encontrar:

  • Rigidez: introducir cambios en el código resulta muy difícil ya que cada vez que lo hacemos debemos realizar cambios en múltiples otras secciones del código que, en muchos casos, no están completamente relacionadas con las modificaciones.
  • Fragilidad: cualquier cambio que realizamos rompe diversas secciones de nuestro código, introduciendo nuevos bugs y comportamientos inesperados.
  • Complejidad innecesaria: muchas veces existen soluciones más simples que las implementadas. En ocasiones, al trabajar con código en mal estado nos vemos obligados a buscar soluciones que no son las apropiadas pero evitan romper el resto de funcionalidades que ya fueron añadidas de una manera poco ideal.
  • Repetición innecesaria: es uno de los elementos que mencionamos anteriormente, podemos encontrar el mismo fragmento de código en diversos lugares de nuestro proyecto y nunca tendremos la seguridad de cuáles son todos ellos en caso de necesitar modificarlos.
  • Opacidad: el código resulta difícil de entender, cada vez que queramos trabajar con ciertas secciones deberemos destinar parte de nuestro tiempo a comprender qué ejecuta, sus dependencias, qué podemos y no podemos modificar, entre otras cosas.
Maintaining code

Debemos aclarar que no es suficiente con intentar escribir un buen codigo, tambien es importante que logremos mantener la calidad del código sobre el que trabajamos, de ahí la importancia de la aplicación de prácticas como la regla del boy scout que nos indica que siempre debemos dejar el codigo mejor de lo que lo encontramos. Esta regla nos motiva a realizar pequeños cambios como los que vimos a lo largo del artículo para que resulte cada vez más simple trabajar sobre ciertas clases, no solo para nosotros, si no para nuestros compañeros que probablemente deban realizar cambios en un futuro.

Conclusión

Como vimos a lo largo del artículo, podemos aplicar diversas prácticas que nos llevarán a generar un código que sea simple de entender, extender y mantener. Al generar conciencia sobre estos métodos de trabajo, podemos conseguir un equipo que apunte a mantener un código limpio, prolijo, que reciba mejoras constantemente, donde todos y cada uno puedan sentirse cómodos para trabajar y se evite el surgimiento de gran cantidad de conflictos.

De esta manera construimos bases más sólidas sobre las que trabajamos en el día a día, ayudando al trabajo en equipo, a construir features con menor cantidad de errores y, a su vez, ahorrar tiempo que solemos emplear en comprender aquello que ya fue desarrollado y requerimos modificar. 

, ,