Tuesday, July 16, 2024

El coste basal del software

Traduccion del articulo original Basal Cost of Software
Traduccion realizada por https://x.com/simonvlc y publicada originalmente en su genial newsletter Estrategia de Producto: El Coste Basal del Software
---

En mi opinión, el coste de desarrollo de cada funcionalidad en un producto puede dividirse de la siguiente manera:

  1. El coste directo de desarrollo o coste inicial.
  2. Un coste semanal o mensual únicamente asociado con su existencia en el sistema. Haciendo una comparación con la Tasa Metabólica Basal del cuerpo humano, podríamos llamar a este segundo coste la Tasa Metabólica Basal o Coste Basal.

El Coste Basal se compone de dos partes diferenciadas:

  • El impacto directo en la capacidad del equipo debido a la complejidad añadida (nuevas dependencias, más código para entender, más posibilidades de bugs ocultos, etc.).
  • El impacto en el coste de desarrollo o evolución de otras funcionalidades debido a posibles incompatibilidades, acoplamiento, etc.

El coste inicial

Es el coste incurrido por el equipo durante el desarrollo inicial de la funcionalidad. Incluye el coste desde que el equipo comienza a trabajar en la funcionalidad hasta que el cliente la tiene disponible y comienza a usarla. Por supuesto, este proceso debería consistir en múltiples despliegues y lanzamientos parciales para obtener feedback y hacer los ajustes necesarios...

El Coste Basal

Tras el coste inicial de desarrollo, afrontamos también un coste continuo que reduce la capacidad del equipo para el desarrollo de nuevas funcionalidades (innovación).

Este coste, que continúa con el tiempo y solo termina con la eliminación de la funcionalidad o el fin de vida del producto, es el coste que paga el equipo por la existencia de ese código en el producto.

Es importante tener en cuenta que este no se refiere al coste de hacer modificaciones o corregir errores; se refiere al coste de simplemente tener el código allí...

¿Por qué se da este coste? ¿Por qué una funcionalidad que no evoluciona supone un coste? 

  • El equipo tiene que conocer ese código (dónde está, qué dependencias tiene, qué interactúa con él, cómo está diseñado...).
  • El conocimiento y las técnicas del equipo están en constante evolución. Cuando mejora en cualquiera de estos aspectos, deberá actualizar el código.
  • Cuando el equipo diseña una nueva funcionalidad, el código debe ser diseñado de manera que sea compatible con todas las anteriores y no genere problemas o regresiones. Y, por supuesto, este coste es proporcional a todas las que ya teníamos en el sistema.
  • Algo similar ocurre cuando un nuevo miembro se une al equipo. Este nuevo integrante debe aprender sobre todas las funcionalidades, por lo que el coste es proporcional a su número..

Y lo peor de todo, este sobrecoste es continuo hasta la "muerte" de la funcionalidad. Por ejemplo, hasta el fin de la vida del producto, hasta que ningún usuario la use, o hasta el fin del mundo (lo que ocurra primero).

Evolución del coste de una funcionalidad

Como hemos visto, el Coste Basal de una funcionalidad es más o menos constante durante su vida. Pero cada lenguaje, dependencia o tecnología que hayamos usado en su desarrollo, puede alcanzar un punto en el que ya no es utilizable por cualquier razón (dependencias obsoletas, problemas de seguridad, evolución normal que deprecia la versión del lenguaje que usamos, etc.). Desde este momento, el coste puede dispararse porque estamos obligados a actualizarlo, incluso aunque no queramos evolucionar la funcionalidad.

En resumen, el coste real puede ser representado por el siguiente gráfico:

El problema

Un error común es negar el Coste Basal y considerar que si no se hacen cambios, el coste de mantenimiento de una funcionalidad es cero. Supongo que esto proviene de utilizar metáforas de otras profesiones como la construcción, que no son aplicables al desarrollo de software.


La capacidad no es infinita

