archivo

Archivos Mensuales: junio 2011

Cuando nos planteamos una estrategia de desarrollo basada en ciclos de vida iterativos e incrementales, como tenemos por ejemplo en las metodologías ágiles, se suele plantear la entrega de evoluciones del producto en períodos constantes de tiempo. Esto es lo que en algunas metodologías se conoce con el nombre de sprint.

La cantidad de trabajo que cabe en una entrega no deja de ser una combinación de estimaciones, ya que por un lado tenemos el parámetro velocity, que constituye la cantidad de unidades de trabajo que puede asumir el equipo de proyecto en una iteración (y que no podemos considerar estable, hasta que se hayan completado algunas de ellas) y por otro la estimación de esfuerzo de las diferentes historias de usuario o casos de uso que se van a implementar en este ciclo, pudiéndose utilizar para ello diferentes técnicas, como por ejemplo Planning Poker.

Contingencias en un proyecto de desarrollo de sofware puede haber muchas, así como errores en las estimaciones, por lo que no siempre será posible implementar lo previsto en cada iteración. Ante esto, hay diferentes opciones, por un lado está el overtime y será el equipo de proyecto en consenso con el cliente el que determine si merece la pena invertir ese tiempo extra, debiéndose tener en cuenta si se ha acudido recientemente al mismo.

Kent Beck cuando describe los principios y recomendaciones de la programación extrema indica que el overtime es un recurso que se debe utilizar solo en causas muy justificadas y de manera muy espaciada y yo estoy totalmente de acuerdo con él.

Otra opción será replanificar la fecha de entrega o entregar los hitos cerrados a la fecha de finalización del ciclo. La metodología en unos casos determinará qué camino utilizar. Soy de la opinión de que si las iteraciones se producen en períodos constantes de tiempo, no se debe romper el ritmo, sin embargo si no lo son, sí que se puede abrir la puerta a la replanificación.

También habrá que tener en cuenta las demandas del proyecto y el tiempo entre iteraciones. Por ejemplo, si no ha dado tiempo de corregir una incidencia y la siguiente iteración no es hasta dentro de cinco semanas, tal vez sí que habría que plantear un cierto retraso en la entrega.

Anuncios

Se entrega una iteración de un sistema, se realiza una evolución del mismo mediante tareas de mantenimiento evolutivo o se corrigen incidencias y no paran de producirse efectos colaterales, una vez, otra vez y otra vez.

Además de dar muestras de que no se realizan pruebas de regresión de manera adecuada (o directamente no se llevan a cabo), el sistema de información da muestras de tener un diseño y/o una codificación deficiente, ya que cuando se toca un componente empiezan a salir grietas por todos lados.

Cuando esto pasa, la sensación de desconfianza entre los usuarios o los responsables tećnicos del cliente es tal que cualquier cambio en el sistema da sensación de pánico y lo peor de todo es que esas sensaciones se terminan convirtiendo en un hecho.

Cuando nos encontramos con estos problemas, hay que estudiar cuanto antes su origen. A veces serán aspectos concretos que se podrán más o menos controlar y otras veces serán tantas las minas en el código que cualquier cambio presenta un riesgo de efectos colaterales.

Una vez detectadas las causas toca tomar decisiones que dependerán de la envergadura del problema, de la disponibilidad presupuestaria y de la frecuencia con que se esperen tareas de mantenimiento sea del tipo que sea.

Una continuación de este artículo se puede leer en Testing Baires.

Si la mayoría de los sistemas de información de una organización no reutilizan código generado en el desarrollo de los mismos o en otros, nos encontramos con que estaremos reinventando la rueda una y otra vez.

La teoría dice eso, pero la realidad pone bastante difícil el proceso de reutilización, sobre todo en entornos con muchos proveedores, con un portfolio de aplicaciones alto y con diferentes estados en su ciclo de vida.

Hay que tener en cuenta que decir que no se reutiliza, no es cierto, ya que la mayoría de los desarrollos utilizan librerías de repositorios estándar, como por ejemplo las que componen determinados frameworks. Además, los proveedores también reutilizan determinadas librerías propias.

Si además se utilizan generadores de código, hay que sumar a las librerías todo el software que se ha escrito automáticamente, que si bien no es código reutilizado es fruto de un componente que se reutiliza una y otra vez (y que también va evolucionando, mejorando y adaptándose a los cambios en la tecnología y en los frameworks).

La reutilización se puede hacer principalmente a dos niveles, uno a nivel de librerías, es decir, que se puedan reutilizar desarrollos de otros proveedores, lo que requiere tener las mismas bien catalogadas y otro a nivel de delegación de funcionalidades en componentes de más alto nivel.

Este segundo nivel de reutilización, se basa en el uso de un componente que realiza una funcionalidad concreta a través de la comunicación con el mismo a través de un API. Estos componentes realizan una funcionalidad de cierta complejidad y nuestro sistema lo utilizará para que realice una determinada competencia, como por ejemplo, la gestión de usuarios, la autenticación, la gestión de documentos a firmar electrónicamente, la generación de documentos, la gestión de su almacenamiento, etc…

