Feeds:
Entradas
Comentarios

Archive for the ‘Metodologías’ Category


Hola a todos, aquí estoy de nuevo tras un tiempo de inactividad. La verdad es que andaba yo sin ningún tipo de inspiración para un nuevo post cuando el mundo real me ha obligado a escribir este. La verdad es que en estos meses, tanto en conversaciones, como en twitter, como en varios cursos de calidad, agilismo y testing que he impartido, han surgido de manera recurrente las siguientes preguntas: ¿y cómo engancho el selenium en todo esto? ¿Y cuándo grabo las navegaciones para automatizar las pruebas funcionales? ¿Y porque tanto rollo de BDD si ya tengo el producto “Super UI Test Automator Robot” que me graba las navegaciones? Y así una detrás de otra. Reconozco que si no te interesa el enfoque BDD ni el TDD, puedes seguir grabando navegaciones como modo de “automatizar test funcionales”. Para el resto de vosotros os ofrezco varias razones por las que considero que usar éste enfoque es una aberración:

La primera razón es bastante trivial, si haces BDD entonces tienes un enfoque “test first”, es decir, escribes el test antes que la implementación, y no se escribe ni una sola línea de código hasta que no tienes una especificación (test) en rojo. En este sentido es completamente imposible usar el paradigma de grabar navegaciones para automatizar el testing, ya que tienes que tener el test automatizado antes que la implementación y por lo tanto no puedes grabar nada.

La segunda razón tiene que ver con el mantenimiento. Si decidimos que el test de cada escenario de una historia de usuario se va a automatizar grabando una navegación, entonces lo que estamos testeando realmente es la UI y no la funcionalidad propiamente dicha. Cada vez que se produzca un cambio trivial en la UI de una página o panel, vamos a tener que grabar de nuevo todas las navegaciones que usen ese panel o página, aunque la funcionalidad no haya cambiado realmente. Claramente al usar el paradigma de “grabar navegación” estamos acoplando nuestros tests al diseño fino de la interfaz de usuario, que como todos sabemos, cambia con más frecuencia que la funcionalidad propiamente dicha de la aplicación. Un simple cambio en el atributo “name” o “id” de un elemento, o eliminar un botón de “buscar” para hacer una búsqueda en tiempo real, nos va a estropear las navegaciones.

Ojo, también nos podemos meter en este lío haciendo BDD, con por ejemplo Cucumber, si no tenemos cuidado. Pero mi argumento es que si usamos una herramienta que graba navegaciones como base de nuestra estrategia de testing funcional, este problema es inevitable. Por el contrario si decidimos usar un enfoque basado en programar nuestros tests podemos evitarlo fácilmente. ¿Cómo? Simplemente usando el patrón “page object”, e implementar cada “page object” con algún framework de automatización de UI, como WebDriver de Selenium o Watir. De esta forma si se produce un cambio en una UI, que no altere la funcionalidad, entonces sólo necesitamos retocar el “page object” correspondiente, sin necesidad de modificar nada en nuestros tests, escenarios y steps.

El que nuestros tests sean código hecho por nosotros es bueno. Podemos aplicar todas las técnicas de ingeniería del software que conocemos y aumentar la mantenibilidad de nuestros tests. Eso es, recordad que el código de test tiene que ser mantenible, y todo el tema de legibilidad, DRY y SOLID se le aplica, y por lo tanto podemos usar toda nuestra habilidad para que esto se cumpla.

Bien, y esto es todo, ¡ no diréis después que mis posts son muy largos ;-) !

P.S. Sois programadores, ¡ que no os asuste programar ! ¡ No os escondais en herramientas !

Read Full Post »


Hola a todos, algunos sabréis que @etnassoft, @pasku1 y yo estuvimos en la HTML5Party de Madrid participando en una hackaton. La verdad es que me gustó mucho el evento y me divertí bastante, también tomé notas del “cómo se hizo” ya que me gustó mucho como la organizaron. Pero este post no es para hablar de lo bonita que fue la hackaton, sino para diseccionar el “fracaso” de nuestra aplicación. La verdad es que me gusta mucho diseccionar los fracasos, ya que es en este tipo de ejercicios donde más se aprende.

Algunos pensareis que las hackatones son situaciones especiales donde no se aplican las reglas normales del desarrollo de software. Como tenemos poco tiempo sólo puedes ponerte a codificar como pollo sin cabeza, echar jornadas maratonianas y beber mucho red bull… ¡ nada más lejos de la verdad ! Este tipo de concursos representan muy bien la mayoría de los proyectos que te vas a encontrar en el día a día normal. Veamos… tiempo y recursos insuficientes, lista de requisitos a todas luces excesiva, problemas de última hora, contratiempos, pánico y la sensación de no llegar a producción, o sea, ¡ el verdadero “mundo real” ! Por lo tanto si al participar en una hackaton dejas de lado la disciplina de trabajo, entonces es probable que en un proyecto de verdad también abandones la disciplina. Por eso, cuando me presenté me dije: “esto hay que hacerlo con TDD y entrega continua de valor a muerte” Je, je, que iluso. Veamos que pasó.

El primer día empezamos bien, nos reunimos y nos pusimos de acuerdo en la funcionalidad, hicimos una maqueta papel, y nos pusimos manos a la obra. El compañero @etnassoft decidió hacer una maqueta HTML y @pasku1 y yo nos pusimos a hacer BDD. Todo bien, ¿no? ¡ Mal ! Esa fue la raíz de todos los problemas. Si vas a hacer entrega continua de valor, no puedes dividirte en equipos de especialistas, todos tienen que trabajar en equipo para sacar lo más rápidamente posible cada historia de usuario. Lo que terminó ocurriendo, de forma inconsciente, es que la UI se desarrolló por completo, para todas las historias de usuario. Igualmente, el servidor se desarrolló por separado para todas las historias de usuario. Lo curioso de esto, es que fue un fenómeno totalmente subconsciente, fruto de separarnos “temporalmente”. En el fragor de la batalla nos olvidamos de que teníamos que centrarnos en historias y no en capas. Cierto, cada capa se desarrolló por historias, pero no integramos hasta que estuvieron todas las historias hechas en cada componente, ¡demasiado tarde!

En un enfoque orientado a la entrega continua de valor, hubiéramos integrado la UI y el servidor por cada historia. Los problemas los hubiéramos detectado al principio, cuando es más fácil, al ser la aplicación pequeña, y encontrarnos más descansados. Además de ser más eficientes hubiéramos eliminado el factor pánico, al tener al menos algunas historias (las más importantes) listas para producción.

¿Hicimos TDD? ¡ Sí ! ¿De que sirvió? De poco. Nuestra capa de “negocio” quedó cubierta por bastantes tests, ¡ pero ninguna capa de negocio sirve sino está conectada a una UI ! Esto debe servir de lección a los que piensen que sólo con prácticas de ingeniería y usar tecnologías y frameworks puedes llegar al éxito de tu proyecto. Tus prácticas de ingeniería deben usarse en el contexto de unas buenas prácticas de gestión de proyectos, y viceversa. Todas las prácticas se apoyan e interactúan unas con otras y están pensadas para cubrir las debilidades de las demás.

Durante el transcurso de la hackaton ocurrieron los consabidos e inevitables imprevistos de todos los proyectos (lo dicho, igualito que el mundo real). A saber:

  • Se cayó el github. ¡Nooooo!
  • Twitter tuvo un bajón de rendimiento. Todas las peticiones tardaban al menos 30 seg. Eso hizo que nuestro ciclo de pruebas integradas fuera impracticable. Ayer probé y la respuesta era instantánea.
  • Un espacio en una cadena de texto de una configuración nos estuvo fastidiando durante 4 horas hasta que nos dimos cuenta.
  • Los drivers de node.js para MongoDB fueron un dolor.
  • Heroku no quería arrancar…. (¿qué demonios le pasó a la nube ese fin de semana?)
  • Un troll se coló por twitter y nos desconcertó bastante. Sí, es cierto, cosas que pasan.
¿Son estos imprevistos una excusa para no haber entregado la funcionalidad prevista? En absoluto, si hubiéramos hecho entrega continua y usado una “bala trazadora”, nos hubiéramos dado cuenta al principio de los problemas con MongoDB y Twitter y hubiéramos hecho un workaround cuando aun estábamos a tiempo.
¿Cómo terminamos el proyecto? Al final nos encontramos con una UI muy bonita (menos mal que estaba @etnassoft) y un servidor sin fallos, pero sin integrar, sin ninguna funcionalidad en producción, cansados y a falta de 5 horas para entregar. En un esfuerzo final conseguimos integrar un 30% de las historias de usuario, pero no precisamente las más interesantes. Si lo hubiéramos hecho con el enfoque ágil, al final de la hackaton nos hubiéramos encontrado con la funcionalidad más interesantes en producción, y no hubiéramos sufrido tanto.
¡ También hicimos cosas bien ! TDD, ritmo sostenible, usar estándares, buen diseño y arquitectura, refactorizar (pero no tanto como hubiera querido), puestas en común, trabajo en equipo, buen ambiente… y por eso no puedo terminar el post sin hacer mención de honor a @pasku1 y @etnassoft. Ha sido un placer trabajar con los dos y una experiencia muy divertida e instructiva. ¡ La próxima ganamos fijo !
P.S. Al final ganamos el segundo premio, que no está nada mal (Pero es que soy un tío exigente)

P.S.S. Enhorabuena a los organizadores, ¡en especial a @VictorSanchez!

Read Full Post »


Acabo con este post la serie dedicada a la estimación. Aunque no lo parezca, entre el primer artículo y el segundo de esta serie ya he explicado lo más importante de la estimación en proyectos ágiles. Todo se resume en cinco reglas sencillas:

  1. No estimes si no lo necesitas. Normalmente se estima para adelantar una previsión a los interesados en el proyecto y para evaluar si algo se debe implementar o no.
  2. Sólo debe estimar personal experto, que haya construido software similar en el pasado.
  3. Estima por comparación, y no usando unidades absolutas.
  4. No compares cosas dispares, tanto en tamaño como en nivel de abstracción. Por ejemplo, tareas con historias de usuario, o historias de usuario con releases.
  5. No necesitas estimar tareas.

La regla número 2 nos la dice explícitamente Scrum: el PO sólo estima el valor y el equipo de desarrollo estima el coste. Cada uno estima dentro de su área de conocimiento. Ninguna de las partes debe admitir presiones por parte de la otra para que cambie su estimación. Se asume un equipo experto, si algún miembro es un junior, yo recomiendo no darle mucho peso a su estimación, pero que participe en ella para que vaya cogiendo experiencia.

La forma más sencilla de aplicar la regla número 3 es no permitir usar unidades absolutas, como horas, dinero, etc. Simplemente se coge una funcionalidad a estimar y se pregunta, ¿en qué grado esta funcionalidad es más grande o pequeña que esta otra? ¿En qué grado el cliente valora más esta funcionalidad que aquella otra? A dichas preguntas se debe responder en unidades relativas, por ejemplo, “es el doble”, “es similar”, “es un 123% mayor”, etc.

Si a la regla número 3 le añadimos la 4, obtenemos una escala de medida relativa pero que a la vez está simplificada. Si sólo debemos comparar cosas que sean similares, ¿tiene sentido decir que esta funcionalidad es 100 veces más grande que esta otra? No. Claramente las dos cosas que estamos comparando no se encuentran al mismo nivel. Otro problema es intentar obtener una precisión en la estimación absurda, similar o incluso mayor a nuestro margen de error. No es razonable estimar que tal funcionalidad es 1.07 veces más grande que tal otra, cuando normalmente nuestro margen de error es mucho mayor de un 7%. Para reforzar estas ideas se han propuesto varias escalas de medida, y al realizar una estimación no se puede asignar un valor que no aparezca en la escala.

La más famosa de dichas escalas es la escala de Cohn. En la escala de Cohn se admiten los siguientes valores: 0, 1, 2, 3, 5, 8, 13, 20, 40, 100. Si os fijáis en escalas pequeñas tenemos 0, 1, 2 y 3, admitiendo que podemos tener cierta precisión. A escalas más grandes, 5, 8, 13, 20, 40 y 100, hay huecos. Estos huecos indican que no debemos invertir esfuerzo en ajustar nuestra precisión ya que estaríamos excediendo el margen de error que normalmente tenemos al estimar.

Siguiendo con el razonamiento que defiendo a lo largo de toda esta serie, ¿tiene sentido decir que una funcionalidad es 20 veces mayor que otra? Seguramente no, y estemos comparando funcionalidades en escalas muy distintas de tamaño. Normalmente una funcionalidad mayor que 5 u 8 se etiqueta como épica. De hecho las funcionalidades épicas normalmente no caben en un sprint, y por lo tanto no son aptas para ser implementadas. Si en vez de Scrum usas kanban, tampoco te salvas, ya que si introduces en el sistema funcionalidades de tamaño muy variable, tanto tu tiempo de entrega como tu productividad se van al garete (teoría de colas). Por lo tanto las funcionalidades épicas son carne de analista funcional, no se aceptan en la fase de desarrollo y deben ser analizadas y descompuestas con más rigor.

Lo inverso ocurre con funcionalidades de tamaño 0. En este caso se consideran tan pequeñas que no merece la pena de gastar esfuerzo en estimarlas. Normalmente representan un nivel de abstracción por debajo del que estamos tratando. Podría se una tarea en vez de una funcionalidad, o un simple detalle de ésta. En general cuando tenemos varias cosas de tamaño cero, o bien las descartamos por estar en un nivel de abstracción inadecuado, o bien las fusionamos si están relacionadas entre si.

Si tenemos en cuenta estas dos prácticas, podemos llegar a la conclusión de que la escala de Cohn, aunque útil para principiantes, no es realmente lo que buscamos. Así llegamos a la otra escala que se usa mucho: tallas de camiseta. Los valores admisible son S, M, L y XL. Las funcionalidades XL se consideran épicas. Sólo se aceptarán funcionalidades S, M o L. Este sistema simplifica el proceso de estimación y evita que caigamos en las trampas de estimar en horas o con una precisión irreal. La dificultad de este sistema estriba en que la capacidad del equipo debe medirse también en tallas de camiseta. Por ejemplo: en un sprint el equipo será capaz de 1 funcionalidad L, 2 M y 4 S. Esto puede causar problemas de estilo de “si tenemos 1 funcionalidad L, pero no hemos escogido ninguna que sea M o S, ¿podemos escoger otra funcionalidad L?”.  Una posible solución es definir equivalencias entre tallas: 1 L son 2 M y 6 S, pero 1 M son 2 S.

Otra posible solución para este problema es simplemente no usar una escala. En esta modalidad las funcionalidades o bien son épicas o no lo son. En este caso se deben descomponer todas las funcionalidades de forma que sus tamaños no sólo sean comparables, sino muy similares y constantes.  De esta forma el proceso de estimación se simplifica enormemente, y si se hace bien se reduce la variabilidad de las funcionalidades a implementar. Esto último es especialmente importante, ya una mayor variabilidad implica peores estimaciones y también peor productividad (de nuevo teoría de colas). De esta forma acercamos bastante Kanban a Scrum, donde realmente estaríamos haciendo Kanban si la capacidad de nuestro equipo fuera siempre igual a su límite de WIP. También nos permite estar alineados con la regla 1. La dificultad de esta técnica radica en que tanto el equipo como PO deben ganar mucha experiencia estimando, analizando las funcionalidades y conociendo la capacidad de la otra parte. Por lo tanto sólo lo veo factible en equipos muy maduros.

Finalmente, la regla número 5 simplemente nos indica que tanto al PO como a los stakeholders del proyecto no les interesa saber la descomposición en tareas del proyecto. Si las funcionalidades se descomponen o no en tareas, y si éstas se estiman o no, es una cosa del equipo de desarrollo. Al PO no el interesa el Sprint Burndown, ni el Task Board, sino el Project Burndown, el reporte de bugs y el flujo acumulado de entrega de funcionalidad. Como excepción a esta regla tenemos el caso en el que parte del equipo de desarrollo pertenece al cliente, o que tal vez no hay un equipo sino varios equipos de especialistas que forman un pipeline. Para asegurar una correcta coordinación, puede ser razonable hacer un plan de tareas y llevar un seguimiento de éstas, pero obviamente esta forma de estructurar equipos no es la recomendada en un proyecto gestionado de forma ágil.

Como apunte meramente personal, mi posición con respecto a la estimación es bastante pesimista, ya he visto muchas veces como hasta la estimación más cuidadosa fracasaba, y como planes muy bonitos plantados sobre un “project” se iban al garete en cuanto tenían el más mínimo contacto con la realidad. Mi opinión personal es que hay que estimar en formato “rápido y sucio”, y el proceso de desarrollo que uséis debe estar preparado para trabajar con este tipo de estimaciones aproximadas. Bueno, pues con esto acabo la serie sobre estimación, espero que os haya sido útil.

Read Full Post »


Una de las cosas sobre las que se discutió en la lista de agile-spain y que me animó a escribir estos posts sobre estimación fue si era conveniente o no estimar las tareas técnicas. Cuando en este post hable de tareas me referiré a tareas técnica (construir un servicio, definir una tabla, documentar, etc). En contrapartida tenemos las funcionalidades que son directamente utilizables por el usuario y le aportan valor. Desde el punto de vista del desarrollo de software, existen varias actividades en las que nos vemos forzados a estimar, las más importantes son:

  • Evaluar si es razonable o no presentarse a un proyecto cerrado, y que coste y tiempo podemos ofertar. El problema precisamente  está en que esta evaluación se realiza en el momento de hacer la oferta, que es justo donde tenemos menos información sobre el proyecto, y por tanto, menor precisión en la estimación. Además la estimación deja de ser una estimación y se transforma en un contrato. En este sentido los proyectos cerrados son muy arriesgados. La única circunstancia en la que el riesgo sería aceptable es que el proyecto que se presupuesta sea muy similar a otros que hayamos hecho en el pasado, y que el tamaño de éste no sea muy grande. Aquí lo único que podemos hacer es estimar el tamaño del proyecto en comparación con proyectos pasados. Para ello podemos hacer una captura de requisitos de alto nivel y asumir un entorno tecnológico y arquitectura similar a la de proyectos pasados.
  • Release plan. En el release plan nos comprometemos a definir que funcionalidades se pondrán en producción en que momento. La release plan se puede hacer de dos formas: orientada a fecha u orientada a funcionalidad. En la primera las fechas de entrada en producción son críticas y lo que estimamos es cuanta funcionalidad podrá entrar para esa fecha. O sea, tiempo fijo, alcance variable. En la segunda modalidad el alcance es fijo y lo que es variable es el tiempo, intentando estimar cuándo podremos poner en producción un conjunto predefinido de funcionalidad.

Si lo pensáis bien, en ninguna de las dos actividades anteriores es necesario estimar tareas. ¿Por qué? Simplemente porque ni a mi jefe ni a mi cliente le interesa saber cuanto voy a tardar en hacer una tarea. Lo que les interesa realmente es cuando voy a tener disponible tal funcionalidad, release o proyecto y cuanto me va a costar. Algunos diréis, sí, sí, pero es que yo descompongo en tareas, las estimo y después las sumo. Yo os digo que estais gastando dinero inútilmente en analizar una funcionalidad para ver que tareas os salen, y que vuestro margen de error no va a disminuir significativamente con respecto a una estimación directa de la funcionalidad. Dicho esto, ¿cómo estimo entonces una funcionalidad? Como dije en mi anterior post, los seres humanos somos buenos estimando por comparación, por lo tanto lo sensato es comparar la funcionalidad a estimar con otras similares implementadas en circunstancias similares en el pasado. Como veis no es necesario descomponer, ya que a nadie, excepto a nosotros, le importan las tareas, y el método que propongo es más sencillo y barato. Comparar una funcionalidad con otras ya hechas es más simple que hacer un análisis de que tareas se necesitan para realizar la funcionalidad, y después estimar cada tarea por separado.

Existen un par de dificultades en la regla anterior de “no analizar”. La primera es que lo que estemos estimando sea de tamaño “grande”. Según lo explicado en el anterior post, cuanto más grande y a más largo plazo estimemos, peor precisión obtendremos. Podríamos pensar en comparar un proyecto cerrado con otro proyecto cerrado implementado anteriormente, pero normalmente los proyectos son lo suficientemente grandes como para que nuestro error de estimación no sea práctico. Lo mismo se aplica con las releases y los release plan. También puede ocurrir con las funcionalidades que sean especialmente grandes. ¿Qué hacemos? Pues simplemente dividir. Dividir proyectos en releases, dividir releases en funcionalidades y funcionalidades grandes en otras más pequeñas. Dividir hasta que la precisión que podamos alcanzar con cada trozo sea razonable (y no más). Pero, ¿no es lo mismo ésto que el caso anterior de dividir una funcionalidad en tareas? No. Veamos las razones:

  • Al cliente le interesan las funcionalidades, no las tareas. El cliente evaluará el resultado final en función de las funcionalidades entregadas. Por lo tanto el plan de proyecto debe expresarse en funcionalidades, y no en tareas, para que el cliente pueda hacer un seguimiento que pueda entender (¿Se nos retrasó esta funcionalidad?¿Cuántas funcionalidades nos quedan?)
  • Funcionalidades y tareas están en dos niveles de abstracción diferentes. Podemos dividir un proyecto en releases, y estas en funcionalidades, y las funcionalidades en otras más pequeñas, que no abandonamos el nivel de abstracción que representa el “qué tiene que hacer el sistema”. El realizar estas divisiones y refinamiento de la funcionalidad es en realidad el “análisis funcional”. Las tareas están en otro nivel de abstracción, el “cómo pensamos construir el sistema nosotros”. En este sentido el salto entre funcionalidad y tareas es más brusco y propenso a error, y realmente hacerlo es parte del “diseño técnico”. ¿Queremos empezar a hacer el diseño técnico antes de planificar y estimar?

La otra dificultad se produce cuando no tenemos experiencia previa con algún proyecto o funcionalidad similar, es decir, no existe nada con lo que comparar. En general en esta circunstancia nuestra estimación va a ser muy arriesgada hagamos lo que hagamos. Estaríamos tentado de analizar dicha funcionalidad en tareas, con la esperanza que las tareas que salgan nos resulten familiares y podamos estimarlas. Esto es un error. Reflexionemos, si no tenemos experiencia con este tipo de funcionalidad, ¿cómo vamos a ser capaces de hacer el análisis y diseño técnico de la funcionalidad para descomponerla en tareas de forma precisa? Sí, podemos hacer esta descomposición, pero seguramente esté mal, o nos falten tareas o no hayamos tenido en cuenta las peculiaridades (para nosotros desconocidas) de esa funcionalidad o proyecto. Existe otra forma mejor, y es hacer una prueba de concepto, o “spike”. Invirtamos algo de dinero en hacer una implementación a pequeña escala de lo que queremos estimar, y usemos el conocimiento adquirido para hacer nuestra estimación. Sí, es más caro y lento, no es perfecto, pero es menos arriesgado que el enfoque anterior.

Finalmente, desde un punto de vista más específico de Scrum, existe otra razón para no estimar las tareas. Recordemos que la planificación en Scrum se basa en el ordenamiento de ítemes en el backlog en función de su ROI. El ROI se entiende como la relación entre el valor del ítem y su coste y/o tamaño. Es decir, en Scrum no sólo se estima el coste sino el valor. Si descompongo una funcionalidad en tareas y estimo cada tarea por separado, no sólo debo estimar el coste de cada una de ellas, sino su valor. Como el valor de una tarea es cero, el ROI de cada tarea es cero también. ¡ Si sumo el ROI de todas las tareas me sale que el ROI de la funcionalidad es cero ! Esto ocurre debido a que hemos sido inconsistentes y hemos cambiado de nivel de abstracción alegremente. Una señal de que hemos violado el nivel de abstracción pertinente es que las cuentas no salen, ya que el todo es mayor que la suma de las partes.

En el próximo post contaré algunas técnicas de estimación ágiles.

Read Full Post »


Hola a todos, aquí estoy después de un buen tiempo. La verdad es que tanto el trabajo como algunos asuntos personales me han tenido bastante ocupado y no he podido escribir mucho; pero ojeando la lista de correo de agile-spain me han dado ganas de escribir sobre un tema: la estimación.

La verdad es que existe bastante controversia entre si es conveniente estimar o no y cómo ha de hacerse correctamente. Los que me conocéis ya estareis anticipando mi respuesta: estima sólo lo justo y necesario y no te esfuerces en ser más preciso de lo que realmente necesitas o puedes. Este es el enfoque que cualquier profesional pragmático debería usar y olvidarse de cualquier postura “idealista”. Para aplicar esta idea en la práctica debemos antes tener claro qué es estimar, cuál es nuestro objetivo a la hora de estimar, y que grado de precisión podemos esperar de forma realista de nuestras estimaciones.

En el contexto del desarrollo de software, estimar es la actividad que realizamos para predecir el coste de realizar una tarea y/o implementar una unidad de funcionalidad, basándonos en nuestra experiencia pasada con tareas o funcionalidades similares. ¿Qué grado de precisión podemos esperar de tales estimaciones? Pues básicamente todo depende de la cantidad de experiencia que tengamos. Si no hemos tenido experiencia en tareas similares en el pasado, nuestra estimación resultará inútil. Ojo, experiencia en actividades similares, no experiencia en cualquier cosa. Por lo tanto sólo existen dos maneras sensatas de estimar:

  • Usar un sistema informático, que gracias a una amplia base de datos de mediciones de cuanto se tardó realmente en el pasado en hacer tareas similares, y mediante un modelo estadístico, nos ofrezca un intervalo de confianza de cuanto esfuerzo necesitaremos en realizar una tarea. Hago énfasis, intervalo de confianza, no una “media”.
  • Usar personal experto en el entorno tecnológico que se va a usar, que tenga un entendimiento claro de la tarea que se va a hacer y con experiencia a sus espaldas. Este personal de nuevo nos podrá ofrecer un intervalo como estimación.

En este sentido no debería estimar ni el cliente, ni personal senior pero no familiarizado con la tecnología a usar, ni nadie no implicado en la construcción del software. Esto coincide con lo que nos dice Scrum de que sea el equipo de desarrollo quien estime y no el PO. Sin embargo tampoco recomiendo que estime el personal junior, tal vez puede intentar estimar para aprender, pero no habría que darle mucho peso a su estimación. Quizás esto último no sea muy ágil, pero a mi me parece de sentido común.

En cualquier caso, existe un límite natural a la precisión de nuestras estimaciones:

  • Cuanta menos variabilidad exista entre las tareas mejor estimaremos, ya que nuestra experiencia pasada será más representativa. Variabilidad en cualquier aspecto: tecnología, tamaño, complejidad, funcionalidad, etc.
  • Las predicciones sobre tareas/funcionalidades grandes son menos precisas que las hechas sobre tareas más pequeñas. Esto es especialmente cierto si hablamos de precisión relativa (%).
  • Las predicciones a largo plazo son siempre menos precisas que las hechas a corto plazo. Esto es debido a que cuanto más tiempo pueda transcurrir, más posibilidad hay de que ocurran imprevistos o de que las circunstancias cambien o varíen.
  • La experiencia más reciente suele ser más representativa que la más antigua, ya que se han podido producir cambios en nuestro entorno. Por ejemplo: nuevas tecnologías, cambios en el equipo (para mejor o para peor), etc.
  • El ser humano es mejor estimando por comparación entre objetos del mismo tipo. Somos muchos más precisos adivinando que una tarea es aproximadamente un 50% más grande que otra tarea del mismo tipo, que intentando asignar tiempo o dinero directamente a cada tarea por separado.

O sea, para que tenga sentido el esfuerzo y dinero que vamos a gastar en estimar se deben cumplir algunos prerrequisitos:

  • Tener personal con experiencia en las tareas y tecnologías implicadas.
  • Que haya poca variabilidad en las tareas a estimar, es decir, que sean similares en todos los aspectos relevantes.
  • No malgastar esfuerzos, y usar un grado de precisión realista a la hora de estimar, en función del tamaño de lo que estemos estimando y la escala de tiempo. Podemos hacer una estimación grosera, en meses, de una tarea que está en la escala de años, y una más fina de horas para una tarea que está en la escala de una jornada, pero es absurdo estimar con precisión de días una tarea en la escala de un año.
  • Usar estimaciones relativas (por comparación) en vez de absolutas. Es importante no comparar dos cosas que estén en escalas distintas, tanto de tamaño como de tiempo.

Mientras no se cumplan los anteriores requisitos no tiene sentido ponerse a estimar, ya que simplemente los resultados obtenidos no nos servirán de nada. Si no se cumplen estos requisitos lo mejor es “adivinar” y “tirarse a la piscina”, ya que es igual de preciso pero más barato.

¿Radical? Pensadlo bien ¿Para que queremos estimar? En el fondo siempre que estimamos lo hacemos para asumir un compromiso con nuestro cliente, ya sea directamente o indirectamente a través de nuestros jefes: ¿cuándo estará terminado? ¿Podréis acabar esta funcionalidad antes de la semana que viene? ¿Te comprometes a hacer el proyecto en este tiempo y con tanto dinero? El hecho de asumir un compromiso nos lleva a correr el riesgo de no cumplirlo, y como tal lo debemos evitar a no ser que sea absolutamente necesario. En caso de que realmente necesitemos comprometernos formalmente debemos evaluar el nivel de riesgo de no cumplir esta promesa, lo cual depende sobre todo de la precisión de nuestra estimación. Por lo tanto, siguiendo el razonamiento de este post, no tiene sentido estimar si nuestra precisión no es aceptable, ya que estaríamos ante un riesgo incontrolado.

Así que ya sabeis, la próxima vez que monteis un plan de proyecto para un proyecto cerrado, o que comprometais una fecha de entrega, pensad antes si vuestras previsiones realmente tienen sentido, y no gasteis dinero en absurdos cálculos basados en cosas como los “puntos función”.

En próximos post abordaré la polémica sobre si estimar tareas o funcionalidades, y distintas técnicas ágiles para hacerlo.

Read Full Post »


Después del interludio con el spring io, vuelvo a la carga con el tema del diseño ágil de software. Como ya comenté, considero que dentro de las limitaciones de tiempo y dinero de un proyecto, hay que estar continuamente reestructurando el código con el objetivo de que sea fácil y barato cambiarlo cada vez que se produzca un cambio funcional, añadamos un nuevo requisito o necesitemos arreglar un bug. A estas reestructuraciones de código se les llama “refactors”.

Un refactor es una transformación de código que no altera la funcionalidad de este, pero sí su estructura. La idea es hacer refactors que hagan que nuestro código sea más flexible y maleable para poder alterarlo de forma rápida. El truco está en saber si al aplicar un refactor estamos mejorando nuestro código o si simplemente lo estamos complicando ¿Qué criterios sigo yo para evaluar si un refactor es bueno o no? Básicamente sólo sigo los tres siguientes:

  • Que el código resultante sea más legible.  Como ya expliqué considero este el criterio más importante.
  • Que se respeten los principios DRY y “experto en información”. Ya los explicaré en otro post
  • Que se respeten los principios SOLID. Entre este post y los siguientes pienso ir explorándolos.

¿Qué es el SOLID? SOLID es un acrónimo que consolida 5 principios: Single responsability, Open/Closed, Liskov substitution, Interface segregation y Dependency inversion. En este post me ocuparé del principio de única responsabilidad (Single responsability o SRP), o sea la S  de SOLID.

El principio de única responsabilidad (SRP) simplemente nos dice que ningún artefacto de código debe tener más de una única responsabilidad, y por lo tanto debe implementar una única funcionalidad. El principio aplica a sistemas, aplicaciones, frameworks, objetos y métodos. Obviamente según el artefacto al que se aplique, la amplitud del concepto de única responsabilidad cambia. Pero en este post nos vamos a centrar en cómo afecta a nuestra clases y objetos.

¿Cómo sabemos si un artefacto, como una clase o un método, respeta este principio? Es muy sencillo, simplemente  debemos preguntarnos a nosotros mismos sobre cuantas razones diferentes podemos tener para querer cambiar dicho artefacto. Un artefacto que cumple el principio de única responsabilidad sólo puede ser cambiado debido a una única razón. Si encontramos múltiples razones para cambiar un artefacto entonces no cumple dicho principio y debe ser refactorizado. A mi me gusta llamar a este principio, el principio del bombero/torero. El hecho de que el personaje de bombero/torero nos cause risa es que es totalmente ridículo que una misma persona realice tareas de dos profesiones tan diferentes al mismo tiempo. Si lo vemos absurdo en una persona, ¿porque nos quedamos tan tranquilos cuando vemos una clase que es un bombero/torero?

Para entender mejor este principio veamos un ejemplo. Suponed que tenemos una historia de usuario tal que sigue:

Cuando se realiza una transferencia importante

Entonces se genera un mensaje con información sobre dicha transferencia

Y se notifica a las personas de interés

Para que den su aprobación y así evitar el fraude fiscal”

Hablando con el cliente me dice que la persona de interés es un auditor, y que había pensado en notificarle vía mail. Las transferencias importantes son aquellas por encima de 1000 euros (son un poco ratas). Cómo estoy codificando en JAVA y usando el famoso framework Zprin, el código que me sale es el siguiente:

public class AuditorTransferenciasMonetarias {
  // Inyección de dependencias con el famoso framework Zprin (TM)
  // Con este framework somos productivos de la leche
  private ZprinPropertySource systemConfiguration;
  private ZprinTemplateEngine templateEngine;
  private ZprinMailIt mailIt;

  public void transferenciaRealizada(Transferencia transferencia) {
    if(this.esTransferenciaImportante(transferencia)) {
      String auditor=this.obtenerDireccionMailAuditor();
      String mensaje=this.componerMensajeAviso(transferencia);
      ConexionMail conexionMail=null;
      try {
        conexionMail=this.abrirConexionMail();
        conexionMail.enviar(new Mail().to(auditor).withBody(mensaje));
      } finally {
        if(conexionMail!=null)
          conexionMail.cerrar();
      }
    }
  }
  private boolean esTransferenciaImportante(Transferencia transferencia) {
    return transferencia.importe()>1000;
  }
  private String obtenerDireccionMailAuditor() {
    return this.systemConfiguration.getProperty("auditor.email");
  }
  private String componerMensajeAviso(Transferencia transferencia) {
    return this.templateEngine.getTemplate("aviso-transferencia-importante.ztpl").execute(transferencia);
  }
  private MailConnection abrirConexionMail() {
    return this.mailIt.openConnectionTo(this.systemConfiguration.getProperty("servers.mail"));
  }
}

Buen código, al usar el framework soy muy productivo y hago la clase super rápido, es más me quito de encima problemas complejos como el envío de mails, el acceso a propiedades configurables, o el tener que hacerme un motor de plantillas :-P Sólo tiene un problema, que no cumple el SRP (maldito SOLID, quién lo habrá inventado). SRP nos dice que la clase sólo debe tener una responsabilidad y la única manera en la que esta debería tener que cambiarse es cuando hay alguna alteración en dicha responsabilidad. En este caso la responsabilidad viene definida por la historia de usuario, así que mientras esta no cambie de forma significativa, entonces no deberíamos vernos forzados a cambiar la implementación. Obviamente el código descrito arriba no cumple esta propiedad. Vayamos por partes.

Suponed que el criterio para clasificar a una transferencia como importante cambia. Nos veríamos forzados a tocar nuestra clase, aunque realmente nuestra historia de usuario es la misma. Para evitarlo alteramos nuestro código:

public class AuditorTransferenciasMonetarias {
  // ....
  public void transferenciaRealizada(Transferencia transferencia) {
    if(transferencia.esImportante()) {
      String auditor=this.obtenerDireccionMailAuditor();
      String mensaje=this.componerMensajeAviso(transferencia);
      ConexionMail conexionMail=null;
      try {
        conexionMail=this.abrirConexionMail();
        conexionMail.enviar(new Mail().to(auditor).withBody(mensaje));
      } finally {
        if(conexionMail!=null)
          conexionMail.cerrar();
      }
    }
  }
  // ....
}

Como vemos ahora la clase Transferencia tiene un método que nos indica si es importante o no. Si el criterio de importancia cambia nos da igual, ya que este método nos protege de dicho cambio. Si el cliente decide que quiere un motor de reglas parametrizable en runtime mediante un backoffice, el afectado es Transferencia no la clase que estamos construyendo.

Otro motivo de cambio ajeno a nuestra responsabilidad es que la definición de “personas interesadas” cambie. De repente queremos notificar no sólo a un auditor, sino a varios y además al director de la sucursal. Para protegernos de ello veamos el siguiente cambio:

public class AuditorTransferenciasMonetarias {
  // ....
  // Ahora tenemos un colaborador de "negocio"
   // que está al mismo nivel de abstracción que esta clase
  private DirectorioEmpleados directorioEmpleados;

  public void transferenciaRealizada(Transferencia transferencia) {
    if(transferencia.esImportante()) {
      String mensaje=this.componerMensajeAviso(transferencia);
      ConexionMail conexionMail=null;
      try {
        conexionMail=this.abrirConexionMail();
        conexionMail.enviar(rellenarDestinatarios(new Mail().withBody(mensaje)));
      } finally {
        if(conexionMail!=null)
          conexionMail.cerrar();
      }
    }
  }
  private Mail rellenarDestinatarios(Mail mail) {
    for(Empleado interesado: this.interesadosEnTransferenciasImportantes())
      mail=mail.to(interesado.email());
    return mail;
  }
  private List<Empleado> interesadosEnTransferenciasImportantes() {
    return this.directorioEmpleados.empleadosConRol(Empleado.INTERESADOS_TRANSFERENCIAS_IMPORTANTES);
  }
  // ....
}

Ahora gracias a la interface DirectorioEmpleados, podemos buscar a todos los empleados que necesitamos notificar. Para ello buscamos por rol. La asociación empleado/rol se puede definir aparte, e incluso ser dinámica y cambiar en runtime. Observad que hemos añadido un colaborador que está al mismo nivel de abstracción de la clase que estamos codificando, y que el código ahora se parece un poco más a la definición de la historia de usuario.

Otro motivo por el cual podemos vernos obligados a cambiar esta clase, es que ahora los mensajes no se envíen por mail, sino por SMS. O peor, ¡ que se envíen a la vez por SMS, Mail, y chat a la vez ! Como no queremos que nos pillen con eso (que me conozco al cliente, que cambia de opinión constantemente) volvemos a “mejorar” el código:

public class AuditorTransferenciasMonetarias {
  // ...
  private SistemaMensajeriaCorporativa mensajero;

  public void transferenciaRealizada(Transferencia transferencia) {
    if(transferencia.esImportante()) {
      this.mensajero
            .enviar(
              new Mensaje()
                   .a(this.interesadosEnTransferenciasImportantes())
                   .conContenido(this.componerMensajeAviso(transferencia))
      );
    }
  }
  // ....
}

Esta vez hemos introducido el sistema de mensajería corporativa, que nos permite enviar un mensaje de forma abstracta a varios empleados. Ya está, ya no hay más motivos de cambio… no perdón nos queda otro motivo y gordo. Hablando con nuestro amigo el “friki”, nos cuenta que el framework Zprin ya no es el acabose y que ha salido el framework XprinGio, y que a Zprin se queda sin soporte en menos de dos años. Nos olemos la tostada, en cuanto se entere el arquitecto jefe del temita nos manda cambiar de framework, o peor, si se entera el jefe de proyecto que sólo nos quedan dos años de soporte, toma migración. Hay que protegerse a toda costa de tan temida migración:

public class AuditorTransferenciasMonetarias {
  private DirectorioEmpleados directorioEmpleados;
  private SistemaMensajeriaCorporativa mensajero;
  private PlantillasCorporativas almacenDePlantillas;

  public void transferenciaRealizada(Transferencia transferencia) {
    if(transferencia.esImportante()) {
      this.mensajero
            .enviar(
              new Mensaje()
                   .a(this.interesadosEnTransferenciasImportantes()
                   .conContenido(this.componerMensajeAviso(transferencia))
      );
    }
  }
  private Documento componerMensajeAviso(Transferencia transferencia) {
    return this.almacenDePlantillas.usandoPlantilla("avisos/transferencia-importante").crearDocumentoCon(transferencia);
  }
  private List<Empleado> interesadosEnTransferenciasImportantes() {
    return this.directorioEmpleados.empleadosConRol(Empleado.INTERESADOS_TRANSFERENCIAS_IMPORTANTES);
  }
}

Esta vez hemos eliminado todo dependencia con Zprin, y tenemos un motor de plantillas abstracto. Obviamente cuando implementemos el motor de plantillas, o el sistema de mensajería nos acoplaremos a Zprin, pero en una posible migración, sólo tendremos que cambiar estos servicios y no todas las clases de negocio. Otro cambio es que ahora la plantilla genera un Documento en vez de un String. Además la clase Documento sabrá serializarse en cualquier formato que se necesite por parte del mecanismo de mensajería (String, XML, HTML, JSON, PDF…) lo que permite tener sistemas de mensajería más complejos.

Como se observa, si seguimos el principio de única responsabilidad nuestros artefactos tendrán una alta cohesión y por lo general un bajo acoplamiento, ya que quedan pocos métodos muy relacionados entre si y muy cortos. Pero lo mejor es que se tenderá a que la tasa de cambios de un único artefacto baje, y que éstos cambios sean más localizados, ya que sólo se va a necesitar modificarlo ante un único tipo de cambio al existir una relación uno a uno entre responsabilidad y clase. Obviamente este era un ejemplo de código algo artificial, y seguro que le sacais pegas, pero creo que podéis captar la idea.

Pero no es oro todo lo que reluce, más adelante en esta serie veremos el principio DRY y el “experto en información” y como ambos se terminan pegando con el principio de única responsabilidad, con lo que se produce un conflicto grave. Para colmo, el principio de segregación de interfaces nos puede complicar también la vida. Armonizar todos estos principios, es el caballo de batalla del diseño orientado a objetos. De hecho, respetar cada principio por separado es sencillo, lo difícil es juntarlos todos, ya que veréis que es como si unos principios contradijeran a otros ¿Terminará siendo la OO en realidad un paradigma fracasado y contradictorio?

Read Full Post »


Andaba yo buscando un ejemplo de código para demostrar el concepto del TDD, refactor y diseño emergente, cuando mi señora me apuntó al coding dojo en Ruby, organizado por @madridrb el pasado día 30 de Diciembre, donde durante las cervezas post-evento se me ocurrió este post. El dojo, donde el señor @ecomba propuso hacer la kata de los números romanos, se encontraba con bastante más gente de lo normal, y es que este hombre tiene mucho tirón. La idea era ir turnándose cada 3 minutos en un ordenador e ir avanzando la kata. Como éramos bastantes, y no había mucho tiempo, al final realmente no pudimos avanzar lo suficiente, y tuvo que acabarla @ecomba de forma rápida y sin poder entretenerse mucho. Esto hizo que tuviera que dar algunos saltos de diseño. Posteriormente en las cervezas me enteré que muchos asistentes no estaban muy versados en esto del TDD y que estaban confusos. Sobre todo la mayor discusión trataba sobre por qué era importante hacer TDD y refactor en pasos muy pequeños y qué ventaja daba a la hora de diseñar el código. Así que en ese momento supe que mi siguiente post iba a ser un ejemplo con código sobre cómo conseguir un diseño emergente a base de Refactor y TDD en pasos pequeños (baby steps).

Este es uno de los posts más difíciles que he hecho ya que voy a intentar mostrar mis procesos mentales de diseño, y como me enfrento a un problema de programación (sencillo por cierto). Aviso a los asistentes del coding dojo que el código final al que llego en este post es diferente al que hizo @ecomba, lo que demuestra que no suele existir una única implementación “óptima”, y que el resultado puede variar en función de criterios personales y algo del azar. No tengo experiencia alguna en Ruby, sólo he hecho los koans y esta kata, con lo que he usado un Ruby muy básico y sencillo al no conocer bien el lenguaje y la librería de objetos. Sin embargo guiado por el TDD y el refactor creo que llego a una solución bastante compacta. Empecemos…

Lo primero de todo es familiarizarse con la funcionalidad a implementar. Ni el TDD ni el refactor son un sustituto de ponerse a pensar, sólo un guía para nuestro proceso creativo, por lo que debemos tener alguna idea del objetivo funcional que debe alcanzar nuestro código. No es necesario ser un experto en la funcionalidad, sino saber por donde van más o menos los tiros y no ir dando palos de ciego. Aquí va la funcionalidad de los números romanos, al menos lo que recordaba cuando me puse a tirar código:

  • Queremos convertir un número entero en un número romano. Nada más.
  • Los romanos no representaban de forma explícita ni el cero ni los números negativos.
  • Existen un conjunto de 13 símbolos o numerales básicos (nosotros tenemos 10). Cada uno de estos tiene un valor predefinido, pero ninguno representa el 0 o valor negativo.
  • Para representar un número, se concatenan estos numerales, y sus valores se van sumando hasta que se obtiene el valor del número.
  • Algunos casos como el 4 o el 9 son especiales y se representan de forma especial.

Así que llegué a mi casa, abrí el vim, me instalé el rspec y secuestré el “mug of vi” de mi mujer para que me sirviera de chuleta (soy penoso con el vi y quería aprender, que lo pasé muy mal en el dojo). Lo primero que se hace siempre es coger el caso más básico, y que cosa más básica que el número 1. En rspec queda:

describe "Roman number" do
	it "I is equivalent to 1" do
		1.to_roman.should == 'I'
	end
end

Evidentemente falla, con lo que hay que añadir una implementación. Pero siguiendo las reglas del TDD y refactor, debe ser la implementación más simple posible que pase el test, ya lo pondremos bonito después si existiera una razón de peso. Para los que no sepan Ruby, en éste lenguaje las clases son abiertas, por lo que el enfoque de diseño tomado es añadir un método “to_roman” a la clase “Fixnum” que representa a los enteros.  La implementación es obvia:

class Fixnum
	def to_roman
		'I'
	end
end

describe "Roman number" do
	it "I is equivalent to 1" do
		1.to_roman.should == 'I'
	end
end

Vamos a probar ahora con el número 5, que es otro de los numerales básicos romanos. La estrategia de momento es ir añadiendo numerales básicos, a ver que nos sale. El test y su correspondiente implementación:

class Fixnum
	def to_roman
		return 'V' if self == 5
		'I'
	end
end

describe "Roman number" do
	it "I is equivalent to 1" do
		1.to_roman.should == 'I'
	end

	it "V is equivalent to 5" do
		5.to_roman.should == 'V'
	end
end

La técnica es sencilla, copiar y pegar lo que funcionó en el caso del 1. Reemplazamos los valores adecuados y cubrimos cada caso con un if. Estamos en modo “pasar tests”, hasta el momento no hemos detectado duplicación y no hemos refactorizado nada. Pero esto ya empieza a oler a cuerno quemado. Volvemos a repetir, esta vez para el 10. La cosa queda:

class Fixnum
	def to_roman
		return 'X' if self == 10
		return 'V' if self == 5
		'I'
	end
end

describe "Roman number" do
	it "I is equivalent to 1" do
		1.to_roman.should == 'I'
	end

	it "V is equivalent to 5" do
		5.to_roman.should == 'V'
	end

	it "X is equivalent to 10" do
		10.to_roman.should == 'X'
	end
end

Ya se observa claramente la duplicación. Tenemos dos (o tres, según se mire) lineas con la misma estructura de código. Esto es normal ya que hemos estado haciendo copy&paste. Ahora toca reflexionar sobre la intención de nuestro código, ¿qué queremos hacer en realidad? ¿Refleja el código actual esa intención (es expresivo)? Es obvio que lo que queremos hacer es mapear números a numerales romanos, y que existe una correspondencia uno a uno. En realidad estamos haciendo una búsqueda de un literal romano por número entero a base de un montón de ifs. El uso de una simple Hash (o mapa o diccionario) nos elimina las líneas duplicadas y nos da un código más expresivo (si elegimos bien los nombres). Tras refactorizar me sale lo siguiente:

class Fixnum
	ARABIC_TO_ROMAN_NUMERAL = { 10 => 'X', 5 => 'V', 1 => 'I' }

	def to_roman
		ARABIC_TO_ROMAN_NUMERAL[self]
	end
end
# .......

Por brevedad no me entretendré con los siguientes 10 numerales romanos, y pasaré al siguiente problema, ¿qué pasa si un número no se corresponde con un literal romano? Según el funcional hay que concatenar los numerales hasta sumar el número deseado. Como caso más sencillo de este escenario añado el test para el número 2 que debe transformarse en ‘II’. Además voy a refactorizar un poco los ejemplos de test, agrupándolos por escenario. En RSpec usaré un contexto por escenario (no se si esto es purista pero a mi me parece bien). El test ahora es:

# .........
describe "Roman number" do
	context "has basic numerals with different values" do
		it "I is equivalent to 1" do
			1.to_roman.should == 'I'
		end

		it "V is equivalent to 5" do
			5.to_roman.should == 'V'
		end

		it "X is equivalent to 10" do
			10.to_roman.should == 'X'
		end
	end

	context "concatenates numerals in descending order until they sum up the desired integer" do
		it "II is equivalent to 2" do
			2.to_roman.should == 'II'
		end
	end
end

Como veis un contexto para los numerales básicos y otro para los que no lo son. Añado la implementación más básica que se me ocurre, si el número coincide con un numeral básico lo devuelvo y acabo, si no, devuelvo ‘II’ a cascoporro:

class Fixnum
	ARABIC_TO_ROMAN_NUMERAL = { 10 => 'X', 5 => 'V', 1 => 'I' }

	def to_roman
		return ARABIC_TO_ROMAN_NUMERAL[self] if ARABIC_TO_ROMAN_NUMERAL[self]
		'II'
	end
end
# ........

Añado un test para el número 3, que debe resultar en ‘III’ y su correspondiente implementación. Pero esta vez me lo curro un poco más:

class Fixnum
	ARABIC_TO_ROMAN_NUMERAL = { 10 => 'X', 5 => 'V', 1 => 'I' }

	def to_roman
		return ARABIC_TO_ROMAN_NUMERAL[self] if ARABIC_TO_ROMAN_NUMERAL[self]
		'I' + (self - 1).to_roman
	end
end

describe "Roman number" do

#      ......................

	context "concatenates numerals in descending order until they sum up the desired integer" do
		it "II is equivalent to 2" do
			2.to_roman.should == 'II'
		end

		it "III is equivalent to 3" do
			3.to_roman.should == 'III'
		end
	end
end

Como veis esta vez he sido más sofisticado y en vez de meter un ‘III’ if self == 3, he leído bien el funcional y se me ha ocurrido un algoritmo. Siguiendo el espíritu de concatenar hasta sumar el número, se me ocurre que puedo tomar el numeral ‘I’ y restarle su valor al número que quiero convertir. El resultado de esta resta, qué es lo que queda para conseguir sumar el número deseado, lo convierto a su vez en un número romano y lo concateno. Aquí hemos tenido que parar y reflexionar sobre la funcionalidad para obtener una idea creativa. El TDD nos ha servido para llegar a un punto donde esta idea se nos pueda ocurrir con facilidad.

Sigamos, ¿qué pasa con otros casos? Añado tests para el 6, el 11 el 15 y el 20. Los hago pasar haciendo copy&paste del caso del 2 y el 3, reemplazando ‘I’ y 1, por ‘V’ y 5 para pasar el test del 11, y ‘X’ y 10 para pasar el test del número 9. La cosa queda así:

class Fixnum
	ARABIC_TO_ROMAN_NUMERAL = { 10 => 'X', 5 => 'V', 1 => 'I' }

	def to_roman
		return ARABIC_TO_ROMAN_NUMERAL[self] if ARABIC_TO_ROMAN_NUMERAL[self]
		return 'X' + (self - 10).to_roman if self > 10
		return 'V' + (self - 5).to_roman if self > 5
		'I' + (self - 1).to_roman
	end
end

describe "Roman number" do

#      .................

	context "concatenates numerals in descending order until they sum up the desired integer" do
		it "II is equivalent to 2" do
			2.to_roman.should == 'II'
		end

		it "III is equivalent to 3" do
			3.to_roman.should == 'III'
		end

		it "VI is equivalent to 6" do
			6.to_roman.should == 'VI'
		end

		it "XI is equivalent to 11" do
			11.to_roman.should == 'XI'
		end

		it "XV is equivalent to 15" do
			15.to_roman.should == 'XV'
		end

		it "XX is equivalent to 20" do
			20.to_roman.should == 'XX'
		end
	end
end

Fijaros en el orden en que pongo las líneas de código, primero evalúo los numerales con valor más alto y después las de valor más bajo. Si lo hacemos en otro orden los tests fallan, al devolverme por ejemplo ‘VVI’ en vez de ‘XI’. En este caso los tests son los que nos han hecho darnos cuenta de que hay que ordenar los numerales de mayor a menor, y además de sumar el valor, debe ser la representación más corta posible. Esto no lo tenía yo nada claro por el funcional que indiqué más arriba. En este caso los tests nos aclaran la funcionalidad.

Sin embargo de nuevo tenemos duplicación y el código da repelús. La misma estructura de código repetida en tres líneas, sólo varía en el numeral romano usado en cada caso y su correspondiente valor numérico. ¿Podemos sustituir esta duplicación por una regla parametrizable? ¿Quizás un método auxiliar que recibiera como parámetros el numeral romano y el valor numérico? Esta solución eliminaría algo de duplicación, pero aun quedaría duplicada la estructura de la cascada de ifs. Es esto último lo que más me preocupa, ya que se viola el principio abierto/cerrado. Cuando quisiéramos añadir un nuevo numeral (cuando nuestro cliente recordara uno nuevo), tendríamos que “abrir” el método to_roman para añadir otra línea más. Esto es más grave que unos cuantos caracteres repetidos. Hay que pararse a pensar de nuevo, es hora de ganarse el sueldo. Lo que se me ocurrió es lo siguiente:

class Fixnum
	ARABIC_TO_ROMAN_NUMERAL = { 10 => 'X', 5 => 'V', 1 => 'I' }

	def to_roman
		return ARABIC_TO_ROMAN_NUMERAL[self] if ARABIC_TO_ROMAN_NUMERAL[self]
		ARABIC_TO_ROMAN_NUMERAL.each do | arabic_number, roman_numeral |
			return roman_numeral + (self - arabic_number).to_roman if self > arabic_number
		end
	end
end
#  .............

La idea es recorrer la colección de numerales, de forma que encontremos el numeral romano de más valor, que sea inferior al número que buscamos, y usamos dicho numeral en la regla recursiva que descubrimos antes para calcular el resultado. Podríamos haber usado un bucle for, pero sospecho que no están bien vistos en el mundo de Ruby, así que uso el método iterador each, y le paso un bloque de código. En cuanto encuentro el numeral buscado devuelvo el valor y el bucle (perdón, la iteración) termina. De paso ya no necesito ese método auxiliar que se me ocurrió antes, ya que la regla recursiva sólo se usa una vez y es bastante compacta. Me encanta cuando los planes salen bien… WTF! ¡ Fallan los tests ! ¡ Qué c*** pasa ! ¡ Es la hora del debugger !

Lo que ocurre es que el método each itera las entradas de la hash en el orden que le sale de las gónadas. Es una sorpresa, ya que según la documentación, debería iterarlos en el orden en que los añades a la hash. Ya estoy por mandar un bug a la comunidad de Ruby cuando descubro mi fallo. Estoy usando Ruby 1.8.x y la documentación es de la 1.9.x. Algo huele a quemado, busco un poco y efectivamente: originalmente el orden de iteración de las hash era aleatorio, pero a partir de la 1.9.x es por orden de inserción. Esto de añadir cambios que rompen la API y cambiar sólo el “minor version” no es buena idea.

Total, que tengo una implementación que supuestamente funciona en Ruby 1.9 (no lo he probado), pero no en 1.8. Algo he de hacer. De momento me calmo un poco, y me dedico a arreglar otra duplicación de código que me está matando, la primera línea del método to_roman. Lo que hago es lo siguiente:

class Fixnum
	ARABIC_TO_ROMAN_NUMERAL = { 10 => 'X', 5 => 'V', 1 => 'I' }

	def to_roman
		return '' if self == 0
		ARABIC_TO_ROMAN_NUMERAL.each do | arabic_number, roman_numeral |
			return roman_numeral + (self - arabic_number).to_roman if self >= arabic_number
		end
	end
end
# ..............

Directamente elimino la línea y cambio self>arabic_number por self>=arabic_number. La idea es que si encuentro un numeral que sea exactamente igual al valor buscado, también puedo aplicar la regla recursiva. En este caso el resto es 0, que como sabemos no se representa en números romanos. Se soluciona devolviendo cadena vacía como caso base de la recursividad cuando el número es 0, eliminando la fea duplicación que teníamos como caso base.

Ahora ya puedo centrarme en hacerlo retrocompatible con Ruby 1.8. Simplemente ordeno de forma explícita la hash en orden descendiente (de mayor a menor). Un cambio trivial:

class Fixnum
	ARABIC_TO_ROMAN_NUMERAL = { 10 => 'X', 5 => 'V', 1 => 'I' }.sort.reverse

# ................

end

Dicho sea de paso, esta ordenación me devuelve un array, con lo que en vez de una hash termino con un array de pares clave/valor en la constante ARABIC_TO_ROMAN_NUMERAL. Debido a que itero con el método each, y a que realmente había dejado de usar la búsqueda por clave, el cambio es transparente.

Ahora toca enfrentarse a la tercera fase de la especificación funcional: los casos especiales. Los romanos no escribían ‘IIII’ para el número 4, sino ‘IV’. Lo mismo con el 9 que se representa como ‘IX’ en vez de ‘VIIII’ (esta regla es para los números romanos clásicos, la versión más primitiva no la tenía). Añado estos ejemplos y obviamente los tests fallan. Tal vez tenga que hacer un algoritmo de simplificación, de modo que si hay tres numerales seguidos iguales los sustituya por la versión simplificada. Parece difícil. Antes de complicarme la vida, y por si cuela, se me ocurre añadir ‘IX’ y ‘IV’ a los numerales básicos. Sorprendentemente funciona, me olvido de algoritmos complicados. Finalmente añado algunos tests con números complicados, para asegurarme que todo funciona bien. El código final es:

class Fixnum
	ARABIC_TO_ROMAN_NUMERAL = { 10 => 'X', 9 => 'IX', 5 => 'V', 4 => 'IV', 1 => 'I' }.sort.reverse

	def to_roman
		return '' if self == 0
		ARABIC_TO_ROMAN_NUMERAL.each do | arabic_number, roman_numeral |
			return roman_numeral + (self - arabic_number).to_roman if self >= arabic_number
		end
	end
end

describe "Roman number" do
	context "has basic numerals with different values" do
		it "I is equivalent to 1" do
			1.to_roman.should == 'I'
		end

		it "IV is equivalent to 4" do
			4.to_roman.should == 'IV'
		end

		it "V is equivalent to 5" do
			5.to_roman.should == 'V'
		end

		it "IX is equivalent to 9" do
			9.to_roman.should == 'IX'
		end

		it "X is equivalent to 10" do
			10.to_roman.should == 'X'
		end
	end

	context "concatenates numerals in descending order until they sum up the desired integer" do
		it "II is equivalent to 2" do
			2.to_roman.should == 'II'
		end

		it "III is equivalent to 3" do
			3.to_roman.should == 'III'
		end

		it "VI is equivalent to 6" do
			6.to_roman.should == 'VI'
		end

		it "XI is equivalent to 11" do
			11.to_roman.should == 'XI'
		end

		it "XV is equivalent to 15" do
			15.to_roman.should == 'XV'
		end

		it "XX is equivalent to 20" do
			20.to_roman.should == 'XX'
		end
	end

	context "converts even complex examples (to gain more trust in our implementation)" do
		it "XVIII is equivalent to 18" do
			18.to_roman.should == 'XVIII'
		end

		it "XIX is equivalent to 19" do
			19.to_roman.should == 'XIX'
		end

		it "XXXVII is equivalent to 37" do
			37.to_roman.should == 'XXXVII'
		end
	end
end

Como veis el código es diferente al que propuso @ecomba. Curiosamente @ialcazar me mostró la solución de @cavalle que es bastante similar a la mía, seguramente porque ambos hemos optado por un enfoque recursivo. Eso sí, ninguna de las tres soluciones tiene métodos de más de 4 líneas de código ;-). Notad que he tomado dos decisiones de diseño importantes: me he decidido por un diseño recursivo, y los casos especiales, como ‘IV’ o ‘IX’, los trato como numerales básicos. ¿Qué ocurriría si hubiéramos decidido que los casos especiales no son numerales básicos? ¿A alguien le interesa explorar este camino?

¿Para que nos ha servido el TDD? En este caso nos ha servido para guiar el proceso de pensamiento. Añadiendo código poco a poco puedo detectar duplicación, violaciones de principios SOLID, y otros problemas rápidamente. Es en estos momentos donde TDD+Refactor en pasos pequeños, nos obliga a parar y pensar. Hasta que no tengamos una visión más profunda del problema, no podremos avanzar, y ésta forma de trabajar nos golpea en la cabeza obligándonos a reflexionar. Sin embargo no olvidemos que el problema de los números romanos es pura algorítmica. En casos más complejos como diseño OO el TDD brilla en todo su esplendor.

Si queréis, podéis entrar github y echarle un vistazo a todo el histórico de “baby steps” que fui haciendo. ¿Alguien se anima a hacerlo en otro lenguaje?

Read Full Post »


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 :-P —> ¡ 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:
    1. 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.
    2. 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.
    3. 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 !

Read Full Post »


Hola a todos, como veo que hay mucha inquietud por todas esas prácticas de desarrollo ágil, como el TDD, el refactor y el pair programming, y como me huelo que por ahí que se ha puesto de moda eso del SOLID, he decidido hacer unos cuantos de posts sobre el diseño de software ágil. Espero que me salga algo coherente…

En posts anteriores he hablado mucho de gestión de proyectos, sobre todo de Scrum. Me centré en que lo bueno de las metodologías ágiles es que proponen que se debe entregar valor de forma continua y cuanto antes mejor. Ésto es mucho más sencillo de decir que de hacer. Scrum, por ejemplo, no nos orienta sobre cómo conseguir implementar en cada sprint rodajas de funcionalidad potencialmente entregables al usuario. De hecho esto es bastante complicado de hacer, ya que implica que con cada nueva funcionalidad que queramos implementar, debemos modificar o extender los componentes de software que hayamos realizado hasta el momento. Además es probable que debamos introducir nuevos componentes, no previstos hasta el momento, en la arquitectura del sistema y que estos encajen con los ya existentes. Todo un desafío. Las prácticas de ingeniería ágiles no son más que unas herramientas para conseguir estos objetivos. Muchas de dichas prácticas existen desde hace mucho tiempo (pre agilismo) dentro del paradigma OO, aunque hayan sidos ignoradas por nosotros, programadores de JABOL.

Antes de meterme en materia técnica, y abrir el arsenal de armas de destrucción masiva, voy a explicar en este post lo que quiero decir cuando en una consultoría digo: “pues depende…”. Es un tema que me preocupa porque empiezo a oír hablar de SOLID, código expresivo y demás, y todo ello justificado en nombre de una entidad abstracta llamada “calidad”. Es una corriente que me está dando algo de miedo, ya que en nombre de la “calidad”, me puedo tirar refactorizando mi código y chapándolo en oro por los siglos de los siglos sin entregar nunca a tiempo nada de valor a mi cliente. Me da la impresión de que algunos hablan de la “calidad” de la misma manera en la que un sacerdote podría hablar del “bien supremo” o un artista de la “belleza” de su obra. Las técnicas de diseño ágil son armas poderosas, y con ese poder viene la responsabilidad de no hacernos pajas mentales con ellas :-P

Mi postura es que la calidad del software, como entidad platónica y absoluta no existe. Es algo que no es medible, ¿alguien me puede proponer una fabulosa métrica que la mida y que sea factible de implementar? Si algo no es medible no debemos tomarlo como guía para tomar decisiones de diseño de nuestro código, por lo tanto, yo no uso la calidad para estos fines. Veamos pues como bajar al mundo real para tomar decisiones de diseño. En el mundo real del desarrollo de software profesional el objetivo es entregar software al cliente y que éste esté satisfecho con él. Por satisfecho quiero decir que se deben cumplir las dos siguientes condiciones:

  • El funcionamiento del software cumple las expectativas del cliente de forma razonable.
  • El software se entrega en un plazo de tiempo y con un coste razonable para el cliente. Si es más importante el coste o el tiempo lo debe decidir el cliente al principio del proyecto, de forma que podamos balancear estos dos factores adecuadamente.

O sea, que no descubro la pólvora, el cliente estará satisfecho si el software funciona, está a tiempo y su coste no se ha ido de madre. La parte quizás más problemática es la definición de “razonable”. Para mi el significado de razonable es algo que se debe acordar con el cliente mediante negociación, aunque eso sí, es una negociación asimétrica donde el cliente tiene la última palabra.

Por lo tanto el objetivo del diseño de software ágil es hacer software que funcione, respetando unas restricciones de tiempo y de dinero, y se entregue dicha funcionalidad de forma continua e incremental, aceptando posibles cambios. Las técnicas y principios de desarrollo ágil como TDD, Pair Programming, SOLID, KISS, DRY, etc. no son más que meras herramientas para conseguir ésto, no las confundamos con el objetivo. Antes de aplicar un patrón de diseño, o una refactorización, calculad el coste que os supondrá hacerlo en tiempo y dinero y comparadlo con el ahorro de coste y dinero que os supondrá a largo plazo. Si no hay un beneficio obvio, no lo hagáis. Así que cuando alguien me pregunta: “¿es bueno seguir los principios SOLID?”; y yo respondo: “pues depende..”; simplemente estoy diciendo que en función del contexto de tu proyecto tendrás que analizar si te va a dar beneficios o no.

En este sentido, la primera técnica que podemos aplicar es usar un timebox. Un timebox es un intervalo de tiempo predefinido, y no alterable, que restringe la implementación de una tarea. Ejemplos de timeboxes son: pomodoro, jornada de trabajo, sprint, deadline… La idea es que nos concentremos en cumplir con nuestras tareas y sintamos la presión de que se no acaba el tiempo. Esta presión nos ayudará a evitar que nos “entretengamos” en cosas que no sean conseguir terminar nuestra tarea. Usar timebox es una técnica común en el mundo ágil, ya que nos permite controlar nuestro ímpetu por demostrar como de bien sabemos programar, y centrarnos en simplemente implementar la funcionalidad, y si da tiempo se mejora su “calidad”. Usar timebox es una forma efectiva de conseguir KISS y YAGNI, si tienes claro que tienes un límite de tiempo vas a lo práctico, y haces lo más simple que puede funcionar y te olvidas de cosas que no vas a necesitar.

Enganchando con la primera parte de este post, el desafío técnico del agilismo, consiste por un lado en entregar incrementos de funcionalidad, y por otro aceptar cambios en la funcionalidad y el contexto del proyecto. Por lo tanto en un proyecto gestionado de forma ágil, vamos a estar continuamente cambiando nuestra arquitectura, nuestro diseño y los componentes ya existentes. Esto es así, aunque no cambien los requisitos funcionales, al tener que modificar el software ya existente para poder acomodar al menos la siguiente funcionalidad a entregar. Si hay cambios funcionales, el ritmo de cambio a absorber por nuestra arquitectura es mayor aun. Por lo tanto nuestro diseño y nuestros componentes deben ser maleables y flexibles. Cualquier técnica o principio de diseño, que recorte los gastos o el tiempo de implementar un cambio, rendirá beneficio en un proyecto ágil.

Por lo tanto ya tenemos una regla para guiarnos a la hora de tomar rápidamente decisiones de diseño. Podremos aplicar una técnica y/o refactorización, siempre y cuando:

  • Podamos aplicarla si no nos vamos a saltar el timebox de la tarea a implementar. Esto asegura que no tiene un coste alto, y no nos desvía de nuestro objetivo de hacer software que funcione.
  • Tenemos claro que consigue hacer que nuestro diseño sea más sencillo de cambiar en un futuro.

La regla anterior tiene una debilidad, la posibilidad de que el timebox sea irreal y demasiado corto para realizar la tarea. En este caso nunca tienes tiempo para refactorizar ya que estas un ritmo de trabajo insostenible, y tu diseño puede hacerse frágil ante el cambio. Por lo tanto tengo otra regla que tiene más precedencia que la anterior: si te cuesta cada vez más encajar una nueva funcionalidad en el diseño, refactoriza. Esta regla actúa como una alarma que me indica que debo “complicar” el diseño, sí o sí, ya que éste no es lo suficientemente flexible para aceptar cambios.

Resumiendo mi postura,la calidad del software no existe en si misma. Lo que realmente nos importa es terminar en tiempo y dinero con software que funcione. En un contexto ágil esto va a implicar que tu software debe ser maleable y aceptar cambios de forma rápida y barata. Olvídate de métricas, documentación y estándares. Céntrate en que tu software funcione y acepte cambios fácilmente. Me gusta llamar a esto calidad mundana del software, en contraste con la idea de que la calidad es un ideal a alcanzar como objetivo. Para obtener suficiente “calidad mundana”, pero nada más, recopilando lo dicho anteriormente, yo suelo usar las siguientes reglas:

  • Usar timebox para centrarme en implementar funcionalidad y no salirte de costes y tiempos. Puedo usar distintos timebox (pomodoros, jornadas y sprints) para distintos ámbitos (tareas, escenarios y Product Backlog Items). Esto promueve KISS.
  • Si sobra algo del timebox, cambia tu diseño para que admita cambios de forma más sencilla. En función del nivel del timebox puedes afrontar cambios más locales o más globales en diseño. Por ejemplo, en un pomodoro puedes intentar un refactor local, en un sprint, proponer a tu equipo alguna tarea de review de código o de rediseño de un subsistema.
  • Si detectas, de forma empírica (nada de corazonadas o instinto), que cada vez es más difícil aceptar cambios, entonces tu arquitectura es frágil ante el cambio. Da la alarma y prepárate para refactorizar.
  • Si no tienes problemas céntrate en producir funcionalidad. Olvídate de posibles futuros problemas que pueden no ocurrir nunca (YAGNI). Al fin y al cabo la calidad cuesta tiempo y dinero, si no necesitas más, ¿para que comprarla?

Bueno, este es mi enfoque personal. Como veis es muy cauto e intento no complicar el diseño hasta que no es realmente necesario. Llegué a esta forma de trabajar tras darme cuenta de que tenía tendencia a “chapar en oro” mi software, y hacer programación barroca. Me di cuenta que esto es un defecto y hay que balancear muy bien el esfuerzo que se dedica a mejorar el código contra el que dedicas hacer funcionalidad. Hay que llegar a un sano equilibrio.

Para finalizar, si tu proyecto no es ágil, olvídate de todo esto. En los proyectos no ágiles, el cambio es algo malo, que hay que penalizar, no aceptar. Por lo tanto no tiene sentido diseñar para aceptar el cambio. Como mucho tendrá sentido diseñar para cumplir el plan. Ahora empiezo a entender, porque algunos principios como YAGNI, KISS, SOLID o DRY no se han aplicado como debían en la industria, porque simplemente en un proyecto no ágil no tienen mucho sentido.

¡ Salud a todos y felices fiestas !

Read Full Post »


Como el post anterior sobre contratos cerrados me quedó un poco abstracto, en este voy a poner un ejemplo, con el que espero ser capaz de clarificar mi idea. Suponed que existen dos versiones de vosotros en dos mundos paralelos. En un mundo, el mundo verde, usáis scrum, y tratais de entregar funcionalidad real al final de cada sprint. En el otro mundo, el mundo rojo, usáis una metodología iterativa tradicional, donde al final de cada iteración entregais artefactos correspondientes a entregables de la cascada (diseño, análisis, capa de persistencia, etc). En ambos mundos el cliente os contrata para hacer el mismo proyecto, bajo contrato cerrado y con las mismas condiciones.

La siguiente figura ilustra lo que ocurre si aprovechando vuestras capacidades CMMI5, conseguís hacer una planificación perfecta durante la preventa y terminais el proyecto en tiempo, dinero y alcance según lo planificado en el contrato cerrado. Podéis observar que aunque en ambos proyectos se ha tenido un éxito rotundo, en el mundo verde se ha ido entregando funcionalidad desde antes. Pero, como el proyecto ha sido exitoso eso sólo son detalles que le importan a los frikies.

Entrega continua de funcionalidad al usuario

Proyecto exitoso (un mundo perfecto)

En el gráfico anterior, se ha medido la entrega de funcionalidad con respecto al paso del tiempo. Sin embargo no todas las funcionalidades son igual de importantes para el cliente. A la importancia de una funcionalidad en concreto lo llamamos valor. En scrum la funcionalidad se entrega por orden de ROI. El ROI es el balance entre el valor de una funcionalidad y su coste estimado. Cuanto más valor más ROI; cuanto menos coste más ROI. Sin embargo, en la práctica, las diferentes funcionalidades se van analizando, refinando y subdividiendo, hasta que éstas tienen un tamaño tal que nos permite, de forma razonable, implementarlas en un único sprint. Debido a esto las funcionalidades que se entregan al final de cada sprint tienen un tamaño similar. En este sentido, y de forma grosera, podemos tratar el factor coste dentro del ROI como una constante. Por lo tanto, de forma aproximada, Scrum entrega las funcionalidades con mayor valor primero, maximizando así el flujo de entrega de valor. Sin embargo en el mundo rojo la funcionalidad se entrega en cuanto se puede. El orden de entrega lo define, no el valor, sino el orden en el que se planificó (cascada) la entrega de todos los artefactos que componen una funcionalidad dada. En este sentido el mundo rojo entrega funcionalidad sin atender al valor, con lo cual el valor de cada entrega será “aleatorio”. Si en vez de medir funcionalidad entregada, medimos valor o utilidad entregada, el gráfico anterior se transforma en el siguiente:

Entrega de valor al usuario

Valor entregado en un proyecto exitoso (un mundo perfecto)

De nuevo ambos proyectos tienen éxito, pero en el segundo gráfico se detecta una diferencia aun mayor de entrega de valor entre los dos mundos. De nuevo esto es una curiosidad frikie.

Sin embargo podría haber ocurrido otra cosa. Ciertamente usando CMMI5 y nuestros mejores algoritmos de planificación y estimación, nuestro plan para el contrato cerrado es impecable y perfecto. Sin embargo el mundo es muy cruel, y el rival de nuestro cliente se adelanta y saca antes que nosotros un producto que es directa competencia del nuestro. El cliente desesperado decide salir a producción cuanto antes, con lo que haya, para minimizar perdidas debido a un “time to market” tardío. Lo siento chic@s, el negocio manda. Supongamos que esto ocurre en la iteración 7, veamos el gráfico.

Entrega de valor si entramos en producción inesperadamente

La competencia se nos adelantó (esto es más realista)

¡ Oh, qué pena ! Si la competencia hubiera tardado una iteración más, el mundo rojo hubiera podido entregar un 65% de valor. Desgraciadamente los muy desalmados decidieron sacar el producto a mitad de la iteración 7 y pilló al equipo del mundo rojo integrando la capa de persistencia con el gestor transaccional y haciendo el “documento de diseño de algoritmos concurrentes” de entrega obligatoria en esa fase de la cascada. Sólo pudieron entregar un 10%. En el mundo verde también hemos fracasado, pero al menos conseguimos entregar un 70% de funcionalidad. Desgraciadamente el “documento de diseño de algoritmos concurrentes” está en blanco y no tiene ni índice, que pena más grande, el señor auditor de calidad y responsable de metodología nos va a reñir.

Olvidémonos ahora de la competencia, supongamos que todo lo anterior fue una pesadilla y que realmente tenemos todo el tiempo acordado para entrar en producción, al fin y al cabo, es un contrato cerrado, ¿no? Lo que nos ocurre ahora es que le mentimos al cliente, para ganar el proyecto tiramos los precios y nuestros recursos no son tanto como los que dijimos. O tal vez no, tal vez simplemente no comprendimos tan bien los requisitos como pensamos y estimamos mal. O quizás el tiempo y el precio nos venían impuestos y pensamos que eran asumibles, pero no lo pensamos mucho. Estos escenarios, donde la planificación y estimación inicial no se corresponden con la realidad es el caso más común. Desafortunadamente el contrato cerrado nos impide renegociar tiempo y dinero y por eso constituyen un riesgo tanto para el cliente como para nosotros.

El siguiente gráfico ilustra una situación parecida:

Entregas de valor en proyecto cerrado con mala estimación

Nos hemos colado en la planificación (como siempre)

De nuevo scrum se centra en maximizar el flujo de valor entregado, con lo que conseguimos en este ejemplo un 60% de valor. El mundo rojo no se centra en el valor sino en artefactos con lo que el valor entregado es sólo el 35%. La hemos pifiado en los dos mundos, pero de nuevo insisto, scrum falla mejor.

A qué mundo preferís pertenecer, ¿al verde o al rojo? Espero haber dejado clara mi postura. Por supuesto los gráficos son ficticios.

Pero no cantéis victoria. Scrum sólo os dice que tenéis que entregar valor y funcionalidad al final de cada sprint, pero no cómo. El cómo conseguir esto es algo muy duro y complicado, y Scrum no te ayuda en nada en este aspecto. Necesitareis un equipo con talento y que sepa construir software de manera ágil. Esto parece material para otro post.

Read Full Post »

Older Posts »

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

Únete a otros 42 seguidores