archivo

Calidad del Software

Me parece muy interesante la reflexión que la Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides) realizaron en el libro Design Patterns: “Una clase es más reutilizable cuando minimizas los supuestos que otras clases deben hacer para utilizarla”.

Es cierto que si piensas demasiado en quién la puede utilizar y cómo, estás diseñando condicionado por ese motivo y en muchos casos se aplicarán supuestos más orientados a un desarrollo a medida de que probablemente reducirán su capacidad de ser utilizada por otras.

¿Es extensible esta reflexión al desarrollo de infraestructuras de mayor nivel? Mi opinión es que sí, pero en estos casos es necesario analizar los pros y los contras de esa solución más genérica y abstracta porque en este caso puede condicionar demasiado el diseño del sistema que hace uso de esa infraestructura y restarle posibilidades de ser más productivo y eficiente, pero también y desde otro punto de vista, si se particulariza demasiado es posible que al final tengamos diferentes componentes software muy parecidos replicados con determinadas características específicas y estemos reinventando la rueda.

A priori es complicado decir qué es mejor (refiriéndome a infraestructuras de mayor nivel) hay que analizar qué cometido hace ese componente software y que aporta que sea un elemento genérico o abstracto.

Un esquema tradicional de la implantación del software podría ser el siguiente: 1) instalación en el entorno de integración, 2) Pruebas del sistema en el entorno de integración, 3) Instalación en un entorno de pruebas/preproducción, 4) Validación de el entorno pruebas/preproducción, 5) Instalación en producción.

Que sea tradicional no quiere decir que todos estén de acuerdo con él (y el esquema sea absolutamente distinto) o que sobre el mismo no haya modificaciones.

No entro a valorar si las instalaciones se realizan de forma automatizada o no, no es el objeto del artículo sino de las pruebas o validaciones. Tampoco entro en si se deben realizar pruebas unitarias o automatizadas de mayor nivel, ya que me quiero centrar más en las pruebas de carácter manual que realiza el equipo de testing (si existe) y/o los usuarios.

¿Se debe probar antes de entregar? Por supuesto que los desarrolladores deben hacerlo porque pese a se prueben secciones del sistema durante el desarrollo, es necesario hacer un testing final que afecte al conjunto de funcionalidades entregadas y hacer pruebas de regresión.

¿Debe probar el usuario? Es cierto que se requiere que los usuarios tengan tiempo para realizar esta tarea y en muchos casos, cuando los cambios son significativos y la aplicación compleja, el esfuerzo necesario es importante. Yo soy partidario de que los usuarios prueben en el entorno de integración sobre todo si el impacto funcional del desarrollo es importante (y/o la criticidad del cambio lo aconseja) y si los desarrolladores no tienen un control funcional lo suficientemente sólido.

Otro factor a tener en cuenta es el tiempo medio que se suele tardar desde que se entrega hasta que el producto llega a producción, teniendo en cuenta los posibles rechazos que puede haber en ese proceso.

En cualquier caso, como suelo decir, no se trata de aplicar necesariamente una política general a todos los proyectos sino de realmente analizar las características del sistema (es muy importante tener en cuenta su criticidad), de los cambios realizados, de los usuarios, etc… lo que pasa es que a veces por buscar una armonización de las tareas de testing se suelen establecer una normativa común (que puede admitir o no excepciones motivadas).

También es importante recordar que normalmente el coste de corrección de una incidencia suele ser proporcional al tiempo que ha pasado desde que se ha incorporado al código.

Aunque resulte paradójico, la agenda es una de las principales causantes de la pérdida de control del proyecto porque en el momento en que se rompen sus premisas: estabilidad de las especificaciones, del contexto y acierto en las predicciones de coste y plazos y no se trata esta situación de manera natural ajustando la misma a la nueva realidad, estaremos trabajando en una condiciones fundamentadas en unas hipótesis erróneas y de esa situación no se puede esperar nada bueno.

La crisis del software se asoció al no cumplimiento de las agendas porque efectivamente los proyectos no cumplían objetivos de costes, plazos, alcance y calidad y a partir de ahí se constituyó la gestión predictiva de proyectos generando con las experiencias pasadas y con el paso del tiempo todo un cuerpo de conocimiento.

Precisamente este problema es el que sufren los desarrollos que siguen el enfoque clásico pero perfectamente son extrapolables a enfoques pretendidamente ágiles si los mismos están condicionados por agendas rígidas (digo pretendidamente porque agenda rígida y agilidad son conceptos contrapuestos).

Como es lógico, se mejoraron los resultados del pasado porque hay que tener en cuenta que se pasó del caos a un cierto orden, pero seguían sin ser satisfactorios a lo que había que sumar el desgaste que tiene este tipo de gestión entre todas las partes implicadas en el proyecto.

El culto a la agenda no es la solución porque pretende estabilidad y predicción en un contexto de incertidumbre que es inherente al desarrollo de software. Por eso, el centro no debe ser el triángulo de hierro sino el valor del producto de manera que costes, plazos y alcances supongan referencias y no límites.

La gestión de la capacidad, velocidad o carga de trabajo del equipo resulta esencial. Uno de los factores que más influyen en la pérdida de control es precisamente el intento de cumplir agendas imposibles.

Con mucho esfuerzo, siempre y cuando, exista posibilidad material de cumplir unos plazos, se pueden llegar a ofrecer unos resultados, pero, ¿a costa de qué?, ¿a costa de quién? Del producto y del equipo, ya que probablemente el sistema resultante tenga funcionalidades que no estén bien rematadas, tenga incorrecciones funcionales, numerosos bugs y la factura técnica sea francamente mejorable, a lo que hay que añadir el desgaste de las personas debido a la presión y al overtime.

Esto impacta en la entrega pero también en lo que queda de proyecto porque será necesario dedicar esfuerzo a “barrer” o “pulir” lo que fue mal mientras se está trabajando en lo nuevo.

Si no se ha reajustado la capacidad de trabajo asumible por el equipo, seguirá desbordado y lo más probable es que no cumpla con la mayoría de los compromisos y los que cumpla presenten deficiencias similares a los de la anterior entrega y se sigan acumulando flecos pendientes. Como una bola de nieve esto irá a más y más durante el transcurso del proyecto y si además se acumulan modificaciones funcionales serias sobre elementos ya desarrollados o hay cambios sensibles en el contexto del proyecto, se volverá absolutamente inmanejable.

Incluso con la intención de ajustar el trabajo a la capacidad del equipo podemos caer en la situación descrita si nos equivocamos a la hora de hacer ese ajuste, si bien, es posible a corto/medio plazo volver a una situación más equilibrada ya que gracias a este enfoque tendremos en cuenta ese esfuerzo de más que vamos a necesitar en cada iteración para lograr ese objetivo.

Comenta Bertrand Meyer que: “El único gran enemigo de fiabilidad (y tal vez de la calidad del software en general) es la complejidad”, y tiene gran parte de razón.

La complejidad en sus dos vertientes: incluir funcionalidades no necesarias (o hacer más complejas funcionalidades que sí son necesarias) y descuidar la arquitectura y construcción del software haciendo el software mucho más complicado de evolucionar (mayor esfuerzo y mayor probabilidad de errores).

Tenemos que aprender a ser pragmáticos, a dejar de construir la casa por el tejado, a querer rizar el rizo sin ni siquiera tener el producto funcionando y todo ello sin olvidar que si no se construye software de calidad estamos poniendo resistencia y frenos a la capacidad de adaptación y evolución del sistema.

Una solución más compleja no tiene por qué ser mejor, una solución con más funcionalidades no tiene por qué ser mejor y una solución que esté a lo último en tecnología no tiene por qué ser mejor. Lo de menos es más, no es tampoco ciencia exacta, pero la verdad es que funciona más veces de lo que creemos.

Si desarrollas una solución demasiado compleja, al final pasa como con los mandos a distancia, muchos botones, muchas funciones, pero al final no utilizas ni el 10% de ellas.

Por otro lado, añadir complejidad de manera sistemática sin tener el producto en producción supone prácticamente comprar toda la taquilla para un más que probable fracaso y con poco margen de maniobra posterior, ya que tendremos un software mucho más voluminoso que mantener (y con una mayor deuda técnica ) y habrá menos recursos económicos para hacerlo.

La definición del flujo de trabajo es algo que está abierto y es lógico que así sea porque cada equipo de trabajo y cada proyecto tiene sus peculiaridades.

Es frecuente encontrarnos con tableros que no tienen una fase para el testing y suele ser provocado porque se entiende que de manera implícita se encuentra en la fase de realización, construcción, desarrollo o como se quiera llamar.

Conociéndonos como nos conocemos los desarrolladores y nuestras prácticas habituales de no probar de manera adecuada los componentes que construimos es una buena idea incluir una fase de testing de manera explícita. Es cierto que no te asegura nada (hacer testing requiere de disciplina y buenas prácticas) pero el simple hecho de considerarse una actividad y visualizarse es algo que tiene mucha fuerza.

Por ese motivo, aunque creas que la inclusión de esa fase no aporta nada, prueba a hacerlo y tras un tiempo, trata de sacar conclusiones de manera objetiva a través del número de defectos que se hayan conseguido solventar antes de alcanzar producción en comparación con los valores que estuvieras obteniendo hasta ahora, a partir de ahí ya tendrás todos los datos necesarios para continuar con estas prácticas de manera general, en proyectos o sprints concretos o tomar la decisión de, como hasta ahora, no incluirla.

La tentación, muchas veces motivada por las circunstancias, es obviar la mejora de la factura técnica del producto, para centrarnos en corregir errores y en incrementar las funcionalidades.

El usuario siente la calidad del software con lo que ve: cumplimiento de sus expectativas de funcionamiento y buen rendimiento y disponibilidad, por eso pondrá resistencia a invertir tiempo en reducir la deuda técnica.

No debemos olvidar que desarrollamos para el usuario y él es quien tiene la última palabra, lo cual no quita que le expongamos la ventaja que tiene la realización de este tipo de tareas, como una inversión que permita no solo incrementar la capacidad de trabajo que puede abordar el equipo, sino además, reducir la posibilidad de efectos colaterales que provoquen errores cuando y donde menos los esperamos.

De serie, un desarrollador debe tener en cuenta estas prácticas y tenerlo en cuenta a la hora de definir su velocidad. El tiempo que se puede dedicar a esto puede ser prácticamente ilimitado porque todo es susceptible de ser mejorado, por ese motivo, ese tiempo de más sobre las buenas prácticas de programación cotidiana es el que se tiene que discutir con el cliente.

Comenta Mary Poppendieck: “La refactorización continua no es opcional. Si se elige no refactorizar se elige no pagar la deuda técnica que con el tiempo demostrará que la posibilidad de fracaso aumenta”.

La clave es mantener la deuda técnica bajo control y esto requiere un esfuerzo desde la programación de base como de las tareas adicionales de refactorización, de acuerdo siempre con las necesidades de quién paga, el cual debe estar siempre informado de las consecuencias que tiene realizar desarrollos sobre un software de baja calidad.