Sunday, April 21, 2024

Eliminar desperdicios en el desarrollo de software

En nuestro primer post, exploramos los orígenes y los principios fundamentales del Lean Software Development. En el segundo, introdujimos ciertos conceptos básicos que usaré durante toda la serie. Ahora, nos enfocaremos en el primero de estos principios: Eliminar el desperdicio. Es clave entender y reducir las actividades que no añaden valor para optimizar nuestros procesos de desarrollo y aumentar el valor que entregamos a nuestros clientes.

En el articulo describiré ejemplos y prácticas que he utilizado en diversos equipos ágiles desde hace años y es importante notar que las prácticas y ejemplos que mencionamos son específicos de nuestro contexto, como el desarrollo de producto y los equipos empoderados, y que suelen reforzarse mutuamente. Por ello, no es recomendable implementarlos de forma aislada. Por ejemplo, iniciar despliegues continuos sin un sistema de pruebas automáticas adecuado podría ser más perjudicial que beneficioso.

Adaptando los Principios del Lean Manufacturing al Desarrollo de Software

En el Lean Manufacturing original, se identificaban siete tipos principales de desperdicio: Inventario, Procesamiento extra, Sobreproducción, Transporte, Esperas, Movimientos y Defectos. Mary y Tom Poppendieck, basándose en su extenso conocimiento del Lean y del desarrollo de software, adaptaron estos conceptos para hacerlos más relevantes en este nuevo contexto. Por ejemplo, redefinieron el concepto de "Inventario" como "Trabajo parcialmente hecho", el “Procesamiento extra” como “Proceso extra”, la "Sobreproducción" como "Features extras", y el "Transporte" como "Cambio de tareas". Consideraron que los demás tipos de desperdicio mantienen su aplicabilidad directa en el desarrollo de software.



Identificando y Eliminando el Desperdicio

Para poder eliminar el desperdicio, el primer paso es capacitar al equipo sobre cómo identificar lo que constituye desperdicio. En este sentido, es importante:

  • Analizar desde la Perspectiva del Cliente: Siempre debemos preguntarnos si una actividad añade valor al cliente/usuario y si podemos eliminarla sin afectar a su percepción.
  • Fomentar una Cultura de Crítica Constructiva: Ser críticos con nuestras acciones y métodos permite al equipo analizar periódicamente su manera de trabajar para identificar y eliminar el desperdicio.
  • Considerar el Impacto a Largo Plazo: Es vital distinguir entre lo que puede parecer un desperdicio en el corto plazo pero no necesariamente lo es en el mediano o largo plazo, siempre con la satisfacción del cliente/usuario en mente.

Es fundamental clasificar los desperdicios identificados en dos categorías: aquellos que son necesarios por razones específicas, como regulaciones o leyes, y aquellos que podemos eliminar completamente o en parte sin comprometer la satisfacción del cliente/usuario. En el caso de los primeros, debemos enfocarnos en entender el motivo detrás de estas limitaciones para poder minimizar el desperdicio lo máximo posible. Para los segundos, es crucial adoptar una actitud más decisiva, buscando eliminarlos de manera sistemática.

Trabajo Parcialmente Hecho

En Lean Manufacturing, el inventario de piezas a medio hacer es físicamente visible y requiere organización y, en ocasiones, mantenimiento. A diferencia de esto, en nuestro contexto el "inventario" (código, conocimiento, información, análisis, etc.) no es tan visible, pero es igualmente costoso.

El valor real para el cliente o usuario surge únicamente cuando este accede a la nueva funcionalidad o cambio. A menudo, incluso en ese instante, el valor sigue siendo incierto hasta que recibimos retroalimentación. Por lo tanto, dado que el valor verdadero solo se potencializa en la etapa final, es crucial acortar el tiempo desde la concepción de la idea hasta su entrega. En otras palabras, debemos esforzarnos por reducir el trabajo en progreso y disminuir el lead time (tiempo de entrega). Como dice Dan North, el objetivo es minimizar el intervalo entre la idea inicial y el “gracias” del usuario.

