Archive for the 'Desarrollo Agil' Category

Las Tareas Son del Equipo

Monday, April 17th, 2017

Es común en ek planeamiento de una iteración, elegir las tareas del backlog, por prioridad dada por los “stakeholders”. Una vez elegidas las tareas, se van ejecutando, generalmente teniendo subtareas. Hay tareas de programación, de diseño de interfaz, de deployment, etc…

He visto dos formas de encarar la ejecución las tareas: o se las asigna UNA persona, o se la ejecutan entre varios, tal vez UN miembro del equipo por SUBTAREA. Y luego de ver mucho éxito en la segunda forma, pienso que es lo más adecuado (siempre dependiendo del context y las fuerzas del proyecto, el estado del equipo, etc…). Incluso participé de un proyecto donde no había más de DOS tareas abiertas, de tal forma que los miembros del equipo colaboraban para terminar cualquiera de esas dos tareas (en este caso en particular, las dos tareas en general eran de programación o de diseño).

El que varios trabajen en una tarea, y no se pase a la siguiente hasta tenerla terminada, veo que favorece el “ownership” colectivo, donde no se forman silos de programación, donde cada miembro del equipo termina colaborando viendo el proyecto en general, y no solamente la parte que a él/ella le toca. Permite también la adopción de programación de pares, la difusión del conocimiento, y un “code review” permanente, adoptando agilidad, en lugar de etapas de codificación y “code review” por separado.

En un equipo maduro, debería adoptarse esta forma de encarar las tareas del “sprint” (siempre y cuando, respetando el contexto y las fuerzas del proyecto en particular). Por lo menos, he visto mejora en el resultado cuando se adopta esa forma de trabajo.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com

 

Alineando las Sillas en la Cubierta del Titanic

Saturday, April 15th, 2017

Desde la aparición de la agilidad aplicada a equipos de desarrollo de software, hemos estado avanzando en nuestra profesión. En mi experiencia, veo que la calidad de lo producido ha mejorado, en general. Por lo menos, cuando veo los proyectos en los que he participado en este siglo, comparados con anteriores, noto esa mejora general. Y también en la satisfacción personal de cada miembro del equipo, en el manejo de los tiempos, los esfuerzos, y demás. Y veo lo mismo en decenas de proyectos en los que no participé pero he visto crecer de cerca.

Pero a veces, veo tomar decisiones que no son las más ágiles, o no están alineadas con lo que el contexto del proyecto pide. Una tendencia, no muy frecuente por suerte, es poner énfasis en alguna métrica o herramienta en lugar de lo que es importante para el proyecto.

Por ejemplo, he visto “sprints”, iteraciones, dedicadas a “incrementar el porcentaje de code coverage a 80%”. Por más que repaso las circunstancias de los casos que conozco, no logro ver que sea realmente lo importante. Es más, tomar ese tipo de trabajo es perder una oportunidad de mejora real en el equipo. El trabajar “a posteriori” en el incremento del code coverage, es poner el carro delante del caballo. Lo que buscamos en un sistema que estamos armando es que funcione como esperamos. Y eso se logra mejor si escribimos los tests de la conducta esperada, de cada caso de uso que queremos cumplir (uso “caso de uso” en un sentido amplio, desde la conducta esperada en una simple clase, hasta el resultado esperado luego de una serie de pasos de negocio). Cuando uno sigue ese “approach”, el “code coverage” nos da automáticamente cumplido como métrica, casi seguro mayor que el 80%, o que el 90%. Eso es lo que tiene que ser la medición del “code coverage”: una métrica que de evidencia de que estamos siguiendo el buen camino, antes que un objetivo en sí mismo. Trabajar a “posteriori” para incrementar esta métrica NO ASEGURA que estemos escribiendo un buen sistema. Ver ejemplo mencionado en el post El Programador Profesional (1) , donde aún con un gran “code coverage”, lo entregado se rompía luego de dos clicks en el browser, en cuatro continentes.

Otra versión parecida de errar en el blanco, es adoptar prácticas porque parecen las “mejores prácticas” o poner procesos obligatorios en el desarrollo, como obligación de manejarse con “branch por feature”, y “code review”, para mantener “la calidad del código”. Las veces que vi que se llegara a esa situación, son pocas, por suerte. Pero igual de vez en cuando aparecen, y veo que es de nuevo atacar un problema con procesos y herramientas, adoptar prácticas que tienen sentido en otro contexto, sin aprovechar atacar las causas raíces y obtener una mejora en el equipo.

Por ejemplo, “branch por feature”, podría adoptarse, pero en casos particulares. Si se necesita adoptar en un caso, es seguramente porque no estamos trabajando en forma incremental, simplemente mejorando el “branch” de desarrollo, trabajando juntos, esforzándonos por no romper algo (por ejemplo, usando TDD (Test-Driven Development) me facilita mucho el saber automáticamente si la conducta esperada no se cumple). Y dificulta la integración, tenemos que trabajar más casi siempre al final de la iteración para tener algo entregable. Y vi que promueve el trabajo en silos, donde cada cual trabajar en un “branch”, en lugar de trabajar todos juntos en una tarea. Cuando la adopción de “branch por feature” se hace obligatoria, es como que se naturalizan esos “bad smell” de equipo, y no podemos activamente trabajar en la mejora. He visto DECENAS de proyectos de equipos ágiles donde no se usa “branch por feature” y todo funciona de forma armoniosa. Esto me lleva a pensar que “branch por feature” es algo más útil cuando el proyecto es de código abierto. En un equipo ágil, donde hay una reunión diaria, donde hay comunicación (aun remota), donde el sistema se va armando de tal forma de saber cuando algo no funciona o cuando introducimos un bug sin quererlo, donde el sistema no se ha dejado llevar por la complejidad accidental y puede entonces ser desarrollado incrementalmente, cuando veo que se da este contexto, veo que no es tan necesario el “branch por feature”.

En cuanto a “pull request con code review” obligatorio, de nuevo: veo que es un síntoma de no atacar otra causa raíz. Por ejemplo, no lo vi tan necesario cuando hay trabajo de a pares. O cuando hay confianza en los miembros del equipo, y se sabe que cualquier cosa que uno agregue puede ser revisada en cualquier momento, porque estamos armando un sistema evolutivo, sin complejidad accidental, que admite cambios como todo lo ágil promueve. Cualquier cosa que parezca mejorable, se puede mejorar en cualquier momento. Por supuesto, cada uno debe hacer el mejor esfuerzo desde el principio.

En definitiva: adoptar herramientas y procesos de esta forma, es como alinear las sillas en la cubierta del Titanic: estamos haciendo algo que parece bueno, en vez de hacer lo correcto, lo que la situación amerita. Y nos naturaliza los problemas, sin atacar las causas raíces.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com

 

El Programador Profesional, The Clean Coder (1)

Monday, April 3rd, 2017

Cada tanto vuelvo a leer algunos libros clásicos de programación, como The Pragmatic Programmer, Programming Pearls, The C Programming Language, Code Complete, Clean Code… En estos días me reencuentro con el excelente The Clean Coder del bueno de @unclebobmartin (ver el blog http://blog.cleancoder.com/ y el sitio asociado https://cleancoders.com/). Me gustó darme cuenta que en estos años estuve mejorando en varios de los puntos que menciona para un programador profesional (en otros todavía estoy en deuda). Quisiera comentar en esta nueva serie de posts las cualidades y actitudes que menciona como importantes.

Primero, Martin se refiere a programadores PROFESIONALES: todo lo que expone es lo que él ve como deseable para un programador que quiera llamarse profesional, no solamente porque trabaja programando sino por el respeto y la posición que alcanza entre sus colegas y clientes.

La primera gran regla que da se basa en el juramento hipocrático:

First, Do Not Harm

Es decir, lo primero, al programar, es no hacer daño. Esto se traduce en NO INTRODUCIR ERRORES (bugs que le dicen). Cualquier software exitoso necesita ser modificado en el tiempo, y es signo de profesionalismo en un programador el no introducir errores en las modificaciones que haga. Uno podría decir: la programación es una actividad muy complicada, tanto que como seres humanos no podemos evitar introducir bugs. Pero Martin retruca: un médico también puede equivocarse, pero el juramento hipocrático lo lleva a esforzarse, a hacer todo lo posible que esté a su alcance y bajo su entendimiento para evitar lastimar a sus pacientes. Eso mismo tenemos que hacer nosotros como programadores: esforzarnos por no introducir bugs cuando corregimos alguno o agregamos nueva funcionalidad. Martin da algunos ejemplos de su propia historia profesional sobre las consecuencias nefastas que puede provocar el agregar bugs sin darnos cuenta.

Pienso que estamos todos de acuerdo en que necesitamos hacer ese esfuerzo, pero ¿cómo lograrlo? Una estrategia, desaconsejable, es hacer que los clientes finales sean los que reporten bugs. ¡Qué bueno! Entreguemos algo rápido, así nos destacamos en nuestro cumplimiento de los plazos y luego, reparemos lo que ellos detectaron, también rápido. Pero no: no es un camino a seguir en los tiempos que corren. Otra aproximación: hacer que el equipo de QA (Quality Assurance), se encargue de detectar los problemas. De nuevo, así podemos entregar algo rápido, avanzar, total, ya alguien va a reportar los errores. Tampoco este es el camino esperado para un programador profesional: malgastar el tiempo de otros cuando uno debería directamente entregar código que funciona, no es profesional, es chapucero.

Entonces ¿cómo hacemos que lo que codificamos no tenga errores? Una es aplicar las mejores prácticas, todos los patrones del mundo, y confiar en nuestra capacidad de escribir bien, de “compilar” los resultados de una función que escribimos en nuestra cabeza, sin nunca probarla. Esta estrategia sigue siendo tan común que me asombra. Aún cuando seamos los “grandes” programadores que toman todas sus precauciones para no introducir errores, los errores siguen apareciendo. ¿Qué propone Martin (y es agua para mi molino, si ya conocen mi postura sobre el tema)? Pues bien:

How can you know your code works? That’s easy. Test it. Test it again. Test it up. Test it down. Test it seven ways to Sunday!

Siempre probar el código. Siempre. ¿Sugiere entonces Martin tener un 100% de cubrimiento de código? No, no lo sugiere, lo DEMANDA. Cada línea de producción que uno escriba debe ser puesta a prueba.

Every single line of code that you write should be tested. Period.

Parece no realista. Pero no lo es. Cada línea que uno escribe es para ser ejecutada alguna vez, entonces, DEBEMOS asegurarnos que funciona como esperamos. No basta con leer el código y “compilar” el resultado en nuestros sesos, decir simplemente: “esto hace lo que se espera”. Martin (y permítanme que yo me sume) DEMANDA que sea probada con un test.

Una estrategia que veo seguir, entonces, es escribir las pruebas DESPUES de escribir el código. He escuchado cosas como: “con escribir código DESPUES, me aseguro que funcione, y el código que YA escribí tiene buena calidad”. Bueno, pocas veces he visto que se cumpliera. Lo que sucede muchas veces es que el código a probar a posteriori, es un código enrevesado, o al menos, difícil de ser probado. Es por eso que Martin escribe:

The solution to that is to design your code to be easy to test. And the best way to do that is to write your tests first, before you write the code that passes them.

¡¡SI!! Es tan simple como eso: escribir las pruebas ANTES del código. Esto nos lleva a TDD (Test-Driven Development), flujo de trabajo del que Martin es un “big supporter” (y yo también).

Por supuesto que esto no quita que probemos nuestro software con pruebas automáticas de integración, o con pruebas manuales de QA. Siempre probar, probar, probar… Hacer todo el esfuerzo como profesionales para quedar seguros de que nuestro software funciona como se espera. Pero el paso inicial es usar TDD: me temo que no encuentro todavía una disciplina o flujo de trabajo que reemplace a TDD para conseguir el “First, Do Not Harm”.

Alguno podrá decir que practicar TDD lleva “más tiempo”. Pues yo todavía tengo que ver eso en el contexto de un proyecto con lógica no trivial: el aparente más tiempo que involucra escribir los tests antes del código, y el trabajo de refactor, que permite hacer que el código evolucione, en vez de “escribirlo bien en la primera vez”, es menor que el tiempo tradicional si contamos el tiempo de depurar, los días y horas gastados en identificar las causas de errores, y el tiempo que se tarda en corregirlos.

Puedo dar como evidencia experiencia propia, lamentablemente sin poder mostrar código concreto por haber sidos proyectos privados. El cliente final fue el mismo, el dominio fue parecido en complejidad y alcance, y los equipos de programadores de muy buen y similar nivel.

Proyecto A: escrito sin TDD, con pruebas de QA, pruebas manuales, pruebas de usuarios en ambiente de test, y luego en stage, antes de llegar a producción.

Proyecto B: escrito con TDD, con tests automáticos de integración, pruebas de QA, pruebas manuales, pruebas de usuarios en ambiente de test y stage, antes de llegar a producción.

El segundo proyecto, escrito con TDD, se implementó evolutivamente, con TRES implementaciones esencialmente diferentes, en el TIEMPO que se había estimado hacerlo sin TDD: Luego, fue más fácil en este proyecto introducir pruebas automáticas de integración, incluso directamente en el código. Y cada vez que modificábamos código o agregamos algo nuevo, al quedar las pruebas de código en “verde” luego las pruebas de integración y manuales de QA eran “un trámite”: muchas muchas veces pasaban, al habernos asegurado el cumplimiento de las pruebas escritas con TDD.

El primer proyecto también tenia pruebas de código, con un cubrimiento de código mayor de ochenta por ciento, escritas DESPUES de escribir el código. Pero aún cuando esas pruebas pasaran a “verde”, eso NO ASEGURABA el buen funcionamiento del sistema final. Y había que dar muchas vueltas para probar un código que no había sido escrito como para ser probado fácilmente. Muchas veces las pruebas de QA fallaban. Y cada vez que se introducía un cambio o se arreglaba un bug conocido, aparecía algún otro problema detectado en QA. Aún dos semanas antes de la entrega final, se introdujo un problema que sólo fue detectado en producción: dos clicks en el browser, y el sistema explotaba en cuatro continentes.

Del segundo proyecto, no escuché nada por más de un año, luego de su entrega. Pregunto un día ¿lo están usando? Sí, lo estaba usando. Ah ¿lo están usando para probarlo cada tanto? No, lo usaban cada día YA EN producción. Se le había entregado el mantenimiento del sistema a otro equipo, lejano. Entonces pregunto; ¿cómo? ¿lo están usando desde hace meses? ¿cómo es que no nos llegan reportes de bugs? La respuesta: porque NO LOS ENCONTRARON (o por lo menos hasta ese momento). Todo había sido escrito ordenadamente, aplicando “baby steps”, programación evolutiva, escribiendo explícitamente los casos de uso esperados, tanto de funcionamiento correcto como de conducta ante entradas inválidas. Estaba armado de tal forma que, al tener ese tipo de pruebas, y código que fuera fácilmente probable, las modificaciones se podían implementar y enterarse inmediatamente si algo anterior se rompía o no. Porque eso es también nos da TDD: el facilitar la profesión de los que vengan después a modificar el software que estamos construyendo.

Por supuesto que TDD solo no asegura la AUSENCIA TOTAL de bugs. Pero su aplicación a conciencia, junto con refactor casi continuo, la aplicación CONCIENZUDA de patrones y otros conceptos (no solamente por que sí), el diseño evolutivo (que permite ir construyendo algo que no se aparta de la simplicidad sino hasta la llegada de un caso de uso que haga necesario agregar algo), todo eso, que llamo “TDD y cabeza”, es lo que permitió el resultado exitoso del proyecto B.

Como programadores que aspiramos a ser profesionales tenemos que poner todo nuestro esfuerzo en este tema, en escribir código sin introducir errores. Y para eso, veo como esencial el escribir las pruebas antes del código, y mantener el sistema en un estado simple, claro, entendible y mantenible. Más sobre el tema en próximo post.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

El valor de TDD (3)

Tuesday, May 26th, 2015

Anterior Post

En el primer post de esta serie, ya mencioné el tema simplicidad, y di algún ejemplo. Es un tema fundamental para entender TDD (Test-Driven Development), pero parece ser que es uno de los menos entendidos. Así que me permito insistir hoy en el tema.

Una de las cosas hacia las que empuje TDD es a la simplicidad, pero siempre y cuando pensemos alineados con el flujo de trabajo de TDD. Veamos la respuesta a la pregunta:

¿Por qué TDD empuja a la simplicidad?

Por dos razones: como se escriben los tests primero que el código (y muchas veces, se escribe un solo test, y hasta que no se termina de trabajar en ese test, no se pasa a otro), nos conviene comenzar con tests sencillos de expresar su intención, no comenzar por los tests más complicados. Como ejemplo, pueden ver los commits de mi proyecto SparkSharp (ver posts). Ahí tengo que implementar un concepto llamado Dataset, sobre el que aplicar map y reduce. ¿Acaso escribí los tests más complicados primero? No, siguiendo la premisa de ir por pasos de bebé (“baby steps”), fui planteando los casos más sencillos primero. Por ejemplo, escribí primero tests para Datasets que se alimentan de items enumerables en memoria, antes de pasar a Datasets que se alimentan de archivos locales. Y luego, en algún momento futuro, escribiré y haré pasar a verde tests sobre Datasets que se alimenten de datos remotos. Pero como en la vida, el más largo camino comienza con un solo paso. Y otro paso y otro paso y así.

¿Acaso en ese proyecto implementé directamente la API del proyecto original, Apache Spark? No, la idea es ir planteando casos de uso pequeños de la API, dado un estado, llamar a un método, y escribir lo que se espera obtener como respuesta o como cambio de estado. Por ejemplo, en el proyecto original los Datasets se crean con métodos factoría de un Spark Context. Yo no he tenido necesidad de eso en los tests simples que he escrito. Cuando llegue el caso de necesitar eso, por alguna circunstancia concreta, lo haré, haciendo refactor y rediseño. Pero recuernde: no hay que cruzar el puente ANTES de llegar al puente.

Y la segunda razón: TDD nos lleva a resolver el pasaje del test a verde DE LA FORMA MAS SENCILLA. Eso lo tenemos que poner nosotros, la voluntad de implementar código de producción, APENAS PARA que pase el test. Si sentimos que al código de producción le falta algo (por ejemplo “debería controlar que este parámetro no sea nulo”), entonces ESCRIBIMOS el test correspondiente, y luego agregamos la funcionalidad que falta. Si no hay ningún test que pida a gritos la implementación de algo, ese algo NO SE IMPLEMENTA. Primero el test, luego la implementación. De alguna forma, esto lleva a que casi cada línea de código de producción PUEDA SER TRAZADO su origen a algún test. Línea de producción que no nace de un test, es sospechosa.

Corolario: no agrego librerías y dependencias que no necesito para resolver un test. No digo “no agregar Angular”, o “no agregar Hibernate” nunca. Digo: lo agrego cuando realmente los casos de uso planteados (los casos de la API, lo que espero que haga), realmente justifican el agregado de la dependencia. Y van a ver, que gracias a TDD, el agregado de la librería en general no cuesta mucho. Una de las consecuencias del uso de TDD y simplicidad y lo ágil en general, es que el diferir las decisiones no tiene un gran costo, y hasta mejora nuestro diseño porque permite que lo que se agrega se agregue por necesidad real, y no por “cargo cult programming” o porque sea “la mejor práctica”. Recuerden, no hay “mejores prácticas”, sino “mejores prácticas en contexto”. Además, el diferir la decisión tal vez ayudar a decidir, luego de ver mejor los casos de uso, que en vez de Angular necesitamos React, o que en vez de un ORM necesitamos un MicroORM, y así. Permite que nuestro diseño no vaya anclado a una decisión temprana de tecnología.

Veamos ahora un “pecado” que he observado cuando se intenta comenzar con TDD: pensar demasiado por adelantado. Recuerden, los primeros tests tienen que ser sencillos. Por ejemplo, en mi implementación de una especie de OLAP en memoria, MemOlap, ¿acaso pensé todas las estructuras internas de antemano? Para nada. A medida que implementé casos de uso, fui implementado mejor lo que quería hacer, y refactorizando. El software crece así como un organismo, guiado por nuestras ideas y por el ambiente que lo rodea, los tests que vamos incorporando de a poco. Justamente, MemStore fue la semilla para un proyecto real no público, que avanzó mucho más, y hoy está en producción. Y cuando en ese avance se plantearon nuevos casos de uso, o se vió que había que refactorizar algo, se lo hizo, sin mayor esfuerzo. Doy un caso: cuando se llegó a probar el rendimiento con cientos de millones de registros, se vió que algunas operaciones eran lentas. Ahí se hizo refactor, se implementó el recorrido de los items de una forma más eficientes, sin apelar a crear y destruir objetos a cada momento, y gracias a los tests en verde, todo siguió andando. Hasta hubo refactor quirúrgico: llamo así a los que cambian mucho de la implementación. Y al comienzo de tal tipo de refactor pueden quedar muchos tests en rojo. Pero volviendo todo a verde, al final queda un sistema tan estable como al principio.

En gran parte, el bajo esfuerzo de cambio resultó de la simplicidad adoptada hasta ese momento, y la ayuda de los tests ya escritos, que permitieron rápidamente, ante un refactor (cambio interno de implementación) o un rediseño (cambio de la API porque hay nuevos casos de uso a contemplar), darse cuenta en el momento qué se había roto y qué no.

Entonces, no comenzar a pensar ya en el primer test en repositorios, ORM, y contextos de ejecución. Hay que relajarse y volver a plantear el desarrollo del software como juego. Imaginemos que cuanto más tests en verde tengamos, más avanzamos en el juego.

Hacer de cada test, un test sencillo.

Hacer de cada implementación, la más simple para que pase el test.

No agregar algo a la implementación, sin respaldarlo con test.

Y como siempre, la neurona atenta, vermú con papas fritas y good show! 😉

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

El valor de TDD (2)

Friday, January 30th, 2015

Anterior Post
Próximo Post

En el anterior post comentaba que una de las ventajas de TDD (Test-Driven Development) es que facilita cambiar nuestro código, ya sea para mejorarlo o para adaptarlo a nuevos casos de uso. Quería comentar hoy algunas experiencias (me temo que sobre proyectos no públicos, así que no tengo un repo para mostrar) donde descubrí ese valor de TDD.

Por ejemplo, en un proyecto ya terminado y entregado, el cliente pide despues de un tiempo un cambio: un nuevo caso de uso. El proyecto contenía un “parser” de expresiones de consulta, tipo SQL, sobre un modelo de dominio en memoria, y en en lenguaje consulta que se había implementado había filtros como

where Year = 2013

Donde se admiten otros operadores que el igual, pero donde la expresión de la derecha era siempre una constante. Para el nuevo caso de uso el cliente pedía extender este lenguaje de consulta (tanto el “parser” como la ejecución de la consulta, para tener expresiones como

where Year = @today – 10

donde @today es el año actual, donde la expresión de arriba es “dame los datos de hace diez años”. Bien, como tanto el “parser” como el ejecutor de comandos estuvieron hechos con TDD, estaba todo preparado para probarlos, y se escribieron los nuevos tests a pasar. Al principio no compilaba (en C#), y luego comenzó a compilar, pero dando las pruebas nuevas en rojo. Se agregó el código necesario para que pasen a verde, se corrieron todas las pruebas para ver si había algún “side effect”, efecto colateral, que rompiera algo, pero todo anduvo bien. Se entregó el resultado al cliente, lo probó, y hasta el día de hoy no ha reportado un solo “bug” sobre el tema. Lo está usando sin problema. ¿Cuánto se tardó en modificar el código? Pues:

UNA HORA

Tardé otra hora explicando lo que había hecho (en inglés, que no es un tema que domino mucho ;-).

Pero no es solamente el tiempo, sino que la forma de trabajo (TDD antes, TDD ahora), permitió:

– Apenas gastar tiempo mínimo en depuración
– No romper nada de lo anterior (porque TODO LO IMPORTANTE que anda de lo ANTERIOR, se implementó siguiendo el flujo de trabajo de TDD, no con simple “test-after”; entonces si los tests de ahora dan en verde, es ALTAMENTE PROBLABLE que el sistema en conjunto siga funcionando)
– Dejar todo preparado para que otro equipo pueda hacer lo mismo (adaptar el sistema a un nuevo caso de uso) más adelante.

Pongamos un ejemplo negativo. En un proyecto, había una lógica que, dado un usuario y un tema, devolvía una lista de documentos de ese tema y accesibles por el usuario (no era exactamente así, pero basta en este post esa descripción para el ejemplo). Obtener esa lista no era simple: había una gran cantidad de lógica, no siempre directa (un documento podría heredar de otro, y heredar entonces su accesibilidad o innaccesibilidad por un usuario; había distintas accesibilidades (para ver, para modificar, etc)…). Y además, en la implementación del anterior equipo de desarrollo, alguien había decidido algo como “si lo ponemos en un Stored Procedure va a andar más rápido” (sin hacer ninguna prueba que corrobere esa afirmación). Conclusión: todo estaba en un stored procedure de decenas de líneas.

Llegó el momento de, ante un caso de uso nuevo, hubo que refactorizar esa lógica: en el caso de uso nuevo, en algunas ocasiones, obtener esa lista llevaba un par de decenas de minutos (jeje… si, la “magia” de poner todo en el stored procedure ;-). Eso en sí no es problema: no siempre la primera implementación es “la mejor” (en parte, porque no hay “la mejor”, sino que quizás hay “la mejor para los actuales casos de uso, pero tal vez hay que cambiarla cuando aparezcan nuevos casos”), y la original implementación que ya estaba en producción había cumplido con su cometido, y el sistema estaba funcionando sin mayor problema. Pero al hacerlo sin TDD, una consecuencia es: FUE DIFICIL agregar un caso de uso, y reimplementar la lógica, porque no había forma de saber, por pruebas automáticas, qué casos eran cumplidos por la nueva implementación (un modelo en memoria fue la nueva implementación, mucho más rápido), y cuáles no. Conclusión: hubo que implementar pruebas con casos de uso más frecuentes, y como no estaba claro qué tenía que devolver en cada caso, la prueba comparaba el resultado DE LA NUEVA implementación, con el resultado que daba la VIEJA implementación. No es la mejor solución.

¿Lección aprendida? Hacer TDD, paga, nos da beneficios, en especial al cliente final, y a todo equipo que venga a mantener el sistema. Sin TDD: horas o días para reproducir y arreglar un bug, interminables sesiones de depuración, costo mayor para el cliente final, desgaste y poco avance diario. Con TDD: todos felices, y dedicando el tiempo a agregar valor y no a remar en un río de mermelada 😉

Tengo más casos para comentar, pero espero que lo de arriba sirva para aportar agua para el molino de TDD.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Proyecto Liqueed (1)

Thursday, January 22nd, 2015

Siguiente Post

Hoy quería presentar en un post corto, al proyecto Liqueed, pueden ver el repo en:

https://github.com/liquid-co-ops/liqueed

desarrollado como aplicación web sobre una idea del bueno de @acyment, leer su post (en inglés):

http://blog.agilar.org/index.php/2014/04/30/leancoops-first-draft/

La idea básica de la aplicación es ayudar a los equipos que estén desarrollando algo a la manera que sugiere Cyment en el tema del reparto de acciones sobre el proyecto. Desde hace unos meses, la aplicación (el código, el backlog, las ideas, la implementación, el hosting y demás) se viene armando en un equipo que también quiere ser “líquido” (con entrada y salida de gente, pero aún estamos cortos con el tema de las votaciones propias, en casa de herrero 🙂

En esta serie de posts que hoy inicio, quisiera comentar temas técnicos interesantes que plantea el proyecto. Por hoy, comento que:

– Es una aplicación Node.js, expuesta a la web usando Express. Con lo que el lenguaje de programación es JavaScript.

– Además de algunas páginas internas de administración con MVC, tiene una API expuesta, que intercambia JSON.

‘- Hay una aplicación Single Page que es la que tendría que usar el usuario final, para ver los proyectos, las votaciones, los repartos de acciones y para ingresar las nuevas votaciones

– La mayor parte del código fue escrito usando el flujo de trabajo de TDD (Test-Driven Development). Incluso, el primer código implementó el modelo en memoria, permitiendo avanzar más fácil sobre la implementación de casos de uso, sin tener que detenerse tanto en la persistencia (hasta el cliente SPA puede ejecutarse sin necesidad de tener un servidor andando)

– Hace ya unos meses, agregamos persistencia con MongoDB. Podríamos usar otra base, relacional quizás. No estamos aprovechando las facilidades de manejar documentos de MongoDB todavía. Solamente la elegimos por su ubicuidad en las plataformas de desarrollo y en los distintos servicios de hosting de Node.

– En algún momento agregamos Istambul para el cubrimiento de código.

– Cuando los tests de TDD por código puro comenzaron a ser largos, se agregó un DSL (Domain Specific Language) simple que permite escribir archivos de texto con los pasos a seguir en el test funcional

– Comenzamos a agregar tests de la SPA (Single Page Application) usando Zombie

Y habría mucho más para comentar, espero que con más detalle en los próximos posts.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Arquitectura y Desarrollo Agil

Saturday, November 8th, 2014

Hace unos días apareció un hilo de discusión interesante en la lista foro-agiles sobre si la arquitectura emerge. Hubo varios aportes y distintas posturas, los invito a leer por ahí todo lo que se escribió y expuso.

Quisiera esta en mi blog, escribir algo sobre el tema. Primero, algo de contexto. Voy a escribir sobre:

– Proyecto profesional o personal
– Ejecutado por un equipo ágil o por uno mismo
– Proyecto donde el principal entregable es software

Al poner lo ágil en el contexto, estoy asumiendo que vamos a abrazar el cambio, uno de los pilares de lo ágil. Se ha visto que en el desarrollo de software es difícil basarse en algo fijo, sino que lo que se va desarrollando se va descubriendo, desplegando, y mejorando a medida que se va construyendo. Es también difícil tener todo definido de antemano. Otro de los puntos que voy a poner en claro, es que lo que necesitamos es basarnos en los casos de uso, por lo menos en gran parte. Escribí sobre el tema en TDD y Casos de Uso. Recuerdo de ahí alguna cita de @unclebobmartin:

The center of your application is not the database. Nor is it one or more of the frameworks you may be using. The center of your application are the use cases of your application.

It makes me crazy when I hear a software developer describe his system as a “Tomcat system using Spring and Hibernate using Oracle”. The very wording puts the frameworks and the database at the center.

What do you think the architecture of that system would look like? Do you think you’d find the use cases at the center of the design? Or would you find the source code arranged to fit nicely into the pattern of the frameworks? Would you find business objects that looked suspiciously like database rows? Would the schema and the frameworks pollute everything?

Here’s what an application should look like. The use cases should be the highest level and most visible architectural entities. The use cases are at the center. Always! Databases and frameworks are details! You don’t have to decide upon them up front. You can push them off until later, once you’ve got all the use cases and business rules figured out, written, and tested.

Amen!!! Si no tenemos en claro los casos de uso, no podemos tomar grandes decisiones, y menos de arquitectura. Sé que parece que en muchos sistemas los casos de uso parecen estar dados desde el principio, pero en mi experiencia profesional, no veo que haya sido así. Lo que uno presupone, por ejemplo, en un Sprint 0, no termina siendo el caso a las pocas semanas de desarrollo. Es por eso que la arquitectura no es algo que se pueda plantear convenientemente desde el principio, sino que es algo que tenemos que ir desplegando a medida que tengamos más masticados, digeridos, analizados, incorporados los casos de uso que vamos implementando.

“Arquitectura” es un término amplio. Veamos de poner algún ejemplo, para poner un contexto más definido. Supongo que tenemos que nuestro cliente es una petrolera, y tenemos que desarrollar un sistema que recibe mensajes de uno o dos sistemas, que luego pueden ser más, y necesitamos procesarlos, por ejemplo, dado un mensaje que indique que hay un nuevo pozo de perforación, entregar los nuevos datos a dos o tres sistemas que ya están funcionando, y prever que se pueda entregar a otros sistemas que vayan apareciendo. Uno podría verse tentado desde el comienzo: “Bien, es un caso para implementar con un Service Bus”. O “bien, es un caso para implementar usando Storm de Java”, o “acá hay que poner un BPM”. En mi experiencia, semenjante tipo de decisiones son tempranas, y hasta incorrectas o al menos, nos van a desviar de los casos de uso a implementar. Si tuviera que hacerlo yo solo, o si convenzo a mi equipo de la postura que estoy promoviendo en este post, lo haría: “Primero los casos de uso, simple proceso de mensajes, en un simple programa”. Solamente cuando tenga ya uno o varios casos de uso concretos implementados, podría ver cuál es la carga de mensajes (¿10 por día, 100000 por segundo?), la dificultad o facilidad de acceso a los sistemas finales ya existentes, la importancia (o no) de procesar un mensaje en menos de un minuto o en una semana, descubrir una “ley de Pareto”: el 80% de lo importante viene en el 20% de los mensajes, y se cubren con este y este otro caso de uso, etc… Sólo luego de haberse empapado de los casos de uso, y haber implementados algunos, podrá verse más en claro las necesidades de arquitectura que tenemos.

Pero alguien podrá decir ¿no es más trabajo? Así, parece que primero implementamos un caso de uso, y luego tenemos que reimplementarlo al cambiar la arquitectura. De nuevo, mi experiencia indica: la primera implementación es un “baby step” que sirve para ir aproximándonos al problema, sin “bajada de línea” previa. Y luego, seguimos mejorando la implementación, cambiando decisiones de arquitectura. Si realmente abrazamos lo ágil (por ejemplo, adoptando disciplinas como Test-Driven Development), los cambios no cuestan, y las decisiones diferidas no implican un costo, apenas algún esfuerzo. Y si luego vemos que una decisión de arquitectura no es la mejor, porque aparecen nuevos contextos, nuevos casos de uso, nuevas fuerzas, SI ABRAZAMOS LO AGIL, el cambio de implementación, y el cambio de decisión, tampoco debería tener un gran costo.

Eso es lo que veo que tiene que hacer el rol de arquitecto (hablo de rol, y no de puesto): saber diferir decisiones, y saber tomar decisiones diferidas que se puedan cambiar. Por más seguro que estemos “X es el camino a seguir (sea X BPM, MapReduce, Actor model, procesamiento distribuido usando colas… )” SI ABRAZAMOS LO AGIL debemos tomar decisiones CAMBIABLES y DIFERIBLES. Algunas de esas decisiones involucran lo que se llama (ampliamente, insisto) “arquitectura”.

Ahora bien, SI NO ABRAZAMOS lo ágil, y no adoptamos las disciplinas técnicas, duras y blandas que permiten encarar el cambio y el desarrollo evolutivo, entonces sí, vamos a tener problemas en la decisiones diferidas. Cada cambio nos va a costar, cosa que en el pasado “Waterfall” se quería evitar DEFINIENDO la mayor parte del sistema de antemano.

He visto sistemas, que por adoptar una decisión temprana, terminan perdiendo el rumbo. Todo el equipo queda con inercia, y todo caso de uso de uso lo quiere solucionar usando la decisión temprana, que se tomó como LA SOLUCION. Por ejemplo, si la decisión fue “vamos a usar Hadoop”, por más que los casos de uso que vengan clamen por otro tipo de solución, el equipo sigue mapeando todo problema a un Map Reduce distribuido.

Pero si diferimos las decisiones, tenemos más cintura. Ya el propio diferimiento permite que cuando se tome una decisión, el sistema no esté anquilosado, dirigido, congelado solamente a esa decisión. Aunque al final la decisión diferida sea la misma que nos dictaba nuestro “pequeño arquitecto yomelasetodas” desde el principio, el haber implementado gran parte de la solución sin haber tomado la decisión de arquitectura, permite armar un sistema más claro, más simple, donde elementos de arquitectura aparecen claramente por aparecer nuevas fuerzas que tenemos que tomar en cuenta, más que por sesudas ideas en general en el aire tomadas al comienzo. Y por lo que ví en estos años del tercer milenio, esa adopción diferida le da más calidad de “si surgen nuevas fuerzas o casos, podemos cambiar la decisión”, nos da un sistema más adaptable a cualquier cambio que aparezca en el futuro. Porque también eso es algo bueno para el rol de arquitecto: estar advertido que lo que estamos armando, si tiene éxito, va a evolucionar. En vez de hacerlo flexible desde el vamos, es mejor hacerlo cambiable, algo que veo que se va ejercitando con cada decisión diferida.

Imagen tomada de Swarm Intelligence. Me gustó la idea y la imagen, pero vean que abandono el adjetivo “emergente” en lo que escribí luego del primer párrafo, porque da a entender como que la arquitectura aparece sin intervención del equipo, por simple dinámica compleja.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez