TEMA 2 MÉTRICAS DEL SOFTWARE · 2 - 1 2.1.- Métricas del software. Toda magnitud física es...

28
i TEMA 2 MÉTRICAS DEL SOFTWARE 2.1.- Métricas del software., 2 - 1 2.1.1.- Métricas de tamaño., 2 - 3 2.1.2.- Métricas de estructuras de control., 2 - 12 2.1.3.- Métricas compuestas., 2 - 15 2.1.4.- Métricas de esfuerzo., 2 - 17 2.1.5.- Métricas de calidad y fiabilidad., 2 - 19 2.1.6.- Métricas de diseño., 2 - 25 2.1.7.- Otra métricas del software., 2 - 27

Transcript of TEMA 2 MÉTRICAS DEL SOFTWARE · 2 - 1 2.1.- Métricas del software. Toda magnitud física es...

i

TEMA 2

MÉTRICAS DEL SOFTWARE

2.1.- Métricas del software., 2 - 1

2.1.1.- Métricas de tamaño., 2 - 3

2.1.2.- Métricas de estructuras de control., 2 - 12

2.1.3.- Métricas compuestas., 2 - 15

2.1.4.- Métricas de esfuerzo., 2 - 17

2.1.5.- Métricas de calidad y fiabilidad., 2 - 19

2.1.6.- Métricas de diseño., 2 - 25

2.1.7.- Otra métricas del software., 2 - 27

2 - 1

2.1.- Métricas del software.

Toda magnitud física es susceptible de ser medida. Sin embargo, el software es una actividad fundamentalmente intelectual, no sólo en su origen, sino que su resultado final también los es. El software no tiene de entidad física más que el soporte que lo registra: el papel, material magnético, los circuitos de la memoria. Las medidas realizadas sobre estos soportes no lo son estrictamente del software, sino que se trata únicamente de referencias indirectas.

De ahí que muchas medidas del software incidan en aspectos abstractos, que resultan difíciles de describir de forma clara. Cuando se miden las magnitudes del software se toman las referencias adecuadas sobre los soportes del software, generalmente el código fuente, y de ahí se obtienen cuantificaciones de conceptos tales como legibilidad o dificultad, junto con otras más accesibles como son el propio tamaño físico del código.

Las métricas del software responden a dos objetivos: valorar y estimar. Las magnitudes objeto de valoración son tres: la calidad, fiabilidad y productividad. La estimación parte de mediciones históricas para prever el esfuerzo y el tiempo que debe invertirse en un proyecto dado, y las características del resultado final.

Cualquiera de estas tareas (valoración y estimación) resulta tremendamente compleja, a pesar de contar con las métricas del software. Calidad y fiabilidad son conceptos importados de la ingeniería hardware, pero que en el software tienen un sentido muy distinto. La productividad es un concepto ampliamente desarrollado en otras áreas industriales, pero la carencia de un sistema métrico unificado, y la diversidad de factores que influyen en ella, hacen que tampoco sea posible obtener valoraciones fiables de esa magnitud en el ámbito del software.

La diversidad de métricas del software es enorme. Son muchos los autores que han sugerido un sistema métrico propio. De ahí que sea difícil comparar (una de las tareas básicas de la medición) los trabajos efectuados hasta la fecha sin contar con un alto grado de incertidumbre e imprecisión.

Aún cuando hay medidas que son generalmente aceptadas (las líneas de código como métrica de tamaño, por ejemplo), no desaparecen los problemas por completo: matices de apreciación (considerar o no los comentarios y las líneas en blanco) y sistemas de referencia de compatibilidad dudosa (código generado en

2 - 2

distintos lenguajes) hacen difícil encontrar una referencia consolidada en la que basar las observaciones.

A continuación se enumeran algunas de las métricas del software más comunes ordenadas por el ámbito del software a que se refiere. A cada una de ellas, podría añadirse una larga serie de variaciones según los distintos autores, pero se pretende dar una visión de conjunto, más que hacer una enumeración exhaustiva.

Hay muchas magnitudes que pueden ser medidas en el software: el tamaño en líneas de código, el coste monetario del desarrollo, el tiempo de desarrollo en días de trabajo, el tamaño de la memoria precisada en bytes, e incluso el número de quejas del usuario antes de entregar el producto. Diferentes observadores del mismo producto, pueden obtener distintas medidas, incluso en una misma magnitud.

Un típico ejemplo de esta circunstancia son las líneas de código. Un análisis puede considerar el número total de líneas en el listado, incluyendo las líneas en blanco y las de comentarios. Otro podría ignorarlos dado que no afectan al programa. Otro más no consideraría como líneas contables aquellas copiadas de algún otro sistema (librerías, generadas automáticamente, ...). La necesidad de una definición unificada de las líneas de código, o de cualquier otra medida es una exigencia común de los ingenieros del software.

Una posible clasificación de las métricas empleadas para valorar el software aparece en la figura 2.1 y establece dos categorías principales: métricas del producto, y métricas del proceso. Las primeras son las que tradicionalmente se han empleado en la medida del software, y pueden ser obtenidas automáticamente tomando como entrada el código fuente: tamaño, estructuras de datos y lógica. Las métricas del proceso por su parte dependen esencialmente del entorno de desarrollo. Un ejemplo de este tipo de métricas es el tiempo empleado para desarrollar un elemento software que depende de distintos factores externos (dificultad del problema, capacidad del personal, metodología empleada,...).

Tamaño

2 - 3

DEL PRODUCTO Estructura de datos Lógica ..............

MÉTRICAS Tiempo de desarrollo DEL PROCESO Reusabilidad Productividad .................

Fig. 2.1. Métricas del software. Categorías.

Un problema asociado al empleo de métricas del software es el de comparar medidas diferentes, aunque aparentemente se refieran a la misma magnitud. Sólo la definición precisa de las condiciones y los elementos que intervienen en una medida determinada permitirán que la comparación de dos valores pueda realizarse con garantías y pueda servir de base a estudios más completos y fiables.

En las siguientes secciones se enumerarán algunas de las métricas más difundidas siguiendo la clasificación mencionada anteriormente.

2.1.1.- Métricas de tamaño.

Los programas se escriben en lenguajes muy distintos y con propósitos muy diferentes, usando técnicas y métodos dispares, pero con una característica común: tienen un tamaño.

Este tamaño se determina habitualmente tomando como referencia el programa en código fuente. Podrían emplearse otras magnitudes para este propósito (la memoria que se necesita para almacenarlo), pero el tratamiento de un fichero fuente es relativamente sencillo, lo que facilita la sistematización y automatización del proceso de medida. El tamaño es una medida empleada fundamentalmente por tres razones: es fácil de obtener una vez que el programa ha sido completado, es uno de los factores más importantes en los métodos de desarrollo, y la productividad se expresa tradicionalmente con el tamaño del código.

La medida de tamaño más usada es la cantidad de líneas de código que se representa como Ss, y se mide en LOC (Lines Of Code, líneas de código). Para programas grandes es más adecuado el uso de KLOC (miles de líneas de código) representadas como S. A pesar de que la línea de código podría suponerse una

2 - 4

medida simple que puede obtenerse fácilmente no lo es tanto. Para muchos autores, las líneas de código medidas no deben incluir comentarios o líneas en blanco, dado que su presencia o ausencia no afectará al funcionamiento del programa. Además, incluir comentarios o líneas en blanco no supone el mismo nivel de dificultad que desarrollar una línea de código. La inclusión de estos elementos en la medida de productividad induce a muchos programadores a producirlos artificialmente con objeto de inflar los resultados dando lugar a elevadas tasas de productividad, generalmente estimada en LOC/PM (líneas de código por Programmer Month, programadores o personas por mes). Por contra, comentarios y líneas en blanco aumentan la legibilidad del código, por lo que facilitan la depuración de errores y el mantenimiento, si bien es difícil cuantificar en qué grado.

Por otra parte, el uso de lenguajes de alto nivel complica la definición de líneas computables: una instrucción puede extenderse a través de varias líneas, o dos o más instrucciones pueden situarse en una única línea. Además, hay elementos del programa que tienen un peso desigual en el resultado final: declaraciones de variables, tipo, constantes, cabeceras de funciones, directivas de preprocesador.

Todo esto contribuye a complicar el uso de las líneas de código como métrica fiable, ante la falta de definiciones estrictas y únicas de esta medida.

Una posible solución a los problemas mencionados anteriormente viene de la mano de la medida de tamaño como cuenta de los "tokens" del código fuente. Un token es un elemento de lenguaje que tiene significado por sí mismo. Los tokens de un programa son las instrucciones del lenguajes, los identificadores, constantes, operadores delimitadores de comentario y signos especiales del mismo. De esta forma se obtiene una medida más realista de la cantidad de información contenida en el código fuente. Ver para ello la subrutina FORTRAN de la figura adjunta y el análisis y descomposición en tokens de la misma en la figura siguiente.

2 - 5

Línea Nivel 1 SUBROUTINE SORT (X,N) 2 INTEGER X(100), N, I, J, SAVE IM1 3 C THIS ROUTINE SORTS ARRAY X INTO ASCENDING 4 C ORDER. 5 IF (N.LT.2) GO TO 200 1 6 DO 210 I=2,N 2 7 IM1=I-1 3 8 DO 220 J=1, IM1 3 9 IF (X(I).GE.X(J)) GO TO 220 4 10 SAVE=X(I) 5 11 X(I)=X(J) 5 12 X(J)=SAVE 5 13 220 CONTINUE 3 14 210 CONTINUE 2 15 200 RETURN 1 16 END

Fig. 2.2. Subrutina FORTRAN.

Operadores Ocurrencias Operandos Ocurrencias SUBROUTINE 1 SORT 1 ( ) 10 X 8 ' 8 N 4 INTEGER 1 100 1 IF 2 I 6 .LT. 1 J 5 GO TO 2 SAVE 3 DO 2 IM1 3 = 6 2 2 - 1 200 2 .GE. 1 210 2 CONTINUE 2 1 2 RETURN 1 220 3 end-of-line 13 n1=14 N1=51 n2=13 N2=42

Fig. 2.3. Análisis de tokens de la subrutina de la Fig. 2.2.

La Ciencia del Software de Halstead constituye un sistema métrico muy original que considera el tamaño del código en tokens. Las métricas básicas de la Ciencia del Software son:

n1 : número de operadores diferentes del lenguaje

n2 : número de operandos diferentes del lenguaje

2 - 6

N1 : número total de operadores

N2 : número total de operandos

Considerando operadores los símbolos o palabras clave que especifican una acción (instrucciones, signos matemáticos, de puntuación), y operandos los elementos que representan datos (variables, constantes, etiquetas).

El tamaño de un programa en términos de tokens se expresa como:

N = N1 + N2

Los conflictos entre medidas de tokens surgen a la hora de considerar como uno solo las instrucciones de entrada/salida, las declaraciones y las líneas que contienen etiquetas, o por el contrario computar todos los posibles tokens que puedan aparecer en estas circunstancias. Las reglas usadas para contar tokens dependen mucho del lenguaje en que esté escrito un programa. Con frecuencia aparecen ambigüedades a la hora de considerar elementos del código en una de las dos categorías de tokens, e incluso al tratar de computarlos como elementos con significado.

La Ciencia del Software define algunas otras métricas adicionales. Por ejemplo el vocabulario se define como:

n = n1 + n2

Otra medida basada en las anteriores es el volumen de programa:

V = N . log2n

La unidad de medida del volumen es el bit. El volumen mide de esta forma el espacio mínimo requerido en memoria para almacenar el programa (suponiendo que cada elemento del vocabulario va a estar representado por un único número binario). Estudios realizados sobre módulos en lenguajes de alto y bajo nivel han mostrado que Ss, N y V están linealmente relacionados, y son todas ellas medidas válidas del tamaño de un programa (aparentemente Ss, N y V miden la misma característica del software). N y V son medidas "robustas" de tamaño: una variación significativa de las reglas de clasificación de operadores y operandos no afectan notablemente al tamaño resultante.

Algunos autores han sugerido unidades mayores que la línea de código para medir el tamaño de un programa, especialmente cuando el código es muy extenso.

2 - 7

Una de estas unidades es el módulo. Para grandes programas, es más fácil estimar el número de módulos que contendrán, en lugar del tamaño en líneas. Sin embargo, esta métrica aporta una información reducida pues es difícil determinar cuál es la equivalencia entre módulos y líneas de código. El tamaño de un módulo depende de su complejidad, de la forma de trabajo de cada programador, del lenguaje y de otros factores. De hecho, cuando se divide el código en módulos, se hace siguiendo consideraciones funcionales y no de tamaño pues el módulo es una unidad funcional (efectúa una tarea determinada), pero no es un múltiplo que contenga una cantidad exacta de líneas de código.

Como consecuencia de lo anterior, se ha propuesto como medida del tamaño de un programa el número de funciones que contiene. Una función es una colección de instrucciones que realizan una tarea determinada para lo que disponen de un conjunto particular de variables locales. El uso de funciones como métricas se basa en el hecho de que un programador piensa en el código en términos de las funciones que debe realizar, y no de los módulos o líneas que lo constituyen.

Un módulo puede contener una o más funciones. La evaluación del número de funciones de un programa es una tarea compleja, aunque supone una ayuda el que el programa esté estructurado en módulos. De hecho, los estudios que tratan sobre la estimación de tamaño siguiendo puntos de función muestran que la experiencia del evaluador es fundamental a la hora de identificar las funciones de un sistema, y de cuantificar su número.

El número de líneas que contiene una función es normalmente pequeño. Una explicación a este hecho está en las limitaciones de la mente humana que la impiden manipular una gran cantidad de información al mismo tiempo. Para algunos autores, el tamaño óptimo de un módulo está entre 50 y 200 LOC, y debe admitirse que cantidades mayores que impiden abarcar de una mirada la totalidad del texto del módulo, dificultan la comprensión de lo escrito.

Los programadores no desarrollan siempre software completamente nuevo. De hecho, buena parte del trabajo de un programador se refiere a la modificación de código ya existente. En las tareas de mantenimiento este hecho es evidente, pero en el desarrollo, la reusabilidad de código es una práctica aconsejable que reduce el esfuerzo total.

Actualmente, según los casos y áreas de aplicación, entre un 50 y un 95% del trabajo de los programadores es modificación de programas. Esto plantea

2 - 8

problemas a la hora de medir el código: no se puede valorar de igual manera el código reusado que el nuevo, y aún en el nuevo se debe distinguir entre el que se desarrolla para adaptar el viejo (que exige más trabajo), y el que es absolutamente nuevo y no depende del código reusado más que de otras secciones de código nuevo.

En muchos programas, el tamaño tiene dos componentes: Sn que representa al código nuevo, y Su que es el código reusado. Cualquiera que sean las unidades en las que se mida el tamaño, es necesario que un valor Se o tamaño equivalente, que sea función de Sn y de Su. El tamaño equivalente representa el que debería tener el código si no hubiera sido construido con código reusado, esto es, partiendo de cero.

Boehm propone para su modelo COCOMO (que se verá más adelante) la siguiente relación:

Se = Sn + a/100 Su

donde a es un factor de ajuste que depende del porcentaje de modificación que haya sufrido el diseño (DM) y el código (CM), y del esfuerzo preciso para la integración (IM):

a = 0,4(DM)+0,3(CM)+O,3(IM)

El valor máximo que puede tomar a es 100, y representa el caso en que la adaptación del código sea tan difícil como reescribirlo por completo.

Una fórmula similar es la propuesta por Bailey y Basili:

Se = Sn + k Su

y sugieren el valor k = 0,2 obtenido de la base de proyectos evaluados para su modelo. Sin embargo, fijar un valor para k a priori, no es realista, y no debe pasar de una primera aproximación al problema.

Thebaut modifica la fórmula anterior aventurando que la relación entre tamaño equivalente y código reusado no es lineal:

Se = Sn + Suk

donde k es un positivo menor o igual que 1. Usando bases de proyectos en la que tanto Sn como Su estaban disponibles, Thebaut obtuvo valores de k en torno a 6/7. En este caso:

2 - 9

Su6/7 > 0,25

En cualquier caso, no hay consenso acerca de lo que mide exactamente el tamaño equivalente. Aunque las fórmulas de este tipo son realmente útiles a la hora de establecer comparaciones entre sistemas distintos.

Métricas de estructuras de datos.

Una de las razones fundamentales de la programación es el proceso de datos. Parte de estos datos constituyen la entrada del sistema, parte tiene un uso exclusivamente interno y, por último, una tercera parte constituye la salida del sistema.

Así pues, disponer de un conjunto de métricas con el que medir la cantidad de datos usado en la entrada, la salida, e internamente resultará de utilidad para valorar el software. Este conjunto es el formado por las métricas de estructuras de datos que atienden a la cantidad de datos, al uso que se les da, y a su aparición en distintas partes del sistema.

Un método para determinar la cantidad de datos es contar las entradas de la tabla de referencias cruzadas asociada al código. Esta tabla contiene fundamentalmente las variables del programa, aunque también puede incluir otros elementos como etiquetas, tipos, o el propio nombre del programa. En general, se puede considerar datos de un programa todos aquellos elementos no pertenecientes al lenguaje (instrucciones, signos, operadores, constantes de todo tipo) que aparecen a lo largo del código.

Una primera medida de cantidad de datos es VARS, que representa el número de variables de un programa. La métrica n2 de Hasteald es válida también para estimar la cantidad de datos:

n2 = VARS + constantes únicas + etiquetas

n2 representa la cuenta de operandos distintos, por lo que no resulta estrictamente una métrica que contenga la cantidad total de datos. Con objeto de subsanar esta carencia, Hasteald propone N2, que representa el número total de ocurrencias de operandos.

Otro aspecto importante a la hora de valorar la información (los datos) usada por un programa es estudiar la forma en que se emplea. Estas métricas están orientadas al uso de módulos, y refieren información concerniente al uso de la

2 - 10

información. Pero la cantidad de variables y operandos no aporta información sobre la complejidad del programa o su construcción.

El proceso de programación, que depende casi en exclusiva del esfuerzo humano, debe contar con las limitaciones de la mente. Una de estas limitaciones se refiere a la cantidad de información diferente que puede tener una persona "en mente" al tiempo que realiza una tarea determinada. El concepto de variables vivas en una instrucción determinada representa esta limitación. Las variables tienen un periodo de vida que empieza con su primer uso (no con la declaración) y termina con la última mención que se hace de una de ellas. El número de variables vivas en una instrucción determinada indica la cantidad de información que el programador debe tener presente al construir el código. LV (variables vivas) indica la complejidad del código en un punto determinado (al margen de la propia complejidad del algoritmo desarrollado). Una medida global referida a todo el código sería el número medio de variables vivas por instrucción (LV ) que puede servir como referencia en comparaciones entre distintos programas.

Otra métrica que estudia el uso que se hace de los datos es la envergadura (SP, span) que representa el número de instrucciones entre dos usos de una misma variable. Grandes envergaduras requieren del programador que retenga en memoria durante mucho tiempo la información referida a una variable sin darle uso. La envergadura adquiere sentido plenamente cuando se usa su valor medio (SP ) referido a las variables.

Las dos métricas anteriores se refieren habitualmente a módulos. El paso a métricas globales de todo el programa se obtiene fácilmente:

LV LVmprograma i

i 1

m

==∑

donde LVi es la vida media de las variables del módulo i-ésimo, y m es el número de módulos.

SP SPn programa i

i 1

m

==∑

donde n es el número de posibles envergaduras del programa.

Las métricas de datos mostradas hasta ahora se refieren exclusivamente (salvo las generalizaciones) al espacio de un módulo. Sin embargo, los datos no están siempre limitados a un área determinada del código: es una práctica habitual el uso de datos comunes a varios módulos. La magnitud del flujo de información

2 - 11

entre los componentes de un sistema da información adicional sobre la complejidad del diseño y las relaciones entre módulos.

En teoría, todos los módulos están interrelacionados, si no es así, no pertenecen al mismo programa. Pero el grado de relación entre módulos depende de la cantidad de datos que constituya el flujo de información entre ellos. Este flujo puede estar formado por parámetros, o variables globales. En este último caso, la complejidad del programa es mayor y resulta más difícil comprender su funcionamiento. En el otro extremo están las variables locales cuya vida discurre en los límites del módulo, por lo que su evolución es más fácil de prever.

Del estudio de Basili y Turner se extraen dos métricas referidas a esta cuestiones. La primera es el par de uso local -global (P, R) que representa una instancia de un módulo P que usa a la variable R (P lee o escribe el valor de R). El número total de pares entre los módulos y las variables globales da una medida de la complejidad de la forma en que comparten los datos. La segunda métrica aporta información sobre la forma en que los módulos comparten información, y se representa por medio de tres valores: (P, R, Q) que indica que el dato R adquiere su valor en el módulo P y se lee en Q. Para que exista (P, R, Q) deben existir los pares (P, R) y (Q, R).

Otra medida que expresa la complejidad de los datos compartidos es la llamada fan-in (HENR81). Se supone que R puede ser tanto una variable global como un parámetro entre dos módulos, y el fan-in de un módulo Q es el número de módulos P que cumplen alguna de las siguientes condiciones:

• Existe una variable R que verifica (P, R, Q).

• Existe un módulo T y unas variables R y S tales que (P, R, T) y (T, S, Q).

El fan-in de un módulo es el número de módulos que directamente o indirectamente le pasan alguna información. De forma similar, es posible definir el fan-out como el número de módulos a los que directa o indirectamente pasa información un módulo determinado.

2.1.2.- Métricas de estructuras de control.

La estructura lógica de un programa es el mecanismo que le permite realizar las distintas funciones para las que fue construido. La estructura lógica del

2 - 12

programa representa los algoritmos empleados en su diseño y procesa los datos (la expresión "Algoritmos + Estructuras de Datos = Programas" WIRT76 es una estupenda definición de programa). La estructura de un algoritmo se representa perfectamente con las gráficas denominadas diagramas de flujo. Son muchos los métodos de medición del software que se basan en estos diagramas.

El flujo de control en un programa es habitualmente secuencial, aunque puede ser interrumpido en ciertas ocasiones:

• En una decisión, se divide en dos nuevas líneas de flujo que responden a la evaluación de una condición determinada.

• Un salto hacia atrás devuelve el flujo de control a una instrucción que ya ha sido ejecutada. Normalmente son la base de los bucles.

• Una transferencia de control a una rutina o procedimiento externo, hace que el flujo discurra por un camino externo al programa.

La métrica denominada cuenta de decisión (DE) mide la cantidad de veces que ocurren situaciones como las mencionadas en primer y segundo lugar. Esto es, cuenta el número de instrucciones de decisión y bucles condicionales. A la hora de contar decisiones debe tenerse en cuenta los casos en que estas son compuestas, en esta situaciones DE cuenta predicados más que instrucciones en sí, por lo que las situaciones en las que se usan los operadores AND y OR incrementan en más de uno el valor de DE (algo similar ocurre con instrucciones del tipo CASE).

Una métrica más sofisticada referida a esta misma cuestión es el número de complejidad ciclomática (v(G)). Esta medida está orientada a valorar el número de caminos linealmente independientes de un programa, lo que está relacionado con la facilidad para probar y mantener el código. Una aproximación al valor del número ciclomático se obtiene de la siguiente expresión:

v(G) = e - n + p

donde e es el número de líneas del diagrama de flujo, n es el número de nodos, y p el total de elementos interconectados (figura 6.10). Otra aproximación sugiere que el número de elementos interconectados es siempre dos (inicio y final), por lo que :

v(G)= e - n + 2

2 - 13

El número de complejidad ciclomática puede asimilarse al número de líneas que constituirían el "esqueleto" de un diagrama cuyo flujo se ha simplificado al máximo. El diagrama más simple, que es aquel que posee un sólo camino, presenta un v(G) = 1. Un posible límite de complejidad estaría en torno a v(G) = 10, más allá de esta cifra, las tareas de prueba y mantenimiento resultan muy complicadas.

Si t representa el número de predicados, se puede demostrar que

e - n = t - 1

v(G) = e - n + 2 = t + 1

dado que t y DE son prácticamente la misma cosa:

v(G) = DE + 1

e = 14 e = 10 n = 12 n = 8

v(G) = 14 - 12 + 2 = 4 v(G) = 10 - 8 + 2 = 4

Fig. 2.4: Números ciclomáticos de dos diagramas de flujo.

Por otra parte, también se puede demostrar que la complejidad ciclomática de un programa formado por varios módulos, es igual a la suma de las distintas complejidades.

Si m es el número de módulos:

2 - 14

v G v G e n mprog i

i

m

i

i

m

i

i

m

( ) = ( ) = − += = =∑ ∑ ∑

1 1 1

2

y de aquí:

v G DE mprog i

i 1

m

( ) = +=∑

La entropía es otra medida relacionada con la complejidad y el análisis de diagramas de flujo. La expresión general es:

Z log p X q n 2 i ji 1

n-1

= +=∑ ( ) ; X = 2

donde qi es la probabilidad de que el elemento de decisión i-ésimo (símbolo IF-THEN) esté en serie con el anterior. Por su parte pi es igual a 1 menos la cantidad anterior.

Zn está relacionada con MIN, el número de regiones delimitadas por un diagrama de flujo. En una sola línea de flujo MIN vale 1, con un bucle o una decisión es 2, con varias decisiones anidadas su cálculo se complica y la entropía da una expresión más exacta que el cálculo manual.

La entropía ha querido ser la métrica de unión entre complejidad y productividad. Actualmente varias herramientas de medida del software la emplean para valorar la complejidad frente a métricas más clásicas como el número de complejidad ciclomática.

Otras métricas referidas a la estructura de un programa son el número mínimo de caminos Np y la accesibilidad del código R. Obviamente, el número de posibles caminos en un código que contenga bucles es infinito, sin embargo, Np no toma en consideración aquellos caminos que se repiten más de una vez. Por su parte, la accesibilidad del código se obtiene dividiendo el número total de caminos entre el número de nodos. En relación a otras unidades del software, siempre se verifica que Np ≥ v(G).

El nivel de anidamiento (NL) es una métrica de complejidad que afecta a lenguajes de alto nivel. Cada instrucción tiene un nivel de anidamiento determinado, y el nivel medio que define un programa (N L ) se obtiene aplicando recursivamente el siguiente procedimiento:

• La primera instrucción ejecutable recibe el nivel 1.

• Si la instrucción a está en el nivel n y la instrucción b la sigue secuencialmente, su nivel también será n.

2 - 15

• Si la instrucción a está en el nivel n y la instrucción b está en un bucle o pertenece a una condición definida por a, el nivel de b será n+1.

Una métrica más de complejidad relacionada con la construcción del software es la cuenta de transferencias incondicionales del control. Dado que el seguimiento de las acciones de un programa a la hora de la prueba y el mantenimiento debe hacerse manualmente, y que resulta difícil averiguar que es lo que hace un código en el que abundan los saltos (representados por instrucciones GOTO), el número absoluto de éstos es una medida de complejidad. El número medio de saltos por módulo, o de instrucciones entre dos saltos sirve como referencias a la hora de comparar código de orígenes diversos.

2.1.3.- Métricas compuestas.

Las medidas descritas hasta ahora miden una única magnitud para darle sentido como una característica del software. Sin embargo, ocurre con frecuencia que para describir una determinada cualidad del software es preciso componer (construir un par) de medidas simples.

Una medida alternativa a la complejidad ciclomática es la formada por el par (CYC-mid, CYD-max) donde:

• CYC-min es el número total de instrucciones condicionales y bucles (incluyendo instrucciones CASE).

• CYC-mid es igual a CYC-min más un número igual a 2 menos el número de selecciones de una instrucción CASE.

• CYC-max es igual a CYC-mid más los operadores lógicos de una instrucción condicional (es equivalente a V(G).

Las medidas compuestas más conocidas son las de la Ciencia del Software de Halstead. Este conjunto de métricas dispone de las medidas simples (N1, N2, n1 y n2 ) ya mostradas y de otras compuestas basadas en éstas.

La longitud estimada de programa (^N) depende únicamente del número de operadores y operandos distintos ( N = N1 + N2 ). El valor calculado para este valor proviene de la siguiente expresión:

2 - 16

^N = n1 . log2 n1 + n2 . log2 n2

el volumen de un programa es definido por Halstead como:

V = N . log2 n

que se puede interpretar como el número de "comparaciones mentales" necesarias para escribir un programa de longitud N. Esta interpretación sugiere que la mente humana sigue un proceso de búsqueda binaria para seleccionar un token de un vocabulario de tamaño n.

Un algoritmo puede ser interpretado por varios programas equivalentes. Entre otros programas, uno tiene el volumen mínimo, también llamado volumen potencial (V*). Halstead indica que la implementación mínima de un algoritmo es una llamada a un procedimiento desarrollado previamente. La implementación de este algoritmo requerirá tan sólo invocar el procedimiento y enviar y recibir los parámetros de entrada y salida. El volumen potencial se calcula como:

V* = (2+n2* ) log2 (2 + n2*)

El primer término entre paréntesis, 2, representa los dos elementos mínimos de la llamada al procedimiento (el nombre y el signo que le separa de sus parámetros).

Un programa de volumen V está implementado a un nivel del programa L que se expresa como:

L = V* / V

Al inverso del nivel de programa, Halstead le denomina dificultad D. Si el volumen V de una implementación aumenta, el nivel de programa decrece y la dificultad se incrementa.

Una fórmula alternativa al cálculo de nivel de programa se obtiene de:

L = 2 / n1 . n2 / N2

Una hipótesis de la Ciencia del Software dice que el esfuerzo preciso para implementar un programa se incrementa cuando el tamaño del programa crece. El esfuerzo según Halstead se obtiene de:

E = V / L = D . V = n1 N2 N (log2 n) /2 n2

2 - 17

La unidad empleada para el esfuerzo es el número de discriminaciones mentales elementales. Tomando como base que la mente humana es capaz de realizar un número máximo β de discriminaciones por segundo (5 a 20, normalmente 18), se puede obtener el tiempo de programación total como:

T = E / β

Existen numerosos lenguajes de programación. Comparar las características de unos y otros es una tarea compleja. Con objeto de obtener una referencia válida entre lenguajes, Halstead propone como resultado de un estudio sobre una serie de lenguajes, la métrica nivel de lenguaje Γ:

Γ = L . V* = L ý V

Algunos valores obtenidos por este método son:

Pascal : 1.53 Algol : 1.21 Fortran : 1.14 Ensamblador : 0.88

Sin embargo, se detecta, según los estudios, variaciones en torno a ±1, lo que da una idea de la fluctuación de estas "constantes".

2.1.4.- Métricas de esfuerzo.

El desarrollo del software es una actividad humana que depende en gran medida del trabajo personal. A la hora de valorar un sistema software debe considerarse la cantidad de esfuerzo que debe invertir el equipo de desarrollo para culminar su construcción. El coste del desarrollo es prácticamente el del trabajo empleado, pues la parte asignada a materiales es de tan poca entidad que resulta despreciable frente a la mano de obra.

El esfuerzo requerido para construir un sistema puede ser medido con muchas unidades. Desde las discriminaciones mentales de Halstead, hasta el número real de horas y minutos que invierte un programador, la variedad es enorme, sin embargo hay una medida que destaca por su universalidad: la persona-mes o meses -hombre. Por otra parte, aunque el esfuerzo es muy importante, en realidad la más importante métrica del esfuerzo es el coste.

2 - 18

La importancia de la medida del esfuerzo y coste responde más a necesidades de tipo administrativo y de gestión que estrictamente técnicas. Pero por ello no deben menospreciarse: el aspecto económico del software, que es el que subyace a la gestión, determina la viabilidad de los proyectos. El conocimiento del esfuerzo invertido ayuda a valorar la productividad y a preparar las medidas de corrección oportunas para mejorar el trabajo del equipo y, estimar las necesidades de futuros proyectos.

La precisión de la medida viene determinada por el tipo de aplicación. No puede medirse de igual manera el esfuerzo y coste invertido en un proyecto pequeño, unipersonal, que el precisado para llevar adelante un proyecto grande, desarrollado por un equipo de trabajo formado por muchas personas.

Los proyectos pequeños son completados habitualmente por una sola persona en unos pocos días o semanas. Por ello, las unidades temporales más adecuadas serían los minutos, horas, o como muchos, los días. Así, el esfuerzo se mide en personas-día.

A esta escala, el aprovechamiento de la jornada laboral resulta muy importante. Las posibles interrupciones a lo largo de un día de trabajo son innumerables: llamadas telefónicas, pequeños descansos, comentarios con otras personas, distracciones,... Y tienen un reflejo inmediato en el rendimiento personal. De ahí que se opte por medir el tiempo de trabajo a "reloj parado", pasando por alto las interrupciones al trabajo.

Otro elemento importante al trabajar a escala reducida es el tiempo de comprensión y aprendizaje que el programador requiere para comprender los requerimientos, el diseño, o cualquier documento previo a la codificación. Aprender a manejar las herramientas y lenguajes, conocer los interfaces, la metodología empleada, etc... supone retrasos importantes en proyectos unipersonales.

Los proyectos habituales en la industria, en la investigación, o en entornos académicos, envuelven a equipos de trabajo formados por varias personas (a veces muchos equipos y muchas personas) durante períodos largos de tiempo: meses y años. De esta forma, las unidades más adecuadas para medir el esfuerzo, son las personas-mes, y personas-año. En estos proyectos hay que asignar recursos materiales y personales diferenciados a tareas que se realizaban conjuntamente en un proyecto unipersonal: diseño, prueba, documentación,... Esta especialización

2 - 19

obliga también a aislar las tareas administrativas, y grupos especiales de trabajo se encargan de coordinar, planificar, monitorizar, y gestionar el proyecto.

La intensidad en el trabajo no es mantenida por largos periodos de tiempo. Las interrupciones de todo tipo son tan numerosas y habituales que no son un factor de ruptura importante. Los tiempos de aprendizaje son grandes y afectan a todo el equipo. En resumen, los elementos que determinaban claramente a un proyecto pequeño no tienen esa importancia en uno grande.

La medida del trabajo realizado, que en proyectos pequeños puede llevarse a cabo íntegramente por observadores externos, precisa ahora de la colaboración de los participantes. En muchas organizaciones, los componentes del equipo de desarrollo están obligados a rellenar diariamente formularios en los que registrar su actividad diaria. Estos formularios contienen impresiones subjetivas de cada persona, y no suponen en muchos casos una referencia válida para medir el esfuerzo invertido.

Las valoraciones de esfuerzo resultan así poco exactas, y a pesar de la enorme importancia que se les atribuye, no pasan de constituir una referencia aproximada a los valores reales, difíciles de someter a medida.

2.1.5.- Métricas de calidad y fiabilidad.

El estudio de la calidad y fiabilidad tiene una importancia cada vez mayor en el mundo de la Ingeniería del Software. No sólo se trata de obtener sistemas desarrollados correctamente, de acuerdo a los requerimientos y a los estándares establecidos, sino que se pretende conseguir programas fáciles de mantener y, lo que es más importante, sistemas fiables en tareas críticas.

A pesar de los avances en las técnicas de generación de código, no se pueden producir programas totalmente libres de errores. De esta forma, entre las distintas fases del ciclo de desarrollo se van filtrando una serie de errores que obligan a emplear mucho esfuerzo en su detección y corrección.

Los errores pueden producirse en cualquier fase del ciclo de vida, pero sus efectos son tanto mayores cuanto más temprana sea su aparición. Los errores en la planificación tienen más consecuencias de tipo económico y de gestión que estrictamente técnicas, sin embargo los de diseño pueden producir los mayores problemas al estar en la base del sistema software. Los errores de codificación, con ser los más conocidos, no son los más importantes, a pesar de ello, son los

2 - 20

más tratados pues es más simple automatizar su detección. Los errores en las pruebas son muy importantes pues dan por válido un código que no lo es, y permiten que se entregue un sistema defectuoso. Los errores de mantenimiento pueden deberse a la ignorancia de fallos antiguos o a la introducción de otros nuevos.

Los procedimientos de detección de errores de fundamentan en dos procedimientos: las baterías de test sobre el sistema, y las revisiones técnicas .

Se puede afirmar que cada vez que se ejecuta un programa, se le está sometiendo a un test. Pero cuando se utiliza un programa no se está agotando el total de posibles situaciones en que puede encontrarse, sino que se trabaja con un conjunto de datos y estado "normales" o habituales. La prueba del software se encarga de someter un programa a una revisión de todos los estados posible por los que puede atravesar en algún momento de su vida útil. Los tres objetivos que dirigen la prueba del software son:

• La prueba es un proceso de ejecución de un programa con la intención de descubrir un error.

• Un buen caso de prueba es aquel que tiene una alta probabilidad de mostrar un error no descubierto hasta entonces.

• Una prueba tiene éxito si descubre un error no detectado hasta entonces.

Sin embargo, la característica más destacable de la prueba del software es que la prueba no puede asegurar la ausencia de defectos: sólo puede demostrar que existen defectos en el software. La afirmación anterior da la verdadera dimensión del proceso de prueba: sólo se puede demostrar que el software es erróneo, y no que sea completamente correcto. Cuando un test falla (esto es, cuando no encuentra errores) no se está en condiciones de afirmar que el programa probado está libre de defectos, sino que también puede ocurrir que el test sea incompleto o está mal construido (en una palabra, que contenga errores). Esto implica que las medidas referidas al número de errores dependa enormemente del proceso de prueba que se haya seguido, pues dos baterías de test podrían detectar distinto número de errores en un mismo programa.

El proceso de prueba debe adaptarse al sistema software que vaya a ser validado. Para simplificar un poco el trabajo del equipo de garantía de calidad se dispone de cierto número de técnicas que pueden emplearse dentro de una

2 - 21

estrategia de pruebas determinada. Unos elementos auxiliares de las tareas de prueba cuya importancia crece día a día son las herramientas automáticas. Habitualmente integradas en otras dedicadas al diseño y la codificación, se basan esencialmente en el uso de baterías de datos de entrada que comparan con listas de salidas correctas. Es de esperar que en el futuro, el progresivo perfeccionamiento de estas herramientas, haga que el proceso de prueba sea más fiables, y requiera menos dedicación por parte de los equipos de desarrollo.

Pero no es una buena táctica esperar al código para determinar su corrección. Como ya se ha dicho, los errores producidos tempranamente obligan a emplear muchos recursos en su reparación. Con objeto de verificar la validez de un diseño se usan una serie de técnicas conocidas como revisiones técnicas .

Las revisiones se basan en la verificación manual (por grupos de expertos) de la validez de un diseño, un programa, o cualquier otro elemento. Dado que se trata de un proceso humano regido exclusivamente por consideraciones subjetivas, las distintas técnicas se basan en estrategias que garanticen la imparcialidad de las decisiones, y que favorezcan la obtención de resultados homogéneos y mantenibles.

Las revisiones se realizan siempre por parte de varias personas, y por esta razón precisan de reglas muy estrictas que eviten consideraciones personales y no técnicas. Las revisiones y la prueba del software son procesos laboriosos que producen una gran cantidad de documentación, pero que tienen como recompensa, la obtención de resultados, si no libres completamente de errores, si al menos más fiables y mantenibles.

En general, la mayor parte de las métricas dedicadas al estudio de la calidad y fiabilidad se basan en contar los errores o fallos. Errores que no tienen que ser necesariamente incorrecciones del código, sino cualquier elemento que aparte los resultados finales de lo especificado en los requerimientos. Otras dos medidas importantes son el número de cambios realizados en el diseño del software, y el número de cambios realizados en un programa determinado. Los cambios en un programa responden a tres categorías: una o varias modificaciones en una instrucción simple, inserción (o borrado) de una o más instrucciones entre dos ya existentes, y un cambio del primer tipo seguido de inserción de instrucciones. La inserción o borrado de comentarios, o líneas en blanco no suele considerarse cambio ya que no afecta a los resultados finales del programa.

2 - 22

La métrica más común referida a los fallos de un programa es la densidad de fallos o errores:

densidad de errores = número de errores / S

siendo S el tamaño del código en número de líneas. Sin embargo, esta medida con ser una referencia importante, no da una idea de la magnitud de un error. Obviamente, no tiene la misma importancia un error en un programa de edición de textos que en el sistema de navegación de un avión, y dentro de un mismo sistema, subir o bajar un sueldo tendrá más repercusión que el redondeo de las cantidades finales. Algunos sistemas de prueba dan un valor en forma de "severidad" de un error, pero esta valoración es en cualquier caso muy subjetiva.

Otra característica difícil de cuantificar de los errores es la dificultad de encontrarlos. Hay lenguajes, formas de programar y tipos de errores que complican la tarea de encontrar los problemas del código. La depuración y el mantenimiento absorben muchos recursos que podrían reducirse si los errores fueran más fácilmente localizables.

En cuanto a la fiabilidad, hay que notar que aunque es un concepto prestado de la ingeniería hardware, no puede tratarse de igual manera en el software. Un componente hardware puede fallar de muchas maneras: por fatiga, sobrecarga, defectos de fabricación, influencias externas,... Sin embargo, los programas sólo puede fallar si están mal hechos, si el diseño o la codificación han sido defectuosos. Aunque un sistema crítico que emplee programas puede fallar por cualquiera de las causas hardware o software descritas anteriormente, sólo se puede atribuir al software aquellos problemas causados por los defectos en su elaboración. De esta forma, la fiabilidad es una medida integral de todo el proceso de desarrollo, pues precisa no sólo que se haya asegurado en todo momento la calidad del sistema, sino que además el proceso de prueba debe haber realizado una depuración perfecta de los errores que pudiera contener.

La fiabilidad emplea varias medidas, la primera de ellas es la probabilidad F de que aparezca un error en un tiempo determinado τ, donde τ > 0. La fiabilidad R es la probabilidad de que no ocurra un error en ese intervalo de tiempo:

R(τ) = 1 - F(τ)

2 - 23

esto supone que se estudia la fiabilidad a lo largo de un espacio continuo de tiempo, sin embargo, es más realista ajustarse al número de veces n que se ejecuta el programa:

R(n) = 1 - dn /n

donde dn es el número de fallo hallados en n ejecuciones.

Si f(τ) es la función de densidad de probabilidad:

f(τ) = dF(τ) / dτ

y f(τt) es la probabilidad de que el software falle en el intervalo (τ, τ + d τ).

La tasa de riesgo h(τ) es la probabilidad de que el software falle en (τ, τ + d τ) si no ha fallado antes:

h(τ) = f(τ) / ( 1 - F(τ) )

de donde podemos obtener una nueva expresión para la fiabilidad del software:

h(τ) = f(τ) / ( 1 - F(τ) )= - d( ln (1 - F(τ) ) ) / dτ = - d ln R(τ) / dτ

ln ,( )

R ( ) = h (x)dx y finalmente : R ( ) = eτ ττ

τ

00z zh x dx

Otra medida de interés es el tiempo medio entre fallos (MTTF, Mean Time To Faillure) que se define como:

MTTF n= ∑1 t ii=1

n

La expresión que relaciona el tiempo medio entre fallos y la fiabilidad de un programa es:

MTTF R t dt=∞z ( )0

La figura siguiente muestra tres figuras relacionadas con el estudio de la fiabilidad del software: en (a) se muestra el historial de errores en la vida de un programa; (b) muestra F(τ), la probabilidad de que aparezca un error en un programa en un intervalo de tiempo determinado; por último , en (c) se muestra f(τ), la función de densidad de probabilidad.

t1 t2 t3 t4 t5l l l l l

2 - 24

Fig. 2.5(a). Distribución de errores del soft. Histórico de errores

1

τ

F( ) = P[t<= ]τ τ

Fig. 2.5(b). Distribución de errores del soft. Función distribución intervalo de errores

f( ) = P [t< ]τ τ

τ

Fig. 2.5(c). Distribución de errores del soft. Función de densidad de probabilidad de errores

2.1.6.- Métricas de diseño.

Como ya se ha visto por las distintas métricas estudiadas, la complejidad de un programa crece con su tamaño: los programas largos son más difíciles de escribir y comprender, contienen habitualmente más errores, y su depuración resulta más compleja. Con objeto de reducir esta complejidad, los diseñadores de software han hecho un uso progresivo de técnicas de modularización y diseño estructurado. Entre las diversas ventajas de las técnicas de diseño se pueden destacar las siguientes:

2 - 25

• Comprensibilidad: programadores y usuarios pueden comprender fácilmente la lógica del programa.

• Manejabilidad: los gestores pueden asignar fácilmente personal y recursos a los distintos módulos representados por tareas.

• Eficiencia: el esfuerzo de implementación puede reducirse.

• Reducción de errores: los planes de prueba se simplifican notablemente.

• Reducción del esfuerzo de mantenimiento: la división en módulos favorece que las distintas funciones las lleven a cabo módulos diferenciados.

Aunque estos beneficios también son discutidos y para ello se alega toda clase de inconvenientes, en general se admite que el paso a la modularidad es un gran salto adelante. Pero el problema que se plantea ahora se refiere a los módulos en si: ¿Cuál es el tamaño idóneo, la complejidad máxima, la extensión adecuada de un módulo?

Algunas de las métricas vistas hasta el momento tratan este problema. Así algunos autores estiman que el tamaño de un módulo debe oscilar entre las 50-200 líneas de código. Otros simplemente indican que un módulo debe completar una función por sí solo. La complejidad límite de un módulo se fija en algunos casos en un número de complejidad ciclomática igual a 10.

Otras discusiones se centran en la organización de los módulos en el programa: estructuras en árbol, o lineales. Con objeto de obtener una valoración de los módulos y una disposición que pueda emplearse como base para comparaciones, surgen las métricas orientadas al diseño.

Muchas de estas métricas son generalizaciones de otras referidas a ámbitos más restringidos (números de complejidad ciclomática, métricas de la ciencia del software,...)

Uno de los estudios más completos relativos a la cuestión de valorar los módulos software es el llevado a cabo por Troy y Zweben en el que se relaciona una serie de métricas básicas con valores de calidad representados por la tasa de modificaciones en pruebas.

En este estudio, un gran sistema fue dividido en módulos usando varias convenciones de diseño. Cada módulo se codificó, probó y preparó para la

2 - 26

integración. Se registraron los cambios realizados en cada módulo. Cada implementación de diseño se acompañó de un gráfico que representaba las interconexiones entre los módulos. En total se obtuvieron veintiuna métricas asociadas a cada gráfico.

Los principios que dirigen estas métricas son:

• Acoplamiento: Se mide como el número de interconexiones entre módulos. El acoplamiento crece con el número de llamadas, o con la cantidad de datos compartidos. Se supone que un diseño con un acoplamiento alto puede contener más errores. Se cuantifica como el número de conexiones por nodo del gráfico de diseño.

• Cohesión: Valora las relaciones entre los elementos de un módulo. En un diseño cohesivo, las funciones están ubicadas en un solo módulo. Los diseños con una cohesión baja contendrán más errores. Las medidas que valoren la información compartida entre módulos cuantificarán la cohesión.

• Complejidad: Un diseño debe ser lo más simple posible. La complejidad crece con el número de construcciones de control, y el número de módulos de un programa. Un diseño complejo contendrá más errores. La complejidad se evidencia en el número de elementos del gráfico de diseño.

• Modularidad: El grado de modularidad afecta a la calidad del diseño. Es preferible un exceso a un defecto de modularidad, pues en este último caso contendrá más errores. La cuantificación de la modularidad se obtiene midiendo la cantidad de elementos del gráfico.

• Tamaño: Un diseño con grandes módulos, o gran profundidad en su gráfico contendrá más errores. De hecho, complejidad y tamaño están muy relacionados y las consecuencias de un exceso de cualquiera de los dos principios tiene los mismo resultados.

Las conclusiones finales del estudio sugieren que a pesar de la correlación encontrada entre los factores estudiados y los errores encontrados, sigue habiendo una serie de factores no detectados que determinan a su vez la calidad de un diseño. De todas formas es posible afirmar que las interconexiones entre módulos, y la complejidad de los diseños aumentan notablemente los errores, y disminuyen la calidad.

2 - 27

2.1.7.- Otra métricas del software.

Además de las mencionadas, existen algunos otras métricas que valoran ciertos aspectos del software.

Las métricas de reusabilidad tratan de medir el grado en que un elemento software puede ser empleado por otros programas, en otras palabras, su independencia. Debido a que es difícil valorar objetivamente esta independencia, la referencia más común es la independencia del hardware expresada en número de cambios en el código al adaptar un programa a una nueva plataforma. Esta medida puede ampliarse al número de cambios realizados en el código por líneas al adaptarlo a un nuevo sistema operativo, o a un nuevo sistema gráfico. Las métricas de portabilidad valoran aspectos muy similares.

Las métricas de mantenibilidad se enuncian como función de los valores de concisión, consistencia, instrumentación, modularidad, autodocumentación y simplicidad.

Las métricas de testeabilidad (o capacidad de probar el software) son función de la auditabilidad (capacidad de someter el software a auditoría), la complejidad de software (ciclomática, contando los GOTO y bucles, o según los valores de la Ciencia del Software), instrumentación, modularidad, autodocumentación y simplicidad.

Las de flexibilidad tienen como componentes a la complejidad, la concisión, la consistencia, la expandibilidad, la generalidad, la autodocumentación, y la simplicidad.

La interpretación que se da de los componentes de cada una de estas métricas es, no obstante, discutible e imprecisa, sin un método definido para obtener una valoración. También se carece de expresiones que determinen el peso que cada componente tiene en la métrica.