Además de las ventajas que supone reutilizar el componente en el desarrollo, tenemos las propias de no tener código duplicado (o funcionalidades duplicadas), es decir, el mantenimiento y testing de esos componentes está centralizado en una sola instancia, lo que además de ahorrar esfuerzo permite incrementar la estabilidad de nuestros sistemas.

Opciones hay muchas. Precisamente hoy he estado hablando de este asunto con algunos compañeros. Hemos consensuado que la solución con menos overhead y más flexible, es utilizar un documento por cada tipo de documento del proyecto, es decir, un catálogo de requisitos, un documento de casos de uso o cualquiera que se utilice en función de la metodología y del tipo de proyecto, siendo extensible por ejemplo a historias de usuario, diagramas de despliegue, etc… que vaya creciendo de manera incremental con cada evolución, pero que en contenido vaya creciendo de manera diferencial.

Es decir, el documento contiene el contenido de las iteraciones anteriores y el de la actual, pero en la parte dedicada a la actual solo se incluye lo correspondiente a esta evolución y solo recoge de las anteriores lo estrictamente necesario para ayudar a la comprensión de la misma.

Un desarrollo de un módulo, componente, clase, etc… es el resultado de una especificación realizada por parte del área usuaria o del área técnica del cliente (de manera directa o intermediando entre usuarios y proveedores).

La especificación puede ser recogida documentalmente (que puede ser más o menos formal) o no y puede contener mayor o menor detalle en función de la metodología que se utilice y del tipo de proyecto.

Entre las estrategias más comunes para recoger esas especificaciónes, tenemos los catálogos de requisitos, los casos de uso (que pueden ir acompañados de una recogida previa de requisitos o no) y las historias de usuario.

Las historias de usuario son utilizadas principalmente en desarrollos ágiles, ya que por su propia naturaleza, como veremos a continuación, resultan muy adecuadas para este tipo de metodologías.

Es importante señalar que los casos de uso constituyen una alternativa igualmente válida, pero hay que tener en cuenta que requieren un grado de formalidad mayor, necesitan un mayor esfuerzo para construirse y mantenerse y necesitan la participación de personal técnico tanto para realizar sus diagramas como las especificaciones de los mismos.

Describir en qué consisten las historias de usuarios resulta sencillo, ya que se basa en describir en pocas frases, utilizando el lenguaje del usuario, una especificación o un requisito software. Sobre esa definición su implementación se puede realizar de formas muy diferentes: fichas, post-it, etc…

Suponen, por tanto, un esquema de trabajo muy flexible, gracias a esa ausencia de formalidad. Si hay que realizar modificaciones en las mismas, basta con enmendar la historia de usuario anterior, crear una versión nueva, etc…, en cualquier caso se requiere muy poco esfuerzo para hacer las correcciones oportunas.

Existen metodologías, como la programación extrema, donde se requiere que las historias de usuario, sean escritas por ellos mismos, ¿quién mejor?.

Se requieren pocos condicionamientos a la hora de considerar como válida una historia de usuario, sin embargo para que sean efectivas deben cumplir la mayor parte de las siguientes características:

– Se debe poder estimar el esfuerzo que requiere su desarrollo de la manera más simple posible, esto debe limitar el alcance y tamaño de la historia de usuario, debiendo ser descompuesta en casos más simples en el caso de que sea necesario.

– Se deben evitar solapamientos funcionales, de manera que cada historia de usuario exprese un requerimiento concreto.

– Una historia de usuario debe poder ser discutida entre usuarios y equipo de proyecto, con el objeto de poder perfilarla, lo que puede dar lugar a diferentes versiones de la misma antes de empezar su construcción o a redefinirla o modificarla en posteriores iteraciones.

– Debe poder ser verificable, de manera que una vez implementada la funcionalidad en la aplicación, se compruebe si efectivamente lleva a cabo la especificación descrita.

En el entorno de personas con las que trabajo existen serias dudas de que la metodología de desarrollo guiado por las pruebas o Test-Driven Development, sea ágil, en el sentido de que entienden que si se aplica de manera ortodoxa o si bien las pruebas unitarias se desarrollan después, se pierde capacidad de trabajo efectivo por parte del equipo de proyecto.

Nunca he participado en un proyecto que siga esta metodología y en los desarrollos en los que he trabajado la importancia que se le da a las pruebas unitarias es muy escasa, por lo que no puedo aportar mi experiencia para opinar sobre las bondades o no de esta metodología, de manera que me tendré que centrar en lo que he leído sobre ella.

No obstante, hay que tener en cuenta una cosa. Agilidad no es lo mismo que desarrollar más rápido o por lo menos no es su objetivo principal, ya que si bien es cierto que la aplicación de metodologías ágiles, eliminan muchos aspectos que son prescindibles en un proyecto de desarrollo de software y por lo tanto pueden optimizar tiempos de desarrollo, el objetivo último de las mismas es conseguir productos software de calidad, que proporcionen satisfacción al cliente, dentro de unos plazos y presupuestos definidos (siempre y cuando, ambos sean realistas y acordes a la naturaleza del proyecto y al nivel de calidad que se espera del producto software), es decir, que no se produzcan los factores que derivan en la crisis del software.

El desarrollo guiado por las pruebas, fue introducido (o reintroducido) por Kent Beck, creador de la metodología de programación extrema y en cierto modo la segunda es una consecuencia natural de la primera, si bien ambas pueden utilizarse sin problemas por separado.

La metodología TDD a grandes rasgos es fácil de exponer:

– Se construyen las pruebas unitarias (o se puede extender si se desea el concepto a otros tipos de pruebas de más alto nivel, siempre y cuando sea automatizable su ejecución).

Como estos casos de prueba están construidos antes desarrollarse el componente software sobre el que se va a trabajar, van a fallar a la primera. De hecho se considera necesario verificar que fallan porque si no es así, querría decir que el test está más construido (¿por qué no?) o que el componente software ya está desarrollado (en proyectos donde se trabaja con sistemas de mediana o gran envergadura y con un número de desarrolladores alto, puede pasar que el componente o módulo ya se haya implementado previamente, bien porque le ha hecho falta a un compañero para otra funcionalidad o bien porque no se ha verificado que efectivamente ese componente ya estaba desarrollado).

Para la construcción de los casos de prueba, los desarrolladores deben basarse en las especificaciones indicadas en los casos de uso o en las historias de usuario. Ahí radica la importancia que tiene la construcción de las pruebas unitarias antes del desarrollo del componente, ya que implica un mayor enfoque en los requisitos, una mayor necesidad de comprender la funcionalidad y comportamiento deseado, es decir, implica comprender el problema, conocer los resultados esperados, antes de empezar a desarrollarlo.

La ventaja de desarrollar las pruebas antes de implementar los componentes es la indicada en el párrafo anterior, es decir, enfrentarse y comprender el problema antes de empezar a resolverlo.

Si las pruebas unitarias se construyen después, no estaríamos hablando de TDD, eso es importante, por lo que lo repito, si las pruebas unitarias se construyen después se trata de otra estrategia de desarrollo de software, no de desarrollo guiado por las pruebas.

Pese a que como he comentado, no se ha fomentado en los proyectos en los que he participado la construcción de pruebas unitarias, soy de la opinión de que es favorable tener una buena cobertura de código, por lo que la existencia de pruebas unitarias, antes o después de implementar los componentes, permite detectar de manera rápida la existencia de efectos colaterales o desarrollos erróneos de métodos o clases.

¿Por qué no lo hemos fomentado? Es una suma de varios factores, por un lado pese a que entendemos los beneficios que puede proporcionar, no tenemos del todo claro su retorno de la inversión, pese a que es objetivo que la deuda técnica se reduce en proporción a la cobertura, por otro lado, los proveedores con los que hemos trabajado tampoco utilizan esta práctica en sus desarrollos. Por tanto y en resumen, la falta de experiencia nuestra y de los proveedores hace que nos más dar el paso y prioricemos otros aspectos en el proceso de desarrollo y en la calidad del diseño y de la codificación.

– A continuación se construye el componente software que debe verificar las reglas de funcionamiento definidas en las pruebas.

Una vez desarrollado, se pasa el test y si funciona adecuadamente, se pasa de nuevo todo el juego de pruebas, si hay errores, se vuelve a revisar el componente, si todo ha ido bien, se pasa a la siguiente fase.

– Una vez que se tiene un software que verifica las pruebas implementadas, se procede a su refactorización.

El objetivo de la utilización de esta técnica es evidente, desarrollar software con el menor número de fallos posible, reduciendo además los errores debidos a efectos colaterales en el desarrollo, respetando también la calidad de diseño y de codificación para favorecer la comprensión y capacidad de mantenimiento del software.

Se sabe que cuanto más tarde se detecten los errores más esfuerzo se requiere para solucionarlo, esta técnica pretende disminuir el riesgo por la presencia de errores de ese tipo, además de facilitar un código donde sea mucho más fácil trabajar, tanto para corregir esas incidencias como para contruir nuevas funcionalidades o modificar, si fuera necesario (el software es de naturaleza adaptativa), las ya implementadas.

Espero poder probar más pronto que tarde esta metodología en alguno de los proyectos en los que participo y poder verificar personalmente las ventajas que tiene la utilización de esta estrategia de desarrollo.

Interesante la siguiente reflexión de Kent Beck (traducción libre): “Utiliza la opción más simple que pueda funcionar”.

Mi experiencia está en consonancia con esa reflexión. Empieza por desarrollar la solución más simple que funcione (consensuada con el usuario) para implementar una determinada funcionalidad de un sistema. Siempre habrá tiempo para añadir complejidad.

Cuanto maś simple y más flexible sea la aplicación que se desarrolla (siempre y cuando, insisto, satisfaga las necesidades del usuario), más posibilidades existirá de que sea bien acogida por los mismos y menos esfuerzo extra se tendrá que invertir en desarrollar funcionalidades con una complejidad adicional que lo más probable es que no sea necesaria o lo que es peor, complique la utilización del sistema por parte de los usuarios.