A pesar de que la capacidad de un equipo cambia (positiva o negativamente) con el paso del tiempo debido a distintos factores (conocimiento del negocio, técnicas, estrés…), esta es finita.

El Coste Basal acumulado de todas las funcionalidades de las que el equipo es responsable reduce la capacidad disponible para desarrollar nuevas funcionalidades (innovar).

Con el tiempo, podemos observar como la capacidad del equipo para innovar decae rápidamente:

Hasta el punto de que cuando la capacidad se agota, el equipo se encuentra en una situación en la que le es imposible innovar porque pasa todo su tiempo “manteniendo” las funcionalidades de las que es responsable.


O bien reduce su velocidad al desarrollar nuevas funcionalidades, acumulando coste basal, pero a menor ritmo.

Conclusiones

Las secciones anteriores destacan varios principios que debemos considerar para mejorar la eficiencia de nuestros equipos de producto.

  • Debemos minimizar el Coste Basal tanto como sea posible, logrando el impacto deseado con la menor cantidad de código posible.
    • Cuando sea posible, incluso sin añadirlo.
    • Iterando la funcionalidad, partiendo de una solución mínima, para adaptarla lo más posible a las necesidades del usuario.
    • Haciendo el software/diseño lo más simple posible. Debe ser fácil de evolucionar sin sobreingeniería (YAGNI).
  • Debemos eliminar cualquier funcionalidad del producto que no tenga el impacto deseado, eliminando así su Coste Basal.
  • Debemos monitorizar continuamente el código del equipo para detectar la obsolescencia de dependencias, lenguajes y tecnologías para evitar que el Coste Basal se dispare.

Veamos un ejemplo teórico sencillo para ayudar a transmitir el mensaje. Supongamos que un equipo acumula un 1% de Coste Basal por semana, lo que significa que, por cada 100 unidades de producto (código, características, mejoras), añade 1 unidad de Coste Basal. A medida que este se acumula, en 52 semanas (aproximadamente 1 año), el equipo solo podrá dedicar el 60% de su capacidad a nuevas funcionalidades/mejoras (innovación). En dos años, será apenas el 35%. Por supuesto, este ejemplo es una simplificación, pero destaca que negar este coste no es una opción viable.


Recordad: Simplificar — el arte de maximizar el trabajo no hecho — es esencial.

Relacionado y referencias

Otros artículos interesantes


Monday, July 01, 2024

Decidir lo más tarde Posible: Limites de Producto

Como mencionamos en el artículo anterior de la serie sobre Lean Software Development, continuaremos explorando técnicas que nos permiten tomar decisiones lo más tarde posible.

Empezamos por definir sistemáticamente Limites de Producto.

Al desarrollar un incremento de la solución que estamos implementando, es fundamental establecer límites concretos en todos los parámetros que puedan introducir complejidad. Esto nos permite centrarnos en lo que aporta valor ahora y posponer soluciones más sofisticadas, evitando el coste y la complejidad añadida. Con el tiempo, esos límites irán cambiando y nos obligarán a modificar la solución, pero con esta aproximación podremos decidir cada solución lo más tarde posible y evitar hasta ese momento el coste de desarrollo y evolución de esa solución más compleja.


Es crucial que al definir un límite, este se incluya en el código o solución para que, si se supera, la aplicación se comporte de manera controlada, avisando al equipo y, posiblemente, al usuario.


Ejemplos de límites que he usado:

  • Número de clientes/usuarios totales.
  • Número de clientes/usuarios concurrentes.
  • Tamaño máximo de ficheros que se pueden subir al sistema.
  • Cuotas por usuario (almacenamiento, requests, número de entidades, etc.).
  • Valores numéricos por cualquier concepto de negocio (del problema).
  • Tiempos de respuesta a las distintas peticiones.
  • Resoluciones/Dispositivos para la IU

Si no definimos claramente estos límites de manera numérica, estamos abriendo la puerta al diseño especulativo para resolver situaciones que aún no enfrentamos.


Ejemplos de uso de límites para "decidir lo más tarde posible"

Conocer y definir el número total de clientes en varias ocasiones me ha permitido ofrecer soluciones muy sencillas para la persistencia, que nos han sido útiles durante meses antes de necesitar cambios. Por ejemplo, si el número de usuarios es pequeño, usar persistencia en fichero y cargar la información a memoria es factible. También nos permite usar soluciones tipo SQLite y posponer la decisión de meter un motor de BD independiente.



Al limitar el tamaño de las solicitudes (en cuanto a volumen) y definir los escenarios de conexión, podemos ofrecer soluciones robustas y simples, procesando las solicitudes de forma síncrona. Esto permite posponer la necesidad de tener una solución asíncrona. Como ejemplo, en una ocasión teníamos que aceptar que el usuario subiese ficheros al sistema; la primera implementación solo permitía ficheros muy pequeños, esto nos permitió hacer una implementación sencilla y tener feedback muy rápido (menos de 2 días). Pasadas unas semanas, una vez que vimos que tenía sentido la funcionalidad, mejoramos la implementación para soportar ficheros más grandes.


En varias ocasiones hemos enfrentado situaciones donde cada usuario acumulaba almacenamiento (archivos/objetos). En estos casos, el definir un límite de producto para el total de almacenamiento para todos los usuarios, otro límite para cada usuario y otro límite para definir cuándo teníamos que empezar a preocuparnos por este problema, nos ayudó a posponer la implementación de cualquier medida de control y gestión de este almacenamiento hasta que se llegase a cualquiera de los límites definidos.



Para ilustrar el uso sistemático de estos límites con un ejemplo concreto, en Alea Soluciones lanzamos un nuevo producto de gestión/control de una red de fibra y los routers de los clientes finales en menos de 3 meses. Sabíamos que nuestros clientes en ese momento no tenían más de 1000-2000 usuarios. Sabíamos que los operadores del sistema de gestión no eran más de 2-3 personas concurrentes. Sabíamos que para tener más usuarios nuestros clientes tenían en muchos casos que desplegar fibra hasta la casa o al menos ir a la casa del usuario a instalar el router; esto implicaba que no era posible que creciesen a más de 2-5 usuarios por semana. Con este contexto, la primera versión del sistema inicial de gestión era un servidor web que procesaba todo de forma síncrona y con la información de usuarios en memoria y que simplemente persistía los cambios a un fichero cuando se necesitaba. Esto permitió que dedicásemos mucho tiempo adicional al resto de componentes del sistema (integración con cabeceras de fibra, sistema de configuración remota de los routers, sistema de monitorización de los routers, etc.). Evidentemente, el sistema fue evolucionando y fuimos mejorando ese sistema, pero siempre esperando al último momento responsable para cada decisión de introducir nueva tecnología.


Otro ejemplo sencillo es en ClarityAI, donde para crear un chat ops en Slack que ofreciera algunas capacidades internas de la plataforma, se definieron ciertos límites, tanto en tiempos de respuesta máximos como en volumen de información tratada. Al definir un tiempo de respuesta máximo alto (2s) pero menor del soportado por slack para la respuesta sincrona a comandos de slack (3s), pudimos posponer bastante tiempo la implementación de una solución asincrona. Esa aplicación trabaja con información de inventario de componentes técnicos (repositorios de código, repositorios docker, etc) y de equipos y personas. Vimos que nos era fácil definir unos maximos para cada uno de los elementos y vimos que en todos los casos el máximo estaba por debajo de 1000 elementos. Estos limites nos permitieron evitar bastante complejidad, simplemente apoyandonos en un backend NoCode (Airtable) como BD, que además nos proporcionaba un frontal de administración básico. Sabemos perfectamente que cuando superemos esos limites, tendremos que pensar en una solución más sofisticada y escalable, pero posponer esa decisión nos ha permitido desarrollar muy rápido en esa aplicación durante más de dos años y medio.


Recursos Relacionados: