archivo

Archivo de la etiqueta: technical debt

La calidad del software puede ser contemplada desde diferentes puntos de vista en el que cada uno de ellos matice un aspecto concreto del producto software.

La crisis del software hacía especial hincapié en las principales carencias que presentaban las aplicaciones: incumplimiento de las expectativas del usuario e incumplimiento de plazos y presupuestos.

Sin embargo cada uno de nosotros tenemos una visión particular de lo que es o debe ser la calidad del software, para algunos consistirá simplemente en que funcione, para otros conseguir la calidad implica muchos más factores.

Desde mi punto de vista la calidad del software pasa por el cumplimiento de las expectivas del usuario pero también por la existencia de un umbral de errores en el producto que se sitúe por debajo de los límites admisibles para el mismo. No debe pasar por los mismos filtros un software que controle los reactores de una central nuclear que el de una aplicación de gestión (o incluso estas frente a otras donde un mal funcionamiento no impacte críticamente en el negocio o en el balance económico de la compañía).

Alistair Cockburn, en el enfoque que le da a las metodologías Crystal tiene en cuenta esa circunstancia. No todas las aplicaciones son iguales, por tanto, el proceso para desarrollarlas no tiene por qué ser el mismo.

Sin embargo, pese a considerar como aspectos más importantes los anteriores, ya que sin su cumplimiento se resta importancia a lo bien que se hayan ejecutado otros, la calidad del software también requiere una deuda técnica admisible, una documentación útil (ni poca, ni mucha, la que sea necesaria y de utilidad) y un consumo de recursos hardware acorde a las características de la aplicación.

Eso es para mi, la calidad del software, ¿qué piensas tú?.

Puede parecer que todo vale por ganar contratos en tiempos de crisis (y en tiempos de no crisis). Se tiran los precios, se ofrece lo posible y lo imposible y nada parece lo suficientemente difícil.

¿Qué pasa después? Desgaste y más desgaste, tijeretazos al alcance, la calidad por los suelos y cuando no un montón de código que no se comporta como quiere el usuario.

Los proyectos de desarrollo de software valen lo que valen, todo es optimizable, lo mismo el alcance de los trabajos se adapta a otros desarrollos previos y basta con hacer adaptaciones, todo eso es analizable, ahora bien, cuando lo que se ofrece es a todas luces imposible, lo más probable es que realmente lo sea.

Si no hay dinero para hacer un desarrollo, busca si existe un producto que se ajusta a tus posibilidades, si no hay ni una cosa ni la otra, lo mejor es que se espere a disponer de presupuesto suficiente, siempre será mejor eso que tirar el dinero o invertir una cantidad que se te multiplicará después en tareas de mantenimiento correctivo y evolutivo con una deuda técnica importante.

Las técnicas de testing de caja blanca se realiza cuando el tester accede al código fuente de la aplicación y en consecuencia a los diferentes algoritmos y estructuras de datos utilizadas. La implementación de este tipo de pruebas requiere habilidades de programación, un conocimiento del framework de desarrollo y un cierto conocimiento funcional que permita conocer qué misión tienen determinadas clases y métodos.

Entre las técnicas de testing de caja blanca más conocidas tenemos la cobertura que consiste básicamente en la verificación de que todos los caminos lógicos de la aplicación son alcanzables teóricamente en función de los diferentes valores de entradas de los parámetros. Este tipo de pruebas, se automatizan con la ejecución de pruebas unitarias.

Otra técnica bastante conocida es la Mutation Testing que se suele utilizar para verificar la bondad de los métodos de testing utilizados. Se basa principalmente en realizar ligeras modificaciones en el programa que darían lugar a un comportamiento anómalo del mismo (resultados distintos) y verificar si la estrategia de testing utilizada es capaz de detectar estos cambios. Ejemplos de estos pequeños cambios lo podemos tener modificando el operador en las guardas de sentencias selectivas o iterativas, eliminando sentencias, etc…

Entre las críticas a esta estrategia de testing se encuentra el hecho de que hay que saber elegir muy bien cómo realizar las mutaciones, ya que hay que verificar que realmente lo que se cambia provoca un comportamiento diferente en el algoritmo porque lo mismo haces un cambio y no cambia de comportamiento.

Otra técnica de caja blanca la tenemos con la Fault Injection que persigue objetivos similares a la Mutation Testing (de hecho se considera a esta técnica como parte de la Fault Injection). Entre este tipo de técnicas tenemos la Code Insertion Testing, en la cual se introducen sentencias nuevas que provocan un error o un comportamiento anómalo en el sistema. Además de estas tećnicas, que se aplican en tiempo de compilación, la Fault Injection también aplica estrategias en tiempo de ejecución, como el hecho de provocar determinados fallos o excepciones del sistema que permitan estudiar el comportamiento del sistema en esos casos.

Una de las técnicas de testing de caja blanca más conocida es el análisis estático de código que tiene como objetivo principal evaluar (directa o indirectamente) la deuda técnica del software o lo que es lo mismo, evaluar el grado de mantenibilidad del sistema.

La mantenibilidad del sistema es una característica a la que no se suele dedicar suficiente atención, al fin y al cabo lo importante es que el sistema funcione (algo de lo que estoy totalmente de acuerdo), pero que una vez que se consigue debe ser compatible con el hecho de que el sistema sea escalable y pueda ser modificado a un coste razonable.

Además, por regla general, un sistema con una deuda técnica elevada será más propenso a tener errores.

Ver también:

Testing de caja negra.
Testing de caja gris.

Asunto espinoso. Las metodologías ágiles están orientadas a la entrega de productos funcionales que vayan completando mediante iteraciones sucesivas y a través del feedback del usuario un sistema completo acorde a las necesidades del usuario (por tanto, un sistema de calidad), dentro los plazos y presupuesto establecido.

¿Es incompatible con lo anterior la calidad del código? Por supuesto que no, pero, ¿es lo más importante? Tampoco lo es.

El objetivo es conseguir que un sistema sea satisfactorio para el usuario, sin eso de nada vale lo demás y eso se consigue mediante aproximaciones sucesivas teniendo como soporte a un usuario que es aliado y que está comprometido en el proyecto.

Si nos centramos exclusivamente en el objetivo y no vemos más allá, la calidad del código queda en un octavo o noveno plano, es decir, el sistema es lo que el usuario quería, ¿para qué pedir más?. Si el sistema no se va a tocar nunca más, si se van a realizar modificaciones mínimas en el mismo o si no presenta problemas de rendimiento por una mala codificación y/o arquitectura, puede tener sentido que no importe mucho lo que está dentro (la máquina me da buen café y no requiere mantenimiento, ¿qué más me da cómo se hayan construido los circuitos que la hacen funcionar?).

Ahora bien, si se prevén modificaciones de cierta envergadura en el proyecto o que las mismas pueden existir, sí que entra en juego la calidad del código, ya que la deuda técnica del software puede hacer muy costoso el mantenimiento (y hacerte recordar a los ancestros de quiénes codificaron el sistema).

Esta misma idea podría ser aplicable a las sucesivas iteraciones en la construcción del producto. Hay que tener en cuenta que en cada iteración además de nuevas funcionalidades, se corrigen aspectos funcionales y no funcionales del sistema de acuerdo a las directrices del usuario y del equipo técnico del proyecto. Una mala arquitectura, una mala codificación, puede condicionar las sucesivas evoluciones del producto, haciéndose cada vez más pesadas y costosas, lo que obligaría a ser menos ambicioso en cada iteración y requiriéndose un mayor número de iteraciones para obtener el sistema, lo que puede provocar que se ponga en riesgo el cumplimiento de plazos y el presupuesto.

El acoplamiento es un concepto teórico que tiene consecuencias fatales en el mundo real. Un programa con alto nivel de acoplamiento es un programa muy difícil (y en consecuencia) costoso de mantener, en consecuencia, un software con un acoplamiento elevado tiene una deuda técnica también bastante importante.

No tener en cuenta el acoplamiento cuando se desarrolla es caer en el reverso tenebroso de la fuerza, lo mismo resulta más sencillo no tener que pensar en eso cuando se codifica, además no requiere de personal más cualificado (hacer las cosas bien para luego hacer que las cosas sean más sencillas necesita de un esfuerzo y por tanto no es nada trivial). Como ya comenté en un artículo anterior, programar pensando en que no se va a tener que volver a tocar el código es vivir ajeno a la realidad de lo que es el desarrollo de software y el ciclo de vida de los sistemas de información. ¿Puede pasar que no se tenga que tocar? Sí, pero, ¿realmente lo puedes asegurar?, ¿qué te dice tu experiencia?. Cierto es que puedes pensar: “bueno, al final le tocará el marrón a otro”, pero ¿y si al final te termina tocando a ti o a algún compañero?.

Un software fuertemente acoplado es una caja de sorpresas cuando te pones a realizar tareas de mantenimiento, lo más probable es que cuando tapes un agujero se abran dos. Al final se conseguirá sacar el mantenimiento adelante, pero con un esfuerzo, desgaste y coste bastante importante.

El uso de frameworks de desarrollo supone un primer paso para reducir el acoplamiento, pero el uso de este tipo de estrategias por sí mismas no aseguran nada, ya que aunque se “deleguen” determinadas tareas en ellos, al final las “tareas de bajo nivel” residen en el software que se implementa y que el software esté más o menos acoplado depende de las buenas prácticas, conocimientos y experiencias del programador.

A los modelos de clases no se les suele prestar demasiada atención ya que una vez obtenidos los requisitos, construido el modelo de datos y consensuada la interfaz parace que lo demás no tiene demasiada importancia, sin embargo, los modelos de clases sí que permiten ver (por lo menos a alto nivel) situaciones de acoplamiento y tomar decisiones antes de codificar que pueden reducir esta métrica. Evidentemente requiere un esfuerzo y hay que sopesar también en función de las características del sistema si se realiza ese estudio o no, habrá situaciones en las que puede merecer (y merecerá) la pena y otras en las que no (aplicaciones relativamente simples).

Nos centramos mucho en los requisitos funcionales y en cierto modo es lógico que así sea, ya que si una aplicación no cumple el propósito y expectativas que se tiene sobre la misma de nada vale que la calidad del producto y del desarrollo sea impecable.

Parto de la base de que no es nada fácil conseguir lo que el usuario o el cliente busca, entre otras cosas porque es complicado que ambos tengan del todo claro lo que quieren (sí lo pueden tener en la cabeza, pero una aplicación son muchos detalles y es difícil que tengan en cuenta todos) y también que los analistas entiendan e interpreten a la perfección lo que les están contando.

Precisamente porque el desarrollo de un producto tiene asociado inherentemente tareas de mantenimiento correctivo y evolutivo es necesario ir más allá de que el producto funcione, entendiendo que el diseño y codificación realizados deben hacerse pensando en que habrá que modificar la aplicación, para ello desde un primer momento hay que pensar también en la calidad de la aplicación desde el punto de vista de la arquitectura, diseño y construcción.

No tener en cuenta estos detalles provocarán en el sistema desarrollado una deuda técnica que se arrastrará en los mantenimientos y tenderá a empeorar salvo que se decida atajar, ya que el software tiende a deteriorarse con los mantenimientos, siendo este deterioro muy sensible en aquellos casos donde el producto no ha tenido una ejecución buena desde el punto de vista de la calidad del código.

Sonar permite ver de manera muy sencillo lo que esconden las aplicaciones debajo de la alfombra a través de las diferentes métricas que permite obtener. Es cierto que solo son un conjunto de métricas y que hay que saber interpretarlas de manera individual, colectiva y en el contexto de cada aplicación, pero permiten dar una visión objetiva y externa tanto a clientes como a proveedores, ya que no hay trampa ni cartón si ambas partes conocen el juego de métricas y reglas que se le van a pasar al código.

Desde mi punto de vista Sonar es una herramienta de implantación obligatoria en los departamentos de desarrollo y/o de calidad del software, ya que además de permitir detectar aspectos del desarrollo que generan deuda técnica, permiten determinar la complejidad (y por tanto tiempo y esfuerzo) de los mantenimientos y puede ser utilizada para establecer determinados acuerdos de niveles de servicio en cuanto al software que se entrega.

Sonar permite calcular muchas métricas de carácter individual, como por ejemplo Cobertura, Duplicated Lines, Complejidad Ciclomática, RFC, LCOM4, etc…, pero también dispone de métricas que ofrecen resultados en función de poner en relación una serie de métricas diferentes.

Un ejemplo de ello lo tenemos en Technical Debt (base como he comentado en otros artículos de lo que es la filosofía y fin último de Sonar, tal y como dice su lema “Put your technical debt under control) y otro lo tenemos por ejemplo en las métricas proporcionada por el plugin de Sonar denominado SIG Maintainability Model.

Como indica la página que el plugin tiene en Sonar, se trata de una implementación del Modelo de Mantenimiento del Software Improvement Group (SIG).

¿Qué es el Software Improvement Group? Es una firma de consultoría holandesa con sedes en otros países europeos, que según especifica en su página web se basa en dar una serie de resultados, basados en hechos (métricas objetivas) y no en opiniones, proporcionando evaluciones de riesgos imparciales, objetivas,verificables y cuantitativas relacionados con los sistemas de información corporativos que se quieran estudiar.

Uno de los servicios que proporciona la empresa SIG es la certificación de la mantenibilidad de los productos software según el estándar internacional ISO/IEC 9126. Dicho estándar clasifica la calidad del software en una serie de aspectos, uno de los cuales es la mantenibilidad basándose el mismo en la obtención de las siguientes métricas: Estabilidad, Facilidad de análisis, Facilidad de cambios y Facilidad de Pruebas.

Por tanto el plugin de Sonar SIG Maintainability Model, permite calcular dichas métricas, las cuales resultan significativas ya que determinan el grado de mantenibilidad de un software basado en un estándar internacional.

Una vez que hemos podido apreciar lo relevante que resulta este plugin, toca analizar como calcular cada una de sus métricas.

El cálculo se basa en la obtención en primer lugar de una serie de métricas más simples (ya que las métricas finales indicadas anteriormente son el resultado de la combinación de las mismas) y clasificando los resultados de dichas métricas en cinco posibles escalas (las cuales voy a ordenar de menor a mejores resultados desde el punto de vista de la mantenibilidad):

Volumen: Se obtiene a partir del número de líneas de código de la aplicación.

Escalas:

–: > 1310000 líneas de código.
-: > 655000 líneas de código.
0: > 246000 líneas de código.
+: > 66000 líneas de código.
++: > 0 líneas de código.

Duplicidades: Se obtiene a partir del porcentaje de líneas duplicadas en la aplicación:

Escalas:

–: > 20% líneas duplicadas
-: > 10% líneas duplicadas
0: > 5% líneas duplicadas
+: > 3% líneas duplicadas
++: > 0% líneas duplicadas

Pruebas unitarias: Se obtiene a partir del grado de cobertura del código a través de pruebas unitarias.

Escalas:

–: > 95% cobertura
-: > 80% cobertura
0: > 60% cobertura
+: > 20% cobertura
++: > 0% cobertura

Complejidad: Se obtiene a partir de la complejidad ciclomática de los métodos.

La forma de cálculo es un poco más compleja ya que se realiza en dos pasos:

1) Clasificar cada método en uno de los siguientes rangos en función de su complejidad ciclomática:

Muy alta: > 50
Alta: > 20
Media: > 10
Baja: > 0

2) A continuación la escala se obtiene a partir del porcentaje de métodos que se encuentran en los rangos definidos en el paso 1):

Escalas:

–: Resto de casos
-: C.C. Media <50%; C.C. Alta <15%; C.C. Muy Alta <5%
0: C.C. Media <40%; C.C. Alta <10%; C.C. Muy Alta <0%
+: C.C. Media <30%; C.C. Alta <5%; C.C. Muy Alta <0%
++: C.C. Media <25%; C.C. Alta <0%; C.C. Muy Alta <0%

Tamaño unitario: Se obtiene a partir del número de líneas de código de los métodos.

La forma de cálculo es un poco más compleja ya que se realiza en dos pasos:

1) Clasificar cada método en uno de los siguientes rangos en función de su número de líneas de código (LOC):

Muy alta: > 100
Alta: > 50
Media: > 10
Baja: > 0

2) A continuación la escala se obtiene a partir del porcentaje de métodos que se encuentran en los rangos definidos en el paso 1):

Escalas:

–: Resto de casos
-: LOC Media <50%; LOC Alta <15%; LOC Muy Alta <5%
0: LOC Media <40%; LOC Alta <10%; LOC Muy Alta <0%
+: LOC Media <30%; LOC Alta <5%; LOC Muy Alta <0%
++: LOC Media <25%; LOC Alta <0%; LOC Muy Alta <0%

Una vez calculadas las métricas simples, se calculan las métricas complejas que son las que definen la métrica de mantenibilidad de la ISO/IEC 9126, realizando una simple media de las escalas obtenidas en las métricas que definen cada métrica compleja:

Analizabilidad (Analysability) (A): [VOLUMEN], [DUPLICIDADES], [PRUEBAS UNITARIAS], [TAMAÑO UNITARIO].
Modificabilidad (Changeability) (C): [DUPLICIDADES], [COMPLEJIDAD]
Estabilidad (Stability) (S): [PRUEBAS UNITARIAS]
Testeabilidad (Testability) (T): [PRUEBAS UNITARIAS], [COMPLEJIDAD], [TAMAÑO UNITARIO].

De esta manera los valores de A, C, S y T oscilará cada uno entre — (valor más malo) y ++ (valor más bueno).

Estos cuatro valores se representarán en un gráfico donde cada eje representa el valor de estas métricas, teniendo cada eje tres escalas que estarán más separadas de la posición (0,0) que se corresponde con el valor (–) conforme más buena sea la métrica. En sentido horario, los ejes representan respectivamente a T, A, C y S.

Además de esa representación, el color del gráfico también es identificativo de la mantenibilidad de la aplicación, ya que tendrá un color entre el rojo (todas las métricas –) y verde (todas las métrica ++).