Archivo

Calidad del Software

Llegar a producción da miedo. El área usuaria siempre tendrá el “síndrome de la última versión” y, por tanto, querrá incluir toda la funcionalidad que puedan y pondrán los umbrales de aceptación muy altos.

A los desarrolladores también les entra el pánico porque por muchas pruebas que se hayan realizado al producto es en producción donde se pasa el examen final, todo lo anterior son exámenes parciales que si bien hay que superarlos no tienen más trascendencia (y no con ello quiero quitale importancia) que crisis puntuales en el proceso de desarrollo.

Si hay presión en el desarrollo, no es comparable con la que se recibe si el producto llega a producción y es un desastre.

Esta situación puede eternizar el paso a producción en muchos proyectos de desarrollo de software.

Ante esto hay que reflexionar (usuarios y desarrolladores) sobre las características del sistema de información que vamos a poner en producción, si se trata del software de una central nuclear o un software crítico que puede poner en riesgo vidas humanas, la salud de las personas, el medio ambiente o la continuidad de la organización se puede entender que toda precaución es poca, pero fuera de ese círculo (que es la inmensa mayoría de los sistemas que se desarrollan) hay que evaluar el coste real que implica retrasar sistemáticamente un paso a producción por el intento de alcanzar una perfección que nunca se va a conseguir.

Ya lo dice Kent Beck: “No estar en producción es gastar dinero sin hacer dinero”.

Sabemos que la mayoría de los defectos, incidencias o malas prácticas de desarrollo tienden a ser más costosos de corregir conforme pasa el tiempo, ya que van quedando, cada vez, más imbricadas dentro del código.

A eso hay que sumarle su impacto sobre la versión del producto que está en producción y sobre la mantenibilidad del sistema.

Por ese motivo, se considera una buena práctica corregir el defecto lo más cerca posible de su origen. Para ello primero hay que detectarlo y después encontrar el momento más adecuado para resolverlo, pero, ¿no es la mejor solución corregirlo cuanto antes?, sí, pero no es tan simple.

Supongamos que hemos detectado un defecto en una de las funcionalidades del sprint, que la misma no resulta crítica para el sistema y que su resolución requeriría de un esfuerzo que comprometería el resto del sprint, ¿se debe corregir ahora o mejor lo dejamos para uno de los próximos sprints?, ¿se incluye la funcionalidad con esa incidencia conocida en la entrega o no se pasa la funcionalidad completa?.

Hay que analizar la situación y solicitar la opinión del responsable funcional, no hay soluciones que valgan para todo. Sí que es cierto que corregir el problema cuanto antes evita que la grieta se ensanche, que estudiar la causa del mismo puede prevenir de otros fallos similares, también es cierto que todas las incidencias no son iguales y no impactan de la misma manera y todos esos factores son necesarios tenerlos en cuenta para seleccionar el momento más apropiado para realizar la corrección.

El desarrollo de software debe buscar la satisfacción de las expectativas del cliente. De lo que nos aproximemos a ese hito, será lo cerca o lejos que estaremos del cumplimiento de los objetivos.

Hablo de objetivos relacionados con la calidad del producto. La calidad técnica, es importante, muy importante, pero para el usuario está en un segundo plano, respecto a si el producto hace lo que espera y le permite trabajar de manera productiva.

Por ese motivo se dice que la calidad tiene dos puntos de vista, el del usuario y el del desarrollador. Es cierto que existe esa dualidad pero también que no se debe entender la una sin la otra.

Un producto software siempre es susceptible de ser evolucionado. Habrá un motivo para ello y el resultado debería ser un incremento del valor del mismo siendo proporcional al esfuerzo (coste) invertido para conseguirlo. De aquí surge otro objetivo: conseguir el mayor valor posible invirtiendo el menor esfuerzo posible.

Si conseguimos ganar valor, ahorrando esfuerzos, tendremos la posibilidad de seguir evolucionando el producto para así conseguir generar más valor. Es algo que debemos tener siempre presente, pero que no debe llegar a obsesionarnos porque llegado a un punto puede dar lugar a situaciones en las que se ponga muchos reparos a especular en el desarrollo, algo que a veces es inevitable porque el usuario no siempre tiene claro lo que quiere y necesita ver ejecutada su especificación para valorar si su idea es buena o si por el contrario necesita ajustes.

Tener presente ese rendimiento de la inversión obliga a tener en cuenta la calidad técnica en los desarrollos (y por ahí es donde se puede y se debe trabajar este asunto con el cliente), rematar las tareas, cumplir los compromisos, reducir la tasa de errores y desarrollar con intención.

Con la intención se pretende minimizar el número de iteraciones. Es cierto que intención y especulación son dos términos que no se llevan bien, pero no son incompatibles, de hecho no se utilizaría el término intención si se tuviera la certeza de que todas las decisiones que se toman son correctas. Se trata, por tanto, de trabajar con el usuario, para incrementar la probabilidad de éxito: historias de usuario con prototipado de pantallas tomando como referencia versiones ya liberadas del producto, de las funcionalidades más prioritarias seleccionar aquellas que se tenga más clara su operativa de uso, etc…

Esto es desarrollo de software, no el pozo de los deseos, si bien, no se trata tanto de intentar que el producto llegue a producción sin errores como de tenerlo como objetivo, ¿por qué? se trata de tener los pies en el suelo y mirar un poco para atrás para darnos cuenta de lo difícil que resulta que no se cuele ningún bug, por eso, sin que se convierta en obsesión, es muy importante poner los medios necesarios para minimizar esas incidencias y si se ha trabajado de manera óptima conseguir que la tasa de errores tienda a cero.

Es muy importante tener en cuenta el tipo de software que estemos desarrollando y lo crítico que puede resultar un fallo, por ese motivo, esas prácticas deben modularse a esa circunstancia, debiéndose dotar al proyecto de los recursos necesarios para tratar de garantizar su calidad.

Hablo de modulación, de adaptación a lo que el sistema necesita en función de sus características. Si nos quedamos cortos puede ser fatal, si nos pasamos mucho no estamos siendo eficientes.

Que el sistema llegue a producción con pocos errores (objetivo cero defectos) es importante no solo en términos de confianza por parte del cliente o por el hecho de que facilite la implantación y puesta en marcha del sistema, sino por el coste adicional que tiene arreglarlos y el impacto que tiene en la evolución del producto, ya que obliga a destinar personal específicamente para la realización de estas tareas, todo eso sin contar con el coste que tiene para el cliente tener parado o en precario un proceso que tiene informatizado.

Si el error es crítico y no es sencillo de arreglar, puede parar incluso la línea principal de desarrollo del producto (la que tiene como objetivo incrementar su valor en cada iteración) para centrar los esfuerzos en resolver el problema, por lo que los costes se seguirán disparando.

La sobreproducción se entiende como la realización de trabajo de más que no resulta necesario en estos momentos. Tiene las siguientes implicaciones:

- Esfuerzo realizado que tal vez no sirva para nada, ya que una vez que el cliente evalúe lo que realmente necesita puede modificar total o parcialmente todo el conjunto de funcionalidades de más que se han realizado.

- División de la atención entre lo realmente importante y lo que no lo es. Ese esfuerzo que se dedica a lo que no es prioritario puede afectar a la calidad, al grado de terminación y a la tasa de errores de lo que sí lo es.

- El usuario, cuando se le presenta la solución en su conjunto (en su versión actual), también pierde el enfoque pudiéndose perderse en detalles que están lejos del objetivo principal de la versión.

- Incremento de la complejidad del sistema tanto a nivel técnico (deuda técnica) como de la usabilidad.

Trabajo de más es resistencia de más para poder adaptarnos al cambio, ya que nos resta flexibilidad. A lo anterior hay que sumarle el coste que se ha invertido en desarrollar funcionalidades que lo mismo nunca se llegan a utilizar o deben ser sensiblemente reajustadas, por lo que disminuye la proporción entre valor y esfuerzo.

Trabajamos para eso, para desarrollar software que funciona. Pero, ¿de qué se trata eso de que el software funcione?. Cada uno tenemos diferentes perspectivas de las cosas, esta no iba a ser una excepción, por lo que para cada uno el listón puede estar a diferente altura y no necesariamente (dependerá del momento del proyecto, del contexto, de las características del sistema de información, etc…) unos tienen que estar siempre equivocados y otros estar en lo cierto.

Siguiendo un enfoque iterativo incremental vamos a ir liberando diferentes versiones del producto hasta obtener una “versión final” (lo pongo entre comillas porque el software es generalmente objeto de una continua evolución por lo que se puede hablar de versiones finalistas de un proyecto más que versiones finales de un sistema de información).

Independientemente de que desde el primer momento intentemos liberar el mejor software posible (desarrollo con intención) es razonable pensar que en las primeras iteraciones de una aplicación el listón no esté tan alto, sobre todo en aquellos casos donde esas iteraciones no llegan a un entorno de producción o de llegar su uso está limitado a su evaluación y porque la incertidumbre y falta de acoplamiento (al proyecto y entre las personas) en los momentos iniciales hace que se falle más de la cuenta (desarrolladores y usuarios).

No se trata de relajarnos en las primeras iteraciones, no quiero decir eso, sino de entender que no todos los momentos del proyecto son iguales (ni todas las circunstancias). No hay minutos prescindibles en un proyecto porque lo perdido no se recupera y por ese motivo siempre soy partidario de ir con intención siempre sin olvidar y, eso es lo que quiero dejar patente, que como en toda carrera de larga distancia hay que saber regular (y que todas las carreras son diferentes).

Un software que funciona debe satisfacer las expectativas del usuario. Si no satisface sus expectativas tenemos un producto que hace cosas pero no tal y como las quiere el usuario. No se trata de términos absolutos sino que tenemos que tener en cuenta los umbrales, es decir, la liberación de una nueva versión puede que no deje totalmente satisfecho al usuario pero sí supere sus umbrales de satisfacción. Nuestro objetivo será que la “versión final” esté más cerca de la total satisfacción del usuario que de su umbral superior pero eso requerirá mucho trabajo, voluntad por parte de los usuarios para darnos su feedback y diferentes evoluciones del sistema.

Un software que funciona no debe quedarse solo con la parte visible del iceberg. La deuda técnica cuenta y mucho. El usuario no la ve, no la valorará y sin embargo condicionará la mantenibilidad del producto y la disponibilidad del sistema ante futuras evoluciones del mismo. Es posible que haya clientes que no la tengan en cuenta, en cualquier caso soy de la opinión de que los proveedores deben marcar unos estándares de calidad para los sistemas que desarrollan, como elemento diferencial respecto a los que no lo hacen.

Más allá de la deuda técnica se encuentra la mantenibilidad (un concepto más general). Un software que funciona debe ser lo más fácilmente de mantener posible (dentro del contexto en el que se ha realizado el proyecto) y eso va más allá de la deuda técnica pudiendo contemplar elementos documentales si así fuera preciso.

Un software que funciona debe tener también en cuenta aspectos no funcionales. Algunos de ellos están en la parte visible del iceberg (aunque tal vez en la parte de atrás, la que no se ve a simple vista o la que requiere más tiempo para ser descubierta) como por ejemplo el rendimiento o la disponibilidad y otros en la parte no visible como por ejemplo la seguridad.

Sistemas altamente acoplados, con clases y métodos kilométricos, con código que resulta complicado de entender son ejemplos en donde realmente se programa con miedo una vez que se comprueba que tocarlo supone implicaciones hasta en las partes más insospechadas de la aplicación.

Si el sistema además es crítico el miedo pasa a convertirse en pánico.

Tal vez las palabras miedo o pánico sean demasiado exageradas (y no es nada positivo trabajar de esa manera) pero por lo menos el programador debe sentir respeto y estar alerta cuando realiza modificaciones en un sistema de este tipo. El exceso de confianza no es bueno y más en un caso como este.

Hay que evaluar si el cambio que se va a hacer en el sistema es algo puntual o si se sabe que van a existir una serie de cambios a corto y medio plazo, así como la magnitud de los mismos y la magnitud del sistema. El esfuerzo que supone realizar estos cambios (mucho mayor que en un sistema con una deuda técnica ajustada a sus características), el esfuerzo en testing y el riesgo que tiene liberar nuevas versiones de estos sistemas debe ser analizado antes de afrontar la mejor estrategia para el cambio.

En condiciones como esta, cambios que sean fruto de un capricho no deben ser contemplados y cambios muy relevantes que supongan una modificación sustancial del sistema podrían dar lugar a que se tome la decisión de desarrollar una nueva aplicación.

El programador por su parte puede tomar sus precauciones: testing unitario, automatización de determinado testing de mayor nivel, etc…

Por otro lado, el paso a producción de nuevas versiones de sistemas de estas características no debe tratarse a la ligera y esperar cruzando los dedos a que no pase nada.

El testing debe ser realizado en profundidad y desde el punto de vista funcional debería ser la combinación de casos de prueba y testing exploratorio.

Cuando la deuda técnica de un componente impide un desarrollo fluído ya sea porque se requiere un mayor esfuerzo y/o porque la posibilidad de que se produzcan efectos colaterales es muy alta o cuando el usuario advierte que el producto tiene alguna deficiencia funcional en un aspecto fundamental de su funcionamiento es el momento de plantearse el dar una solución a esos problemas antes de continuar con la evolución del producto.

Huir hacia adelante no suele funcionar bien en el desarrollo de software porque el problema no se elimina de esa forma y por el coste que tiene arrastrar con esa deuda técnica y/o esas deficiencias.

Desde mi punto de vista esa decisión no debe ser tomada unilateralmente por el desarrollador sino que debe ser consensuada con el responsable funcional, product owner o como queremos denominarlo porque es responsabilidad del mismo definir la línea de desarrollo del producto y, si vamos a realizar acciones que van a ralentizar la evolución del producto (a corto plazo), debe ser consciente de ello y autorizarlo.

Si la tarea de refactorización puede desarrollarse dentro de la iteración se consideraría como una tarea más de la misma y se ejecutaría sin más (avisar o no al product owner dependería de las políticas que, al respecto, se tuvieran definidas en el proyecto).

Se tiende a añadir más y más funcionalidades al producto, pensando que de esta forma se le estarán dando al usuario más posibilidades, sin caer en la cuenta de la relación coste/beneficio de las mismas y que llegado el momento el usuario utiliza casi siempre las mismas.

Eso en el caso de que se piense en el usuario y no en incrementar la facturación a costa de eso (que es lo que suele ocurrir: crear necesidades donde no las hay).

Las nuevas funcionalidades visten más que otras tareas a realizar en el sistema y que son mucho más importantes, como rematar bien funcionalidades que sí que resultan esenciales, mejorar el rendimiento y la usabilidad o situar la deuda técnica en unos niveles acordes a las características del sistema.

Nuevas funcionalidades sí, siempre y cuando aporten valor, pero un valor real.

Sobre este tema Mary Poppendieck opina lo siguiente: “Nuestros sistemas software contienen más funcionalidades que las que se van a utilizar jamás. Esas funcionalidades extras incrementan la complejidad del código y elevan los costes de manera no lineal. Si ni siquiera la mitad de nuestro código es innecesario, el coste del sistema con le código adicional no es el doble, es por lo menos diez veces más caro de lo que debería ser”.

Con más funcionalidades se incrementa la deuda técnica, las mismas tendrán feedback y se realizarán ajustes, habrá errores que lleguen a producción y puedan tener influencia sobre otras funcionalidades más importantes y casi lo peor de todo: nuestra atención se desvía de lo realmente importante y se centra en lo accesorio, afectando al acabado y calidad del producto final.

No rematar las tareas y arrastrar deficiencias en el producto constituyen una resistencia importante al proceso de desarrollo, afectando a la velocidad del equipo de desarrolladores y lo más importante, afectando al nivel de satisfacción de los usuarios.

Por ese motivo es fundamental que se tengan en cuenta en el proceso de desarrollo, ya sea dentro de la línea principal y/o como parte de un segundo equipo dedicado exclusivamente a resolver estos problemas, en base a las prioridades que se establezcan para las mismas.

Sobre este tema Mary Poppendieck realiza la siguiente reflexión: “La única forma de prevenir que se nos escapen defectos en el futuro es examinar los que se producen en el presente, metódicamente encontrar sus causas y, uno a uno, eliminarlos”.

El ritmo de desarrollo es muy importante, ya que condiciona la capacidad de adaptación al cambio del producto, ya sea provocado por el entorno o por las necesidades del usuario, por ese motivo, reducir la probabilidad de que se produzcan contingencias que afecten al funcionamiento continuo de la “maquinaria” resulta esencial.

Seguir

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

Únete a otros 1.715 seguidores