Código limpio: causas y efectos

8 mayo
Viktor Svirskiy, Sr Python Developer / Team Lead
Código limpio: causas y efectos
Cada programador tiene su propia definición de clean code. Por lo general, en las entrevistas laborales me dicen que un buen código es aquel que es fácil de leer. Y estoy de acuerdo, pero esa es solo una parte.

La primera señal de que un código ya no está limpio, es el tiempo prolongado de desarrollo para nuevas funcionalidades, y un incremento en el margen de regresión para cambios mínimos en el sistema. Esto resulta de la acumulación de deuda técnica, la estrecha relación de componentes del sistema y la falta de pruebas automatizadas.

Las razones por las que esto sucede pueden ser:

  • Externas, tal como las presiones que surgen por parte del sector empresarial y su afán de obtener las nuevas funcionalidades de la forma más rápida.
  • Internas, como los procesos de desarrollo poco definidos y una interacción deficiente entre los miembros de los equipos, la falta de control y estándares de desarrollo, o simplemente la falta de competencia profesional.

Pero no debe postergarse todo el trabajo para garantizar la calidad del código y la arquitectura. Por ello, durante el proceso, es importante poder diferenciar las tareas que afectan directamente a los negocios (por ejemplo, las que se relacionan con nuevas funcionalidades) y las tareas técnicas que afectan solo indirectamente. El modo de separarlas dependerá del estado del proyecto, los plazos y el volumen de trabajo actual.

¿Qué es el código limpio?

Resulta que la legibilidad del código no alcanza para afirmar que está limpio y el sistema bien diseñado, sino que también debe contar con otras cualidades:

  • Debe poder modificarse fácilmente. Con el diseño y la arquitectura correcta, la extensión del código no debería demandar mucho tiempo y requeriría costos técnicos mínimos. El código debería ser parcialmente abstracto y autosuficiente. Además, las entidades no deberían guardar relación estrecha entre sí: durante el desarrollo, cada una debería ser responsable solo por su parte de funcionalidad.
  • Debe ser estable, predecible y confiable. Sin importar qué tan legible sea, se le deben realizar las pruebas pertinentes: los códigos correctos van de la mano con las pruebas. Y la calidad de éstas, es tan importante como su cantidad. Tal código no provoca ningún problema durante la ejecución y la depuración, no ocasiona ningún cambio en el entorno.   
  • Debe ser seguro. Al momento de escribir un código, no debemos olvidar la seguridad general del producto. Recomiendo estudiar las normas de seguridad general y respetarlas. Para proyectos web recomiendo OWASP.
  • Prácticamente, un buen código no existe. No significa que todos deben estar escritos en una sola línea ni que deberías sentirte orgulloso de tus métodos delicados. Sino que el código no debería ser duplicado y algunas cosas deberían mantenerse en el nivel de abstracción. En teoría, la simplificación de código debería conducir a tener menos errores.   
  • La legibilidad del código en sí también es importante. Cada desarrollador tiene su propio estilo de código y la legibilidad depende de nuestra experiencia. Todos queremos escribir códigos simples, hermosos y concisos.

El code smell es un término que se utilizó por primera vez en 1961 para evitar una evaluación subjetiva de la calidad del código. Es un conjunto de reglas y recomendaciones que ciertamente determinan si llegó el momento de refactorizarlo. La ‘hediondez’ conduce a rupturas de código y los desarrolladores siempre deberían intentar eliminarlas: al prevenir la causa, podemos evitar las consecuencias. Para mayor información sobre las señales claves que indican que es necesaria la refactorización del código, recomiendo consultar el libro de Martin Fowler: Refactoring: Improving the Design of Existing Code.

¿Vale la pena escribir un código limpio?

¡Sin dudas! Pero no siempre ni en todos lados.

No debe olvidarse la conveniencia y longevidad del código. Por ejemplo, si se presenta la tarea de desarrollar una PoC, y considerás que la tecnología seleccionada completará la tarea, tu código estará desactualizado en una o dos semanas. No vale la pena mejorar esta funcionalidad.

Si bien existe la teoría de que no es necesario controlar la calidad del código o parte del sistema que pronto será reemplazado, no creo que esto sea correcto por varios motivos: un rendimiento de alta calidad hará que la transición o la integración con las nuevas partes resulte perfecta, más sencilla y más veloz.

Sin dudas, simplificará la vida en aquellos casos donde deberán respaldarse varias versiones del código al mismo tiempo. El código limpio hará que disminuya la cantidad de errores de regresión. Además, no debemos olvidar que nada es más permanente que lo temporario y las tareas para mejorar ciertas partes del código pueden permanecer archivadas por varios meses.

¿Cómo mejorar el código?

La mayoría de los programadores sueñan con escribir códigos de la forma más rápida y hermosa posible, de modo que todo funcione perfectamente desde el principio. Pero no todos logran que su código sea viable, ni siquiera entendible. Entonces, ¿cómo se lo puede escribir de forma exitosa?

Existen dos formas: la autogestión y el trabajo en equipo.

Autogestión

Existen varias formas posibles de mejorar individualmente la calidad del código. Estas recomendaciones resultarán útiles para desarrolladores de cualquier nivel:

Poner atención a las herramientas que se utilizan

Este punto es fundamental, en especial cuando nos referimos al entorno de desarrollo. Una herramienta práctica supone media batalla ganada. El entorno de desarrollo integrado no solo complementa al código sobre la marcha, sino que también indica si requiere refactorización. Al extenderlo con algunos programas adicionales, se obtiene una navaja suiza. Gran parte del trabajo se realiza con esta herramienta, por lo que es aconsejable hacerlo de la forma más conveniente y flexible posible. Vale la pena invertir en un producto original; usar una versión ‘pirata’ de software será una pérdida de tiempo y esfuerzo.

2. Participar en proyectos de código abierto

En un equipo, es importante poder trabajar con el código de otros. No siempre es sencillo entender, leer y amoldarse a su estilo; pero por lo general, es una oportunidad para aprender de nuevos enfoques para solucionar problemas no triviales. Se debe leer y analizar el código.

3. Establecer límites estrictos

Cuando se desarrolla un proyecto pequeño para solucionar un problema específico, y se diseña e implementa una arquitectura propia, se pueden establecer también límites técnicos propios. Es importante generarse este hábito de trabajar con límites. Después de todo, no siempre habrá alguien controlando la calidad de nuestro trabajo.

4. Mejorar el pensamiento abstracto

Es recomendable leer y utilizar patrones de programación, los cuales no están relacionados con ningún lenguaje particular y ayudan a solucionar problemas de forma más efectiva. Así, se podrá comprender mejor a otros desarrolladores, cómo funcionan las herramientas y las bibliotecas de terceros. Asimismo, resolver enigmas de programación es una gran forma de mejorar habilidades y aprender los subtítulos de un lenguaje específico.

5. Analizar

Antes de precipitarse a buscar soluciones, es bueno hacerse planteos e incluso interrogar a desarrolladores senior.  Siempre es importante comprender la causa y efecto de una decisión: si se entiende bien el problema, es posible solucionarlo de forma efectiva. Un buen desarrollador no es un artesano que escribe códigos, sino un ingeniero cuyo trabajo combina la investigación, la planificación y el diseño.  

6. Leer la documentación

Este punto es tan importante como leer el código. El próximo paso será aprender a escribir documentación.

7. ¡Practicar!

Toda experiencia es útil, y definitivamente es mejor que no tener nada de práctica.

Trabajo en equipo

La mayor parte de las tareas se solucionan en equipo, por lo que resulta muy importante compartir la responsabilidad de la calidad entre todos los miembros. Cuanto más grande sea el equipo, más difícil será mantener las buenas condiciones del producto. Sin embargo, se pueden tener en cuanta varios enfoques para desarrollar un código decente:  

1. Revisión del código

Este método es sencillo para la comprensión y la aplicación:  al menos dos personas deben revisar el código, incluido su autor. Aquí existen varias cuestiones para tomar en cuenta:

  • Revisar si cumple con las normas de codificación. Este proceso puede y debería ser automatizado por medio de analizadores estáticos en integración continua.
  • Otras cuestiones son el mantenimiento de código y la gestión de errores que no pueden controlarse de forma automatizada.
  • Por último, pero no menos importante, se debe controlar que el código esté completo: ¿este fragmento de código contiene la función completa que se esperaba?

2. Integración continua

Permite obtener un feedback comprensivo sobre el estado actual del código en un período de tiempo muy corto. La integración continua funciona bien cuando sigues estas dos reglas sencillas:

  • El montaje del producto debería ser rápido. No permitas montajes lentos; si no se aprueban los testeos, el montaje fracasará y serás notificado de inmediato.
  • Deberían agregarse analizadores estáticos al guion de montaje para controlar las nomas de codificación, mejorar la calidad del código y verificar la seguridad.

3. Normas de codificación

Es importante tener una lista con todas las normas, pero antes, los miembros del equipo deben comprender la importancia que éstas tienen. No esperes convenirlas de inmediato, seguramente habrá mucho debate previo.

La cantidad de reglas que agregues a esta lista es ilimitada y puede variar, lo importante es hacer lo que funcione mejor para el equipo. Incluso, pueden irse agregando nuevas normas, así como también se pueden eliminar otras. Lo único fundamental es respetarlas, y la mejor forma de hacerlo es verificarlas con analizadores estáticos y con integración continua, ya que no requiere ninguna función manual.

Un código de calidad puede acelerar el desarrollo de software de largo plazo. Puede reutilizarse, y los desarrolladores no perderán tiempo arreglando errores antiguos. También facilita la incorporación de nuevos miembros al equipo.

4. Pruebas

Eliminan errores críticos y garantizan un código de buena calidad y que funcionará tal como se espera. En este punto, es importante contar con una estrategia clara. Al menos, el código debería ser modular e incluso es mejor si se desea utilizar otros métodos, como por ejemplo: pruebas de integración o regresión.

La gran mayoría de las pruebas en proyectos de programación deben ser unitarias. Son económicas y rápidas. Existen varias herramientas para ayudar a crear pruebas modulares e informes de cobertura de código, y puede realizarse a través de la integración continua. Incluso la realización de un montaje puede llegar a fallar si la cobertura de código no cumple con el porcentaje requerido.

5. Análisis de errores

La existencia de errores en el código probablemente sea inevitable, por lo que es muy importante analizarlos y saber cómo tratar cada uno. Para mejorar las habilidades, es importante aprender de los errores.

Cuando ocurre un error, se deben analizar algunas preguntas:

  • ¿Es un error de alta o baja prioridad?  En el primer caso, debe corregirse de inmediato. Si por el contrario es un error menor y permite que el producto cumpla sus funciones sin ningún problema, puede corregirse en las próximas repeticiones.
  • ¿Qué fue lo que salió mal?
  • ¿Por qué no lo revisamos (de la forma correcta)?
  • ¿Puede volver a ocurrir? ¿Dónde?
  • Y lo más importante, ¿cómo podemos prevenir que suceda algo similar en el futuro?

6. Recopilación de métricas

Existen varias herramientas que se pueden utilizar para medir la calidad de un código. SonarQube, por ejemplo, se encarga de esta tarea con facilidad y ayuda a recolectar las métricas más importantes que se necesitan:

  • Posibles errores: la cantidad y gravedad de los errores son indicadores muy importantes. Su búsqueda debería ser automatizada, pero solo de forma parcial; deberían revisarse para detectar errores más profundos en la misma lógica del código. Cada componente del conocimiento debería tener una representación consistente y acreditado dentro del sistema: el principio de ‘don't repeat yourself’.
  • Métricas de complejidad: normalmente, se calcula utilizando complejidad ciclomática, indica la cantidad de caminos linealmente independientes en el código del programa. Existe una correlación entre la complejidad ciclomática y la frecuencia de errores. En teoría, la simplificación de código debería disminuir los errores.
  • Comentarios necesarios: solo algunas líneas separadas correctamente con comentarios: un comentario sobre un módulo, una clase o un método será suficiente para que el código sea mucho más limpio.
  • Criterio de cobertura de prueba: utilizado para pruebas de software, muestra el porcentaje del código abierto del programa que fue ejecutado durante el proceso de prueba. Establece el criterio para un porcentaje de las pruebas y se adhiere a ese criterio.

El error en un código es como el impacto ambiental. Un caño de escape por sí solo no hará ningún mal, sin embargo, reducir el daño que en su conjunto le hacen al planeta es actualmente una necesidad innegable. Asimismo, escribir códigos limpios resulta ser una responsabilidad de cada desarrollador. Sin importar qué camino se elija, se debe intentar escribir un código funcional y limpio.

Es mejor si logramos que un código limpio no se convierta en un fetiche, al considerar su longevidad y conveniencia de futuras mejoras. Es importante recordar lo siguiente: por un lado, están los usuarios, quienes pueden verse afectados por una repentina falla en incluso una pequeña parte de un sistema que desarrollamos; por el otro, los ingenieros, quienes deberán brindar mantenimiento al sistema.