Me propongo aclarar algunos términos.

Cuando comenzamos un nuevo proyecto software, cualquier tipo de aplicación, raramente sabemos con exactitud cómo lo vamos a implementar; quiero decir, qué estructura de clases habrá, cómo será la mejor forma de organizar los assets del proyecto, qué interfaces surgirán y hasta qué decisiones de diseño y arquitectura deberemos tomar. Como digo, tendremos si acaso una aproximación mental pero no la forma completa y exacta que tomará la solución. 

Es cierto que con el tiempo y la experiencia, ya vas teniendo una idea clara antes de comenzar según el tipo de proyecto y de aplicación.

Por tanto, en realidad hasta que no se va implementando funcionalidad, no sabremos la mejor forma de hacer las cosas, lo que no es más que una versión de aquello de "caminando no hay camino: se hace camino al andar".

Puedo asegurar esta ley universal: la primera aproximación nunca es la mejor.

Lo peor que le puede ocurrir a una aplicación es dejarla exactamente tal y como surgió en su implementación inicial, sin refinar ni mejorar absolutamente nada de su estructura y diseño. Cuando hablo de aplicación, podemos bajar de nivel a librería, módulos, clases, métodos, etc. que el principio se sigue cumpliendo.

La construcción de una aplicación de calidad es un proceso de mejora incremental y continuo y, en realidad, siempre quedará algo por mejorar.

Todo se complica cuando vamos añadiendo más y más funcionalidad, alguna que ni siquiera esperábamos ni imaginábamos, algo totalmente natural en el desarrollo de software, de modo que el peor error que podemos cometer es añadir esa funcionalidad en el esqueleto anterior ya realizado, de cualquier modo. De ahí, con el tiempo, tendremos el terreno abonado de una aplicación inmatenible y difícil de entener y evolucionar.

Refactorizar es esencial para evitar la situación anterior, y cuando hablamos de refactorizar nos refererimos exclusivamente a la aplicación de ciertas técnicas que permiten mejorar el microdiseño del código que hemos escrito. Son pequeños pasos en los que cada uno mejora un diminuto aspecto de la solución.

Por extensión, también hablamos de refactorizar como sinónimo de mejorar cualquier elemento de la aplicación, y está bien que extendamos ese concepto aunque en origen se refiere exclusivamente a la aplicación de técnicas muy concretas y bien documentadas para mejorar el código. De ahí que aunque no sea demasiado correcto, se pueda entender también qué se quiere decir con refactorizar el diseño de una base de datos, un script para esto o lo otro, etc.

Ahora bien, ¿para qué refactorizar si un trozo de codigo o la misma solución en conjunto "ya funciona"?

Toda solución software debe ser diseñada para admitir cambios con facilidad, algo que le va a ocurrir con el 99% de probabilidad. Por tanto, refactorizar nos permite tener una aplicación con mejor diseño y mejor estructura, más entendible y más mantenible.

Pero también hay aspectos sutiles que solo se aprenden con la experiencia: refactorizando, conseguimos que nuestro código sea más testeable, pero también al revés: nuestro interés por hacer tests nos lleva a la obligación de refactorizar (consciente o inconscientemente). Este es uno de los aspectos del desarrollo de software que más me gustan, porque muchos elementos de la naturaleza del código son sutiles de asumir.

Refactorizar va de la mano de dos conceptos y está tan intrincados en ellos que es imposible hablar de una cosa sin la otra: la orientación a objetos y el testing.

Una aplicación programada en un lenguaje orientado a objetos permite una mejor estructura y una mejor solución porque nos permite abstraer un problema en entidades mentales más comprensibles a nuestro modo de pensar (objetos), mientras que el testing es un requisito ineludible para garantizar la calidad y el correcto funcionamiento de la misma.

Si cambiamos algo, por mínimo que sea en el código, ¿cómo sabemos que todo sigue funcionando? Exacto, con tests.

Por tanto, es imposible o incluso peligroso aplicar técnicas de refactoring si la aplicación no está suficientemente cubierta y respaldada por tests.

En software no es una buena práctica mejorar lo que sea al final, sino que es más productivo insertar toda mejora en el día a día, como parte misma de la actividad de desarrollo. Esto lo dice la teoría, pero es que lo he comprobado yo mismo a lo largo de todos estos años. Mejorar el código continuamente permite alcanzar más velocidad de desarrollo sin ninguna duda.

Esto es, las técnicas de refactoring se aplican continuamente y de forma incremental. Es sorprendente cómo pequeñísimos cambios en una aplicación pueden tener un efecto enorme y acumulativo en la aplicación final.

Existen muchas técnicas bien documentadas desde que apareció este concepto en el libro de Martin Fowler y Kent Beck, y muchas otras que diferentes autores han propuesto desde que en nuestra industria se asumió que el código se debe mejorar continuamente a pequeños pasos incrementales.

Incluso una aplicación que no ha sido refactorizada en este sentido nunca y que incluso no tiene buena cobertura de tests, se puede enriquecer aunque haya que comenzar desde cero su refactorización, aunque sea poco a poco.

Si quieres saber más sobre refactoring, detallo las técnicas más comunes en mi último trabajo El Libro Práctico del Programador Ágil, junto con infinidad de prácticas esenciales para crear código de calidad. 

Este comienza a convertirse en uno de mis mantras favoritos: "refactorizar o morir".

Recientemente he vuelto a poner en práctica una de las mejores virtudes de realizar este tipo de trabajo continuamente. El resultado finalmente es muy satisfactorio después de muchos momentos tipo "esto está quedando fatal", "así sólo voy a llegar a una solución muy enmarañada", etc. El desánimo cunde rápido, sobre todo si se trata de un pequeño proyecto que haces a ratos por las noches o fines de semana.

No obstante, una mínima tenacidad (y seguramente tirarlo todo a la basura y volver a empezar en algún momento), te hace llegar a una buena solución que no sólo funciona, sino que además es ampliable y evolucionable con cierta facilidad.

Se habla mucho de productividad; para mí es muy sencillo describir qué es productivo y qué no en software: las soluciones fáciles son más productivas que las inextricables que sólo pueden entender sus autores, aquello que nos permite ganar velocidad de desarrollo es más productivo y con ello conseguiremos más con menos esfuerzo. Nada más. Así de contundente y simple.

Hay quienes se procupan de mejorar el código de una aplicación en alguno de sus aspectos en algún momento del trabajo: al final, cuando ya todo funciona, de vez en cuando... Sin embargo, las ventajas de incorporar estas tareas de mejora en todo momento no siempre se aprecian como productivas, mucho menos cuando nos acercamos peligrosamente a las fechas de entrega y comenzamos a dejar cabos sueltos (de los que nos acordaremos sin duda semanas o meses más tarde).

¿Por qué refactorizar cuando lo importante de una aplicación es que le funcione al cliente? Buena pregunta, y al mismo tiempo, ingenua. Quienes aún no ven claro las virtudes de invertir tiempo en este trabajo, deben saber que lo primero que se gana es velocidad de desarrollo, por tanto, productividad.

La velocidad con la que desarrollamos cualquier pieza de software viene a ser una medida de la productividad, sobre todo cuando ésta nos acerca al objetivo de entregar las funcionalidades que nos piden.

Tenemos que perseguir que el diseño o la estructura de la solución que vamos construyendo, con el tiempo nos permita avanzar más rápido y no lo contrario.

En esa aplicación en la que llevo trabajando un tiempo he ido aplicando continuamente todas las técnicas de mejora posibles: simplificaciones, abstracciones en clases de utilidad, mejora en la estructura de la solución, modularizaciones, etc, de modo que cada dos semanas he podido ver cómo el código de un commit de hace quince días no tenía nada que ver con el del último. Esto cobra especial relevancia cuando desde el principio no tenemos claro cómo vamos a implementar ciertos aspectos de la aplicación un poco complicados.

A medida que avanzamos en la solución y gracias a que aplicamos todo ese trabajo de mejora, vemos cómo poco a poco va surgiendo (emergiendo) un diseño cada vez mejor. "Mejor" es una palabra muy subjetiva y difícil de consensuar entre dos o más personas. Para mí, en software, algo es mejor si te permite desarrollar más rápido (invertir menos tiempo en la misma solución) sin perder calidad en todos los aspectos de esta.

Ese diseño elegante, sencillo, correcto, es el que nos permite a partir de un punto ganar velocidad. Llega un momento en que esto es así y es entonces cuando te das cuenta de que todo ese trabajo de mejora ha sido en realidad una buena inversión de tiempo que ahora se va a amortizar. Es como si tuviéramos una madeja de hijo que al principio cuesta mucho desenredar hasta que llega el momento en que es fácil y rápido sacar más y más hilo de ella.

En ese proyecto en concreto que estoy realizando, ha llegado un momento que he conseguido resolver todas esas partes más difíciles de manera tan sencilla y elegante que ahora lo que me queda es decorar el resto hasta finalizar la aplicación. Esto no habría sido posible si no me hubiera parado al principio con esa idea de mejorar la aplicación en todos sus aspectos.

Los comienzos de una aplicación en la que hay muchas dudas que resolver son duros, cuesta apreciar verdaderamente avances y tenemos la tentación de tirar por lo rápido y fácil, sin darnos cuenta que ese camino se volverá en nuestra contra con el tiempo.

Llega un momento en que esa mejor arquitectura y diseño para nuestro propósito es tan maduro que ya sólo nos queda seguir esa coherencia para terminar la aplicación con éxito.

Lo mejor, además, es que llegaremos al final con una solución limpia y fácil de mantener y evolucionar.

Algunas de las técnicas que he empleado hasta el momento son las siguientes:

  • A medida que el código crecía, he ido adaptando la estructura y distribución de este mucho mejor.
  • Clases largas las he ido abstrayendo en clases más concretas y sencillas con una relación lógica entre ellas (principio SRP).
  • Duplicidades las he aislado correctamente en sus servicios correspondientes (el frontend está basado en AngularJS).
  • He ido buscando continuamente todo aquello inyectable, es decir, todas aquellas dependencias que se podían sacar para implementar Inyección de Dependencias.
  • He simplificado muchos bloques de código similares con sus correspondientes funciones de utilidad.
  • Métodos de más de tres o cuatro parámetros los he ido simplificando.
  • y un largo etcétera.

Al final, todo este tipo de trabajo constituye un entrenamiento por el que terminas haciéndolo de la manera más natural y ni te planteas conscientemente el realizar esas actividades como algo separado del trabajo de desarrollo, sino como algo consustancial al mismo.

En El Libro Negro del Programador se insiste mucho (como en cualquier otro libro que nos enseñe a ser mejores profesionales) que la refactorización de código no es sólo una herramienta que debemos usar sino que ésta es un hábito que emplear en el día a día y que distingue a los mejores desarrolladores de software.

No obstante, el concepto de refactorizar se suele entender exclusivamente como un trabajo de modificar líneas de código y mejorarlo sin afectar a su comportamiento externo; conseguimos, efectivamente, limpiar en cierta medida el código para que sea de mejor calidad, más legible y mantenible.

Siempre insisto en que esto no es algo que se hace o no, sino que forma parte del hecho de programar una solución, es algo consustancial a la misma.

Cuando terminamos de implementar una nueva funcionalidad, siempre nos debemos preguntar ¿podemos mejorar lo que hemos implementado en algún aspecto?, ¿se puede simplificar?, ¿se puede mejorar en relación al proyecto general como parte que se pueda reutilizar?, etc. Lo sorprendente es que cuando te creas el hábito de plantearte este tipo de cosas apenas termina haciendo falta hacerlo al final de implementar algo, ya que tienes el hábito de hacer las cosas limpias desde un principio de modo que al final, es esfuerzo o tiempo que empleas como puro trabajo de refactorizar es mínimo.

No obstante, se suele entender por refactorizar el trabajo de mejora del código, pero ¿qué ocurre con la misma estructura de un proyecto que va creciendo y creciendo con el tiempo? No hablo de diseño, sino de cómo están organizadas las carpetas, subproyectos, etc.

Cuando un proyecto crece en tamaño (en forma de librerías dispersas, multitud de sub-proyectos, demasiadas dependencias externas, incluso una estructura de los proyectos de pruebas compleja) se debe y tiene que refactorizar igualmente.

Recientemente hemos cerrado una nueva revisión de uno de los proyectos en los que trabajamos actualmente (la Plataforma de Telegestión y MDM IRIS para dispositivos de telemedida PRIME). Pues bien, he planteado unos días de trabajo para mejorar la estructura interna del mismo para evitar que sea cada vez más compleja (de gestionar y de trabajar sobre ella).

En concreto, algunas de las cosas que vamos a evaluar son:

  • Unificación de algunas librerías dispersas que podrían vivir en un único proyecto.
  • Integración de algunos subproyectos pequeños que podrían vivir coherentemente en otros.
  • Mejora de la distribución y estructura de las puebas de integración.
  • Plantear las bases para que el gestor de tareas, ahora muy ligado a la idiosincrasia de la solución, viva como un framework independiente del proyecto (y reutilizable por otros proyectos que pongamos en marcha).
  • etc.

¿Qué pretendo conseguir con esto?

Cuando un proyecto crece en complejidad, cuando hablamos de cientos de miles de líneas de código, aunque esté todo realizado con extremada limpieza y con una arquitectura general y microdiseños muy cuidados, la misma distribución de los artefactos del proyecto puede ser un problema que impida llegar a los sitios con sencillez y comodidad. La espaguetización del proyecto no sólo se puede producir en su código sino también en la estructura misma de sus módulos.

De este modo, al mejorar y simplificar cómo están distribuidas y organizadas las cosas, ganamos agilidad y comodidad a la hora de trabajar en una nueva revisión. Lo que no es fácil de ver es que esta agilidad y comodidad se traduce después en ahorro de muchas horas de trabajo. Coseguimos la máxima calidad en algo cuando tenemos un entorno cómodo de usar y de trabajar. La facilidad de mantenimiento también tiene mucho que ver con esto.

Detrás de cada acto de refactorización, del tipo que sea, se esconde un ahorro de tiempo futuro al dedicar menos esfuerzo y tiempo en evolucionar una aplicación bien hecha, o lo que es lo mismo, el tiempo invertido en refactorizar se traduce en una mejor productividad (= menos recursos económicos necesarios para hacer algo).

Mis libros en todas las tiendas:

Amazon
Google Play
Apple
Kobo
Barnes and Noble
Scribd
Smashwords
Payhip
Gumroad

Rafael Gómez Blanes Rafael Gómez Blanes
¿Hablamos?

 

Trabajo en...

Archivo

Mis novelas...