¿Es posible entregar software compatible, rápido y correctamente?

¿Te gusta el olor de un libro nuevo? Ese momento en el que quitas el papel que lo envuelve y tus dedos tocan la cubierta lisa por primera vez y el olor a tinta fresca. La sensación de novedad te hace disfrutar, admiras el contenido y las ilustraciones por primera vez; estás inmerso en una ráfaga de curiosidad, absorbiendo completamente lo que trae consigo el nuevo libro.

¿Qué aspecto tiene después de unas semanas o meses de lectura? Junto con sus bordes desgastados por llevarlo en una bolsa, el libro se encuentra en una estantería. Entre las páginas, se ven los lugares en los que dejaste el marcapáginas. El olor ya no es el mismo. El aroma se ha acabado, y el encanto de la novedad ha desaparecido para siempre.
Probablemente te preguntes por qué escribo sobre esto, y qué relación tiene con el desarrollo de software. Cada nuevo proyecto es un poco como un libro nuevo. La perspectiva de las nuevas tecnologías, los marcos, las bases de datos o un nuevo dominio empresarial. Este soplo de frescura y la enormidad de emociones que lo acompañan. También nos decimos que esta vez lo haremos bien. Este proyecto no acabará como otros.

Measure the level of maturity of your software development organisation across 5 distinct areas.

¿Qué ocurre después de varios años de trabajo? Lo que ocurre cuando te das cuenta de que tu proyecto es código legacy. Toda la emoción se esconde entre líneas ilegibles y una arquitectura confusa, cuyo creador ya ni siquiera está presente en el proyecto. La velocidad y la calidad de la entrega de nuevas funcionalidades se resienten por ello. De ser un libro hermoso y fragante, nuestro proyecto se convirtió en otro volumen con los bordes rasgados y la cubierta doblada, escondido en algún lugar de una estantería.

Por supuesto, todo el mundo en la empresa entiende la necesidad de volver al estado anterior. Antes todo iba muy bien y muy rápido. Los programadores estaban motivados, los clientes satisfechos y el número de errores de producción no era demasiado elevado. Hay que recuperar la antigua gloria, preferiblemente reescribiendo el proyecto desde cero. Se eligen nuevas tecnologías, tal vez un lenguaje diferente, como si la falta de paréntesis junto a "si" que determinó la calidad del código. Se selecciona un grupo especial y se le encomienda la misión secreta de reescribirlo todo. Esta vez de forma correcta, como si la intención fuera otra al principio. Todos conocemos la historia y sabemos cómo termina. Reescritura significativa del monolito o refactorización del gran proyecto.

¿Existe una cura para esta enfermedad? ¿Se puede hacer algo para evitar que el próximo proyecto acabe en la estantería del código legacy? Sí, por supuesto.

Velocity - Time

Veamos el diagrama anterior. Muestra la relación entre la duración del proyecto y la velocidad de entrega de nuevas funcionalidades. Por lo que podemos ver, todo va muy rápido en la primera fase de la vida del proyecto. Con el tiempo, a medida que el sistema se expande y su nivel de complejidad aumenta, el ritmo de trabajo se ralentiza hasta llegar a su nivel crítico. Este proceso se puede observar muy bien en proyectos que duran varios años. He trabajado en proyectos de este tipo y recuerdo las preguntas que siempre surgían en el grupo de gestión. ¿Por qué el desarrollo va tan lento?

El problema al que nos enfrentamos no se manifiesta en el primer año del proyecto, sino en una fase posterior, cuando ya estamos metidos de lleno en él. ¿Es posible entregar rápidamente un software que cumpla los requisitos del cliente y tenga una alta calidad en todas las etapas de su vida? ¿Incluso después de muchos años? Sí, es posible.

Enumeremos los principales obstáculos que pueden encontrarse en los proyectos antiguos. Arquitectura inadecuada o falta de ella. Bibliotecas y herramientas obsoletas. Código ilegible y difícil de refactorizar. Código que nadie entiende. "No lo toques o lo romperás".

De hecho, cada uno de ustedes se ha encontrado con este tipo de situaciones en sus proyectos más antiguos. Vamos a describir cada uno de estos puntos, sucesivamente, para mostrar qué tipo de soluciones podríamos aplicar.

 

Arquitectura inadecuada

Empezamos a trabajar en un nuevo proyecto, y una de las primeras preguntas es qué base de datos vamos a utilizar. ¿Relacional, basada en documentos o algo más parecido a una caché? En una de sus conferencias sobre arquitectura limpia, Robert Martin dijo que un buen arquitecto pospone las decisiones críticas. Es un consejo excelente. Si no lo sabemos, nuestro sistema aún no ha alcanzado el nivel de madurez que nos permitiría verificar qué tipo de base de datos se adaptaría mejor a nuestra aplicación. ¿Es un ejemplo puntual o es algo que se repite muchas veces en cada proyecto?

Bibliotecas y herramientas obsoletas.

¿Qué ocurre con la estructura del código? ¿Cuántas veces has creado una jerarquía de paquetes impulsada por los requisitos del marco de trabajo que estás utilizando? ¿Qué pasaría si de repente decides cambiar el framework? ¿Qué pasaría con toda tu estructura? Se convertiría en una reliquia del pasado, y tendrías que explicar a cualquier nuevo programador de dónde procede.

¿Qué pasa con el uso de bibliotecas en nuestro código? ¿Qué pasa con herramientas tan grandes como Hibernate, Play o Spring? ¿Has intentado alguna vez sustituirlas o eliminarlas? ¿Recuerdas el dolor de migrar de una versión a otra? La razón, por supuesto, era la ubicuidad de estas herramientas en el código del proyecto. Crecieron como virus, infectando las clases vecinas.

 

Código Legacy

Luego tenemos clases antiguas, de varios años, con miles de líneas de código. Nadie recuerda qué hacían, cuáles eran sus implicaciones. ¿Cuál era su propósito en el momento de su creación? Cada modificación en ellas puede dar lugar a errores que aparecen mágicamente en producción hasta que, finalmente, alguien de los programadores con más experiencia en el proyecto te dice a la cara: "no toques o se romperá".

 

¿Qué podemos hacer?

Alguien me dijo una vez que la única constante en el universo es el cambio. Nada es eterno y nunca podemos esperar que las cosas permanezcan igual. Estas palabras las recuerdo muy profundamente porque hay una lección universal en ellas. Si todo lo que creamos en algún momento se borra o cambia, en lugar de luchar contra ello, podríamos facilitarlo. Intentemos abordar los problemas descritos anteriormente desde esta perspectiva. ¿Qué puedo hacer para facilitar mi trabajo dentro de tres meses o un año?

 

Arquitectura hexagonal

Si la arquitectura, las herramientas o el tipo de base de datos pueden cambiar, quizá sea mejor dejar la puerta abierta en nuestro proyecto. ¿Cómo se puede hacer esto? ¿Has oído hablar de algo llamado arquitectura hexagonal o puertos y adaptadores?

La idea es desconectar el dominio, el núcleo de la aplicación, de todo el código relacionado con los frameworks, el almacenamiento de información o los métodos de comunicación como HTTP. El código está físicamente desconectado, y toda la comunicación entre el dominio y el llamado framework se realiza a través de puertos, en el caso de clientes y repositorios, y adaptadores para la comunicación de tipo HTTP o UI.

Gracias a esta división del código, cualquier cambio en la librería u otra herramienta debería cambiar sólo el código colocado en la parte del framework. ¿Qué nos aportará? Cualquier cambio de arquitectura no tendrá un impacto significativo en nuestra aplicación. Gracias a esto, nos libraremos de los tics de las malas decisiones y de las viejas herramientas.

¿Cuál es el inconveniente de esto? Tendremos que escribir más código, lo que a veces nos parecerá una sobreingeniería, pero créeme, tiene una base sólida. Como resultado, pasaremos más tiempo programando para acelerar los cambios en el futuro.

 

Aplicaciones pequeñas y desacopladas

El segundo punto importante es el tamaño de la aplicación. Si estamos trabajando en un monolito con miles de clases, será un reto llevar a cabo una refactorización eficiente. Lo que necesitamos es una forma de dividir nuestro proyecto en una serie de pequeñas aplicaciones disjuntas. Podemos utilizar conceptos como los microservicios o una aplicación con módulos desacoplados.

Lo más importante es que nuestras pequeñas aplicaciones sean de un tamaño que se pueda reescribir en una semana. ¿Para qué nos va a servir? ¿El código es ilegible y nadie lo entiende? Reescribir la aplicación. ¿El modelo ya no se ajusta a los requisitos de la empresa? En lugar de tapar las deficiencias, reescribe la aplicación con un nuevo modelo que se ajuste. No esperes que tu código escrito hace tres años sea actual. Recuerda que todo cambia. Crea tu aplicación de forma que la adaptación a las necesidades futuras sea lo más fácil posible.

Dividir la aplicación en otras más pequeñas dará lugar a la necesidad de implementar la comunicación entre ellas. Creará un trabajo extra que no habríamos tenido con un monolito.

 

Buena cobertura de pruebas unitarias

El último punto son las pruebas unitarias. Espero que ya no sea necesario hablar de las pruebas, y que todo el mundo haya entendido sus ventajas, y hasta que se nos ocurra algo mejor, nos quedaremos con ellas durante un tiempo. Desde la perspectiva de los problemas que hemos mencionado, ¿qué puede hacer por nosotros una buena cobertura de pruebas unitarias?

Las pruebas describen el comportamiento de los métodos, por lo que constituyen una especie de documentación viva. Gracias a ello, podemos entender de qué es responsable nuestro código. Conocemos su impacto en otras partes del sistema, sus dependencias, relaciones y qué comportamiento se espera de él. Nos da la confianza en el desarrollo de software de que nuestro código es correcto y nos protege contra posibles errores durante la refactorización. Gracias a esto, no tendremos nada en el código que, al cambiarlo, estropee mágicamente nuestra producción.

Las pruebas también son código, por lo que hay que escribirlo y luego mantenerlo. Esto añadirá trabajo extra a los desarrolladores y ralentizará ligeramente el ritmo.

 

Resumen

Después de observar detenidamente nuestros problemas y las soluciones propuestas, encontramos una interesante reflexión. La mejor manera de escribir código rápido es escribirlo... más lento. Las técnicas que hemos mencionado harán que el proceso de desarrollo del producto sea más lento en la primera fase del proyecto, pero mejorarán la calidad y la rapidez del trabajo a largo plazo.

Si estás interesado en otros métodos para mejorar la calidad del trabajo, puedes utilizar la herramienta compass.codurance.com preparada por Codurance. Con unas sencillas preguntas, podrás definir la madurez de tu proyecto y obtener sugerencias sobre cómo mejorar la situación actual. Además, en futuros artículos profundizaremos en los temas aquí tratados. El tema de la calidad de los proyectos es tan amplio que un solo texto no puede hacerle justicia.

¿Recuerdas cuando hablamos del libro al principio? ¿Cómo sería si pudieras tener capítulos separados físicamente en lugar de un solo libro? Por ejemplo, veinte libros pequeños en lugar de un gran volumen. No es todo. ¿Y si el autor reescribe uno de los capítulos cada mes, añadiendo nuevos héroes, nuevos acontecimientos, nuevos lugares? ¿Sería más interesante un libro así? ¿Le gustaría volver a él cada mes, y en qué estantería lo pondría? ¿En la que lleva la firma de "código legacy", o tal vez en la de "todavía fresco y caliente"?

Mide la madurez de tu software