Hola de nuevo a todos, el tema que voy a tratar en este post en su momento me ocasionó un shock acompañado de su posterior revelación (es que soy un tío simple e impresionable).
En el anterior post de esta serie comenté que había que valorar el coste de complicar nuestro diseño contra el tiempo disponible y los beneficios a largo plazo que nos iba a reportar. En el caso de un proyecto gestionado mediante el paradigma ágil, el equipo de desarrollo debe ser capaz de aceptar muchos cambios a un ritmo alto. Estos cambios provienen al menos de dos fuentes diferentes: cambios o malentendidos a nivel funcional, y otros ocasionados por tener que entregar funcionalidad de forma continua, lo que nos deja en la posición de tener que cambiar y extender nuestro diseño de forma interminable. Como conclusión tenemos que nuestro diseño debe ser barato y rápido de cambiar y que toda técnica orientada a este fin es valiosa.
¿Cómo podemos abaratar y agilizar los cambios en el software? ¿DRY, SOLID, OO, Modelo de dominio? Bueno, no vayamos tan rápido, centrémonos primero en lo esencial. Pensad un poco en la siguiente pregunta, ¿qué es lo que hacemos siempre (siempre) cada vez que tenemos que introducir un cambio en nuestro sistema? …. Pues es obvio, siempre hay que leer el código ya existente y entenderlo para empezar a saber como cambiarlo. ¿Cuántas veces os ha pasado que no entendéis el código que escribisteis hace dos meses? ¿Y el de la semana pasada? A mi me ha ocurrido de no entender código del día anterior, ¡ menuda bazofia ! Si no podéis entender vuestro código, imaginaros la comprensión que tendrán de éste vuestros compañeros cuando les toque mantenerlo o añadir un cambio.
Para hacer que vuestro código se pueda modificar de forma rápida y barata, es indispensable que sea legible y comprensible (dios, que shock, ¡ no voy a poder dormir !). Este principio es muy básico y para mi es el principio de «calidad mundana» más importante que existe. Un nombre popular para este principio es el de «código expresivo». Es un principio tan esencial que aunque no seáis ágiles lo vais a necesitar. Tal vez no sea tan crucial para proyectos no ágiles, pero sí es muy importante en la fase de mantenimiento, ¿alguno ha mantenido aplicaciones ilegibles alguna vez? Seguro que sí. Quizás el ejemplo clásico de «no ágil» y necesidad de «código expresivo» sea el caso del mantenimiento de aplicaciones legacy. Cuando hay que mantener una base de código que es difícil de leer, a veces es más barato reescribirla desde cero.
Un escenario más confuso se produce con aplicaciones de «campaña», de usar y tirar. La aplicación debe estar lista para una fecha (time to market crítico), se usará durante dos o tres meses, y después se desechará. En estas aplicaciones no va a haber mantenimiento evolutivo. En un entorno ágil vamos a seguir necesitando que el código sea expresivo, ya que los cambios llegarán seguro durante la fase de construcción. En un entorno no ágil no habrá cambios durante la fase de construcción (¿seguro?), gracias a un fabuloso contrato cerrado, con lo que alguno podrá pensar que no es necesario que el código sea legible, y que la necesidad de facilidad de cambio (calidad mundana) tiende a cero. A esas mentes preclaras les deseo que entreguen el software con cero defectos, porque como empiecen a llegar bugs en la fase de explotación, que para colmo dura unas pocas semanas o meses, no sé como los vas a resolver a un ritmo suficientemente rápido como para que los de negocio queden satisfechos. Por lo tanto la legibilidad del código no sólo es importante para aceptar el cambio (ser ágil), sino también para arreglar bugs. Como todo el software tiene bugs (escondidos tal vez), todo el software, ágil o no ágil, de «campaña» o «estratégico», debe ser legible.
¿Qué técnicas nos pueden ayudar a conseguir este fin? Pues hay muchas:
- Pair programming (programación por parejas). Sí, señor, no hay nada como un compañero leyendo por encima de tu hombro lo que escribes, tratando de entenderlo y buscándole defectos, como para que tu código de repente se vuelva legible (para los demás). Afortunadamente cuando cambie el turno te podrás vengar, je, je }:-)
- Peer review (revisión de código). El pair programming no es más que revisión de código continuo, ¿por qué necesitamos otra etapa de revisión de código? Pues muy sencillo, a veces las parejas desarrollan un sentido de la identidad compartida, y un sentido de la legibilidad un poco particular. Para evitar esto es conveniente rotar las parejas, y también que otros compañeros, y deseablemente alguien un poco externo al proyecto (normalmente alguien respetado), revise el código. Por supuesto en la medida de lo posible y las restricciones de tiempo y dinero.
- Domain Model (modelos de dominio). Consiste en modelar de forma explícita conceptos usados en el dominio del problema. De esta forma cuando veamos el código reconoceremos conceptos del problema directamente en el código, lo que nos ayudará a entender éste. Este enfoque es muy popular y tuvo como repercusión la aparición del paradigma OO, en el que aparece el concepto de objeto que nos permite modelar los conceptos de nuestro problema de forma directa, algo muy costoso de hacer con un lenguaje procedural.
- Fluent API (API fluida o API sexy) o Internal DSL y también External DSL. Como ya comenté en un post anterior, los DSL nos permiten una mayor legibilidad, al modelar directamente el lenguaje de dominio de nuestro problema. Este problema puede ser la funcionalidad de negocio, pero también puede ser un problema técnico (procesamiento de XML, de BBDD, seguridad, etc). Notad que en los DSLs el énfasis está en el lenguaje y la dinámica del problema, mientras que en el modelo de dominio nos centramos en la estructura estática. Esto hace ambas que ambas técnicas sean complementarias unas de otras.
Como veis la mayoría de las técnicas de diseño ágil tienen como una de sus ventajas, si es que no su principal ventaja, el hacer el código más legible. Algunas son más potentes que otras, y también más costosas que otras. Como siempre la decisión debéis tomarla teniendo en cuenta el coste. Por ejemplo, construir un DSL, ya sea interno o externo, puede tener costes altos, sobre todo en algunas plataformas tecnológicas. Otras como TDD, Pair Programming o modelado de dominio puede tener algún beneficio adicional.
Por otro lado, para que lo penséis, ¿se os ocurre alguna técnica no ágil que facilite la legibilidad del código? ¿Wizards y generadores de código? ¿Copy&Paste? ¿DFD? ¿Alguna idea? Al fin y al cabo el paradigma no ágil también necesita que su código sea legible.
No lo he puesto en la lista anterior, pero algunos refactors pueden ayudarnos con la legibilidad de nuestro código. El problema es que esos mismos refactors en diferentes circunstancias nos hacen perderla. En general yo uso la cantidad de legibilidad que gana mi código como guía para saber si hacer el refactor merece la pena o no.
Finalmente, si usáis TDD, tenéis que tener en cuenta que la legibilidad del código no sólo se aplica al código de producción, sino también al código de test. Para mi, es incluso más importante que el código de test sea legible a que lo sea el de producción, ¿por qué? Simplemente porque el código de test define la API de vuestro sistema (o componente), son los ejemplos de uso. Si estos ejemplos (los tests) son legibles, ¡ habréis conseguido que vuestro código de test sea a la vez la documentación ! Nada de irse al javadoc, un buen vistazo a los escenarios de vuestros tests y un programador ya sabrá como manejar vuestro sistema (o componente). Por otra parte, una buena suite de pruebas, bien legible, y refactorizada adecuadamente, os proporciona además, un «fluent API» o «internal DSL». O sea, que simplemente por hacer que vuestro TDD sea legible, obtenéis un DSL y una guía del programador (con how to’s), gratis de serie con vuestro sistema con pruebas automáticas. A mi esta idea me seduce bastante.
Por supuesto si no practicáis TDD todo esto os dará igual 😛 —> ¡ A qué esperais para empezar con vuestro TDD ! ¡ Es que acaso pensáis que el TDD sirve para «probar» !
Un punto importante a tener en cuenta es la plataforma tecnológica en la que nos movemos. Por ejemplo, algunos lenguajes nos permiten hacer nuestro código más expresivo que otros. Comparad en este sentido JAVA o COBOL con Ruby, Scala, Groovy, etc. La mayoría de los lenguajes modernos van en la dirección de facilitar cada vez más que nuestro código sea legible. De hecho la evolución de los lenguajes de programación, desde el código máquina hasta el Groovy, han ido en esta dirección. Los desarrollos más recientes (o no tanto) que hacen que JAVA quede obsoleto en este sentido son:
- Intentar eliminar al máximo el su ruido sintáctico. Declaraciones de tipos, palabras reservadas, tokens, etc. oscurecen la sintaxis de nuestro código, haciéndolo menos legible. Algunas técnicas usadas por los lenguajes para mejorar esta:
- Para los lenguajes de tipado estático, la inferencia de tipos, como demuestra Scala, nos evita tener que escribir un montón de código. El compilador es inteligente y en los casos en los que el tipo de una expresión sea computable en tiempo de compilación, nos permite omitir la declaración de tipo en las variables, parámetros y valores de retornos de las funciones. Esto es una de las razonas por las que el compilador de Scala es más lento que el de JAVA.
- Los lenguajes de tipado dinámico no necesitan estas declaraciones, con lo que son de forma natural más sucintos y expresivos. Esto, junto con el auge del TDD, hace que este tipo de lenguajes se haga cada vez más popular.
- Sintaxis flexible. Los lenguajes modernos permiten una sintaxis flexible. Podemos ahorrarnos llaves, comas, puntos, puntos y comas y paréntesis en algunas circunstancias. El resultado es un código con menos ruido sintáctico y más parecido al lenguaje natural, lo que ayuda al desarrollo de DSLs internos. Groovy, Ruby o Scala son ejemplos de lenguajes que usan esta técnica.
- Paradigma mixto. La mezcla de paradigmas OO y Funcional nos permite usar el paradigma más expresivo según para que tarea. El uso de closures o bloques de código nos permite simplificar mucho nuestro código haciéndolo más legible.
- Metaprogramación. La metaprogramación nos permite construir DSLs internos de forma más sencilla. En este sentido Groovy y Ruby son bastante potentes.
Si veis que vuestro lenguaje de programación os impide que vuestro código sea lo suficientemente expresivo como para cambiarlo con suficiente rapidez, empezad a pensar en adoptar otro lenguaje.
Como os dije soy un tío simple e impresionable. ¡ A pasarlo bien !