Estas son las prácticas que usamos para eliminar el trabajo parcialmente hecho:

  • Analizamos/Preparamos trabajo en el backlog bajo demanda: Mantenemos el backlog para no más de un mes y, si crece, eliminamos iniciativas. Si es importante, volverá a surgir.
  • Vertical slicing radical: Tanto a nivel de producto como técnico, permitiéndonos desplegar incrementos en pocas horas o un día. Esto por supuesto requiere hacer CD / Entrega Continua.
  • Trunk Based Development: Evitamos trabajo parcial en feature branches y los problemas de merge.
  • Gestión extremo a extremo por nuestro equipo: Realizamos despliegues, validamos la calidad, monitorizamos el producto, etc. Evitando el tiempo de espera por otros equipos o especialistas.
  • Despliegue inmediato de mejoras: Tanto mejoras de usuario, que ofrecen feedback de negocio, como técnicas, que aportan feedback del sistema.

Como se muestra en el Diagrama de Flujo Cumulativo del equipo, mantenemos un backlog mínimo y gestionamos las tareas relacionadas solo cuando es necesario y en la menor cantidad posible.




Proceso Extra: Simplificación hacia el Valor

Alineándonos con el Manifiesto Ágil, Lean Software Development promueve que el avance principal se mida por el software de valor entregado al cliente. En este marco, cualquier elemento —como documentación excesiva, procesos redundantes, reuniones innecesarias o aprobaciones— que no contribuya directamente al valor para el cliente/usuario, debería ser evaluado para su eliminación.

Desde que adopté los principios ágiles, he colaborado con equipos para depurar nuestros procesos, descartando lo que no generaba valor. 

De esa experiencia destaco dos cambios significativos:

  • Transición de Scrum a Kanban: Scrum nos ayudó inicialmente, pero evolucionamos hacia Kanban para enfocarnos en un flujo de trabajo continuo. Esto redujo las reuniones y planificaciones extensas, favoreciendo sesiones más breves y enfocadas, adaptándonos al modelo Just In Time caracteristico de Lean.
  • Eliminación de Estimaciones: Priorizamos cambios pequeños y continuos, lo que nos permite prescindir de las estimaciones tradicionales. Aún hacemos estimaciones de alto nivel para grandes iniciativas, pero con un enfoque en minimizar el riesgo y la inversión de tiempo innecesaria.

Hemos descubierto que al trabajar en pasos pequeños y preparar solo lo inmediatamente necesario, minimizamos el re-trabajo (failure demand), puesto que no hacemos trabajo “especulativo”. Esto simplifica enormemente la priorización y gestión del backlog, permitiéndonos concentrarnos en lo esencial y ahorrar esfuerzos considerables.

En resumen: Adoptar un enfoque de hacer solo lo necesario y justo a tiempo (Just-In-Time) nos ha llevado a un proceso más eficiente, con menos retrabajo y una gestión de prioridades y backlog más ágil.


Aunque es imposible eliminar toda la documentación o burocracia asociadas a regulaciones y certificaciones de seguridad, es posible abordar estos requisitos de manera creativa para evitar trabajo adicional. En nuestro caso, adoptamos el desarrollo basado en Trunk (Trunk Based Development). Cada commit o push incluye a los coautores y desencadena una serie de pruebas exhaustivas. Esta metodología no solo satisface a los auditores sino que, de hecho, resulta ser más efectiva que los métodos tradicionales de revisión asincrónica (feature-branching + PRs) y la necesidad de aprobaciones explícitas para avanzar hacia la producción.


Funcionalidades y Código Extra

Este para mi junto con el trabajo parcialmente hecho son el desperdicio más significativo que tenemos en desarrollo de software y productos. Demasiado a menudo, software desarrollado durante meses termina sin usarse o es evitado por los usuarios por no cumplir sus expectativas. Este es el DESPERDICIO en el desarrollo de software. Como dijo Mary Poppendieck, "The biggest cause of failure in software-intensive systems is not technical failure; it's building the wrong thing."

Además de aplicar Lean Software Development, debemos utilizar otras técnicas para realmente entender las necesidades de nuestros usuarios y descubrir qué problemas merecen ser solucionados. Herramientas como Lean Product Management, Continuous Discovery e Impact Mapping son fundamentales en este proceso, aunque no las detallaremos en esta serie de artículos.

Suponiendo que hemos identificado un problema digno de ser solucionado y que tenemos clientes/usuarios con una necesidad clara, nuestro objetivo es resolver este problema/necesidad con la menor cantidad de software posible y lo más rápidamente que podamos. En nuestro caso, usamos las siguientes practicas:

  • Adoptamos el principio ágil de "La simplicidad, o el arte de maximizar la cantidad de trabajo no realizado, es esencial."
  • Vemos el software como un medio y no como un fin, buscando resolver las necesidades con la menor cantidad de software y tecnología posible.
  • Nos enfocamos en el valor para el cliente, asegurándonos de que cada iniciativa y funcionalidad se alinee con las necesidades reales de los usuarios.
  • Postergamos las decisiones técnicas y de producto tanto como sea posible, para aumentar las posibilidades de nunca tener que implementarlas o por lo menos no implementarlas en su totalidad. Siempre buscamos la versión mínima que nos es suficiente.
  • Empleamos Outside-In TDD, lo que asegura que solo hacemos el código mínimo necesario para implementar el caso de uso.
  • Seguimos el principio de YAGNI  (You Aren't Gonna Need It), centrados en la funcionalidad que es requerida ahora, sin caer en diseño o desarrollo especulativo.
  • Y cuando algo que hemos desarrollado deja de usarse o no cumple con su objetivo, lo eliminamos por completo o lo adaptamos hasta que vuelva a tener un impacto positivo.
  • Trabajamos en pasos muy pequeños (<1.5-2 días), presentando nuevos incrementos a los usuarios para obtener retroalimentación rápida que nos permite adaptarnos y decidir los siguientes pasos. Esto, a menudo, nos permite detener la inversión en una funcionalidad de la aplicación cuando es "lo suficientemente buena" para el usuario, evitando así desarrollos innecesarios.


Cambios de tarea

Cambiar de tarea frecuentemente puede interrumpir significativamente la productividad de un equipo. Cada cambio obliga a reiniciar el proceso mental, retrasando el reingreso al estado de "flujo" de trabajo. Para minimizar estos cambios, aplicamos varias estrategias:

  • Minimizar el WIP: La estrategia más efectiva para prevenir cambios de tarea frecuentes es reducir el Trabajo en Curso (WIP) a nivel de equipo. Nos esforzamos por concentrarnos en una, o máximo dos, iniciativas simultáneamente. El ensemble/mob programming es nuestra técnica predilecta para limitar el WIP, ya que cuando todo el equipo se enfoca en una única tarea, las interrupciones internas se eliminan naturalmente.
  • Revisiones de código continuas y sincronas:  Trabajando en ensemble/mob programming se eliminan todos los cambios de tarea que se generan cuando las revisiones de código son asíncronas. Ver Code reviews (Synchronous and Asynchronous) 
  • Vertical Slicing y Technical Slicing: Aplicando rigurosamente estas técnicas, podemos trabajar en incrementos realmente pequeños. Esto nos ayuda a preservar el flujo de trabajo hasta completar y desplegar un incremento. Naturalmente, después de cada despliegue, surge la posibilidad de cambiar de tarea sin el impacto negativo que implicaría hacerlo en medio de un incremento
  • Completitud de Tareas y Spikes: Nos aseguramos de que las tareas se pueden completar de principio a fin. Si vemos que no es posible hacemos un spike (http://www.extremeprogramming.org/rules/spike.html) para eliminar la incertidumbre o buscamos otras aproximaciones que no requieran interrumpirlas.
  • Técnica Pomodoro: Usamos Pomodoros para periodos de trabajo concentrado y descansos sincronizados del equipo.
  • Calidad a Todos los Niveles: La alta calidad previene interrupciones por fallos. Aplicamos TDD, ensemble/mob programming y otras prácticas de Extreme Programming para mantenerla.
  • Rotaciones en Operaciones/Soporte: Para equipos con funciones de soporte, implementamos rotaciones, concentrando a parte del equipo en trabajo emergente y al resto en iniciativas planificadas.

Esperas

Cuando analizamos en profundidad el proceso de desarrollo de producto, lo más usual es encontrar que cada incremento/idea/backlog item pasa casi todo el tiempo esperando. Esperando para tener respuestas a preguntas, esperando analizar más profundamente el problema, esperando tener feedback sobre el diseño, esperando la aprobación de los cambios de arquitectura, esperando que ciertos especialistas estén disponibles, esperando que alguien apruebe el cambio, esperando que se hagan revisiones de código, esperando que se active el feature toggle, esperando comunicar el cambio... Esperando, esperando, esperando. Evidentemente, si vemos el proceso desde el punto de vista del cliente/usuario, cualquier tipo de espera es simplemente desperdicio.

Para eliminar gran parte de estas esperas, estas son algunas de las tácticas que nos han funcionado en el pasado:

  • Asignar al equipo la responsabilidad de principio a fin para ir desde la definición del problema hasta la puesta en producción y operación de la solución. Y, si es posible, incluso darle la libertad de encontrar el problema que merezca la pena resolver. Esto implica encargarse de la gestión del producto, el desarrollo, la calidad, el despliegue y las operaciones.
  • Incluso aunque el equipo esté empoderado, a veces no dispone de todos los skills necesarios. En esos casos, tenemos que conseguir la colaboración de algún especialista, pero intentar siempre que el especialista nos ayude a mejorar nuestros skills en esa especialidad en vez de resolver el problema por nosotros. Esto no cubrirá todos los casos, pero hará que en los casos más básicos no necesitemos volver a requerir al especialista.
  • Por otro lado, cuanto más multidisciplinar sean los miembros del equipo, más fácil será cubrir las necesidades dentro del propio equipo. Esto no implica que todo el mundo sepa de todo, pero sí que fomentemos los perfiles en T (https://es.wikipedia.org/wiki/Habilidades_en_forma_de_T ).
  • A nivel técnico, la forma de eliminar sistemáticamente la mayor parte de las esperas es avanzar hacia la Entrega Continua / CD, lo que normalmente implica poner bastante peso en las prácticas técnicas ágiles (TDD, CI, Despliegue separado de Activación, etc.) y tener una confianza muy alta en nuestro testing automático.
  • Una de las formas más eficientes (eficiencia de flujo) es trabajar en mob/ensemble programming de forma que todo el conocimiento y los skills disponibles estén a la entera disposición de la única iniciativa en curso (en la que trabaja el mob/ensemble).
  • De nada sirve salir pronto a producción si luego permanecemos de forma pasiva esperando la retroalimentación del cliente/usuario. Es mucho más eficiente buscar esa retroalimentación de forma proactiva y disponer de instrumentación a nivel de producto y sistema para aprender lo antes posible.

Movimiento

Otro de los siete desperdicios básicos que considera Lean Manufacturing es el movimiento. En este contexto, resulta evidente que los movimientos que un operario debe realizar en una fábrica, ya sea entre máquinas, para recoger material o para hacer consultas, constituyen un claro desperdicio. En el caso de Lean Software Development, se decidió mantener también la categoría de movimiento, aunque en nuestro ámbito, este tipo de desperdicio no sea tan directo y evidente. 

En el libro original, el movimiento se refiere al esfuerzo necesario para acceder al cliente, obtener información del dominio, realizar hands-offs entre especialidades (ops, QA, seguridad), etc.

Muchas de las tácticas empleadas para eliminar las esperas también son válidas para reducir el desperdicio de movimiento, especialmente en lo que respecta a los handoff entre especialidades.

Adicionalmente, para eliminar otros tipos de movimientos, nos han resultado útiles las siguientes estrategias:

  • Proporcionar al equipo acceso directo al cliente/usuario final o a su representante más cercano. Una solución práctica puede ser asumir la responsabilidad de las operaciones, de manera que el equipo esté directamente expuesto a las quejas y necesidades de los clientes/usuarios.
  • Desarrollar herramientas específicas que permitan acceder de forma directa y eficiente a la información necesaria, evitando repetir procesos (por ejemplo, herramientas para extracción de datos, observabilidad, etc.).
  • Crear irradiadores de información para que sea fácil visualizar los avances o información relevante sin necesidad de buscar activamente (pizarras de gestión visual, notificaciones automáticas, etc.).

Defectos

Por último, Lean Software Development considera los defectos como una gran fuente de desperdicio. Desde mi experiencia, diría que los defectos son el segundo desperdicio más importante después de realizar actividades que no son necesarias (funcionalidades/código extra). Aunque, bien mirado, podríamos considerar que no hacer lo que necesita el cliente es también un tipo específico de defecto :).

Cada defecto que cometemos no solo genera el desperdicio del tiempo invertido en crear ese código incorrecto, sino también el tiempo dedicado a solucionarlo, el impacto en nuestra credibilidad ante el cliente/usuario y todo el esfuerzo desde la creación del problema hasta su resolución. Por lo tanto, no es solo importante evitar generar defectos, sino también encontrarlos cuanto antes, ya que el desperdicio/coste asociado aumenta de manera exponencial cuanto más tiempo tardamos en detectar el problema.

Con esto en mente, las tácticas y prácticas que solemos usar para minimizar este desperdicio son:

  • Minimizar en todo lo posible el código que necesitamos desarrollar para conseguir el impacto deseado. Como bien sabéis, menos código implica menos posibilidades de cometer errores.
  • Utilizar Outside-In TDD, comenzando por tests de aceptación del caso de uso. Esto, por definición, genera la mínima cantidad de código posible que además está bien testeado desde su creación.
  • Este proceso no cubre todos los escenarios y problemáticas, por lo que también es necesario crear ciertos tests de extremo a extremo y tener estrategias para temas específicos como análisis de seguridad, test de carga, test de rendimiento, etc.
  • Otro punto importante es probar los componentes de terceros que usamos para evitar problemas cuando actualizamos versiones o los utilizamos de manera diferente a la habitual. Ver Thin Infrastructure Wrappers.
  • Con todos los puntos anteriores tenemos un buen punto de partida, pero cada vez es más común depender de infraestructura y servicios de terceros (SaaS, clouds, etc.). En esos casos, se hace más imprescindible que nunca utilizar tácticas de testing en producción. Al fin y al cabo, a nuestros clientes/usuarios les da igual cuál haya sido la fuente del problema; solo les importa el impacto que ha tenido.


Conclusiones

Como se puede observar en las tácticas y prácticas que empleamos para minimizar el desperdicio, muchas de ellas se relacionan con tener prácticas de desarrollo sólidas (pair programming, TDD, BDD, revisiones continuas, CD, CI, etc.) que nos permiten desarrollar de manera sostenible. Otras se centran en evitar en la mayor medida posible realizar tareas innecesarias, enfocándonos en lo que realmente valora el cliente (que no siempre coincide con lo que pide), limitando el software a las necesidades actuales y trabajando en pasos muy pequeños para poder cambiar de dirección o cesar la inversión en algo tan pronto como sea necesario.

Trabajar en estos pasos tan pequeños y adaptarnos de forma continua nos permite aligerar mucho el proceso necesario: necesitamos poca gestión del backlog si tenemos muy poco en él; no hay que coordinar distintos flujos de trabajo si todos trabajamos en lo mismo al mismo tiempo; no hay que estructurar la comunicación ni los handoffs con otros equipos si los gestionamos nosotros mismos. Al final, se trata de simplificar todo lo más posible para hacer solo lo absolutamente necesario, siempre centrados en lo que realmente aporta valor. Esto implica, evidentemente, cuestionarnos constantemente lo que hacemos y cómo lo hacemos. Que algo haya sido útil hace un par de meses no significa que siga siéndolo. No es tan sencillo como parece, ya que requiere una profunda implicación en nuestro trabajo (pasión) y, al mismo tiempo, la capacidad de dejar ir lo que no aporta (desapego). Es vivir centrados en una ventana deslizante de lo que aporta valor ahora, de lo que nos es útil en el presente.

Recuerda que eliminar el desperdicio es solo el primer paso en el camino hacia el Lean Software Development. En nuestro próximo post, exploraremos cómo "Amplificar el aprendizaje" para garantizar la excelencia de nuestros productos. ¡Nos vemos pronto!



No comments: