Clase 9: Medición de productos software Calidad de Software Por: Ing. Belén Bonilla Medición del producto La medición del producto está centrada en evaluar la calidad de los entregables. Los productos del software son las salidas del proceso de producción del software que incluye todos los artefactos entregados o documentos que son producidos durante el ciclo de vida de software. Líneas de código (LOC): Es la métrica más popular a nivel de código de programa. Es la suma de las líneas de código, sin incluir las líneas que son comentarios o líneas en blanco. Métricas a nivel de código Para poder emplearla efectivamente, hay que determinar lo que implica una línea de código en la evaluación. En SonarQube: Número de líneas físicas que contienen al menos un carácter que no es un espacio en blanco ni una tabulación ni parte de un comentario. Métricas a nivel de código Líneas de código (LOC): De forma típica, los softwares con más líneas de código poseen una complejidad más alta y son más difíciles de mantener que aquellos con menos líneas de código. Líneas de comentario (CLOC): Número de líneas que contienen comentarios o código comentado. En SonarQube: Las líneas de comentario no significativas (líneas de comentario vacías, líneas de comentario que contienen sólo caracteres especiales, etc.) no aumentan el número de líneas de comentario. Métricas a nivel de código Densidad de comentarios: Nos da idea de hasta cuánto está documentado el código. Métricas a nivel de código 𝐶𝐿𝑂𝐶 𝐷𝑒𝑛𝑠𝑖𝑑𝑎𝑑 𝑑𝑒 𝑐𝑜𝑚𝑒𝑛𝑡𝑎𝑟𝑖𝑜𝑠 = ×100% 𝐿𝑂𝐶 + 𝐶𝐿𝑂𝐶 • 50% significa que el número de líneas de código es igual al número de líneas de comentarios. • 100% significa que el archivo solo contiene líneas de comentarios Métricas a nivel de código Complejidad ciclomática: Es una métrica cuantitativa que refleja la complejidad del software. Una de las técnicas más reconocidas para el cálculo de la complejidad ciclomática es la propuesta por Thomas J. McCabe, y que se le conoce como “Complejidad ciclomática de McCabe”. Según McCabe, una complejidad ciclomática superior a 10 por lo general indica que un algoritmo es complejo y con más probabilidades de errores y más difícil de mantener y probar. Por tanto la correlación es positiva: A mayor valor de la complejidad ciclomática, mayor complejidad, mayor densidad de errores, mayor esfuerzo de mantenimiento y pruebas. Complejidad ciclomática en SonarQube: Es una medida del número mínimo de caminos posibles a través del código y, por lo tanto, el número de pruebas requeridas, ¡no se trata de la calidad del código! Métricas a nivel de código La complejidad ciclomática es igual al número de puntos de decisión + 1. Algunos puntos de decisión: if, while, do, for,?:, catch, enunciados case y operadores && y ||…Se debe revisar los criterios específicos por lenguaje para tener una lista completa, según el caso. Ver: https://docs.sonarqube.org/latest/user-guide/metricdefinitions/ Ejemplo… Métricas a nivel de código Complejidad ciclomática en SonarQube: Según la teoría pura, el nivel 1-4 se considera fácil de probar, 5-7 OK, 8-10 considere refactorizar para facilitar las pruebas y 11+ refactorice ahora, ya que las pruebas serán tediosas. Cuando se trata de la medición de la calidad del código mediante esta métrica (no aconsejable, preferir complejidad cognitiva), el nivel 10 es un nivel muy bueno como objetivo final. Se puede tener un nivel de complejidad 15 o incluso superior, pero manténgalo por debajo de 20 para detectar automáticamente el código realmente mal diseñado. Complejidad cognitiva de SonarQube: Es una medida que nos permite aproximar qué tan difícil es entender el flujo de control del código. Métricas a nivel de código La complejidad cognitiva se evalúa de acuerdo con tres reglas básicas: 1. Ignorar las estructuras que permiten que varios enunciados sean legibles abreviados en uno. 2. Incrementar (agregar uno) por cada ruptura en el flujo lineal del código. 3. Incrementar cuando las estructuras que rompen el flujo están anidadas. Complejidad cognitiva de SonarQube Se evalúan incrementos para: Estructuras de bucle: for, foreach, while, do while Métricas a nivel de código Estructuras condicionales: operadores ternarios, if Ambos tipos se evalúan con un incremento (+1) por la propia estructura, más un incremento (+1) por cada nivel de anidación en el que estén. Para else if, else se evalúa +1 por la propia estructura. No se evalúa ningún incremento de anidación para estas estructuras porque el costo mental ya se “pagó” al leer el if. Switch y todos sus case representan en total un incremento. Complejidad cognitiva de SonarQube Se evalúan incrementos para: Métricas a nivel de código Secuencia de operadores lógicos: la complejidad cognitiva no se incrementa para cada operador lógico binario sino que evalúa un incremento para cada secuencia de operadores lógicos binarios. Debido a que las expresiones booleanas se vuelven más difíciles de entender con operadores mixtos, la complejidad cognitiva aumenta para cada nueva secuencia de operadores similares. Ejemplo: if(a && b && c || d || e && f) if (a // +1 por el if && b && c // +1 || d || e // +1 && f) // +1 Complejidad cognitiva de SonarQube Se evalúan incrementos para: Métricas a nivel de código Catch: Un catch representa una especie de rama en el flujo de control tanto como un if. Por lo tanto, cada cláusula de catch da como resultado un incremento estructural de la complejidad cognitiva. Un catch solo agrega un punto a la puntuación de complejidad cognitiva, sin importar cuántos tipos de excepciones se detecten. Los bloques de try y finally se ignoran por completo. Saltos a labels: goto agrega un incremento a la complejidad cognitiva, así como break o continue a un label (o número en algunos lenguajes) (goto label, break label, continue label, break number, continue number) Entre otras reglas más que se pueden ampliar en: https://www.sonarsource.com/docs/CognitiveComplexity.pdf Métricas clásicas a nivel de código Complejidad cognitiva de SonarQube Resumen de reglas Hay un incremento por cada una de estas estructuras: Las siguientes estructuras incrementan el nivel de anidación Las siguientes estructuras reciben un incremento de anidación acorde con su profundidad anidada dentro de las estructuras indicadas en la segunda columna if, else if, else, operador ternario, switch, for, foreach, while, do while catch, goto LABEL, break LABEL, continue LABEL, break NUMBER, continue NUMBER, secuencias de operadores lógicos, cada método en un ciclo de recursión. if, else if, else, operador ternario, switch, for, foreach, while, do while, catch if, operador ternario, switch, for, foreach, while, do while, catch Otras métricas de mantenibilidad Cantidad de code smells: Métricas a nivel de código Los code smells son síntomas en el código que indican que tal vez no se están haciendo las cosas de una forma del todo correcta, lo que puede llevar a que haya algún problema a futuro y un problema de trasfondo. Un problema relacionado con la mantenibilidad en el código. Dejarlo como está significa que, en el mejor de los casos, los mantenedores tendrán más dificultades de lo que deberían para realizar cambios en el código. En el peor de los casos, estarán tan confundidos por el estado del código que introducirán errores adicionales a medida que realicen cambios. Otras métricas de mantenibilidad Cantidad de code smells: Son ejemplos de code smells: Métricas a nivel de código • Código duplicado • Operador switch complejo o demasiadas sentencias if • Métodos muy largos (más de 10 líneas de código) • Muchos parámetros para un método (más de 3 ó 4) • Demasiados comentarios • Entre otros (Para más información, revisar: https://refactoring.guru/es/refactoring/smells ) Otras métricas de mantenibilidad Deuda técnica: Métricas a nivel de código Esfuerzo para corregir todos los code smells. En el caso de SonarQube, la medida se almacena en minutos en la base de datos y se asume un día de 8 horas cuando los valores se muestran en días. Cada issue (un tipo de issue son los code smells) tiene asociado un “remediation cost” (tiempo estimado para reparar un issue, tiempo de reparación); esto podría ser 1 m, 5 m, 30 m, etc. La suma de todos estos costos de reparación para los code smells (issues de mantenibilidad) para un proyecto, es la deuda técnica. Otras métricas de mantenibilidad Proporción de deuda técnica: Métricas a nivel de código Relación entre el costo de desarrollar el software y el costo de repararlo. La fórmula es: 𝐶𝑜𝑠𝑡𝑜 𝑑𝑒 𝑟𝑒𝑝𝑎𝑟𝑎𝑐𝑖ó𝑛 𝑃𝑟𝑜𝑝𝑜𝑟𝑐𝑖ó𝑛 𝑑𝑒 𝑑𝑒𝑢𝑑𝑎 𝑡é𝑐𝑛𝑖𝑐𝑎 = 𝐶𝑜𝑠𝑡𝑜 𝑑𝑒 𝑑𝑒𝑠𝑎𝑟𝑟𝑜𝑙𝑙𝑜 Que se puede reformular como: 𝑃𝑟𝑜𝑝𝑜𝑟𝑐𝑖ó𝑛 𝑑𝑒 𝑑𝑒𝑢𝑑𝑎 𝑡é𝑐𝑛𝑖𝑐𝑎 = 𝐶𝑜𝑠𝑡𝑜 𝑑𝑒 𝑟𝑒𝑝𝑎𝑟𝑎𝑐𝑖ó𝑛 𝐶𝑜𝑠𝑡𝑜 𝑑𝑒 𝑑𝑒𝑠𝑎𝑟𝑟𝑜𝑙𝑙𝑎𝑟 𝑢𝑛𝑎 𝑙í𝑛𝑒𝑎 𝑑𝑒 𝑐ó𝑑𝑖𝑔𝑜 ×𝑛ú𝑚𝑒𝑟𝑜 𝑑𝑒 𝑙í𝑛𝑒𝑎𝑠 𝑑𝑒 𝑐ó𝑑𝑖𝑔𝑜 El valor del costo para desarrollar una línea de código es de 0.06 días. Paréntesis obligatorio Métricas a nivel de código Refactorización: El objetivo principal de la refactorización es combatir la deuda técnica. Transforma un desorden en código limpio y diseño simple. Para cada code smell existe una o más técnicas de refactorización que permitirán eliminarlo. Por ejemplo, los code smells “Long method” y “Duplicate code” pueden eliminarse empleando la técnica de refactorización “Extract method” en conjunto con otras técnicas. (Para más información: https://refactoring.guru/es/refactoring/techniques ) Métricas a nivel de código Algunas métricas de fiabilidad: Número de bugs: Un bug es un error de codificación que romperá el código y debe corregirse de inmediato. En SonarQube es uno de los tres tipos de issues (en conjunto con los code smells y las vulnerabilidades). Métricas a nivel de código Algunas métricas de fiabilidad: Esfuerzo de reparación de fiabilidad: Esfuerzo para solucionar todos los errores (bugs). En SonarQube, la medida se almacena en minutos en la base de datos. Se asume un día de 8 horas cuando los valores se muestran en días. Métricas orientadas a objetos a nivel de diseño Existen varias propuestas como: • Métricas MOOSE (las más utilizadas) • Métricas MOOD • Métricas de Lorenz y Kidd Métricas orientadas a objetos a nivel de diseño Métodos ponderados por clase (WMC, Weighted Methods per Class): Considere una clase C1, con métodos M1, M2, …, Mn que son definidos en la clase. Con c1, c2, …,cn como la complejidad de los métodos, entonces: ? 𝑊𝑀𝐶 = % 𝑐< <=> Métodos ponderados por clase (WMC, Weighted Methods per Class): Métricas orientadas a objetos a nivel de diseño • Es un predictor de cuánto tiempo y esfuerzo se requiere para desarrollar y mantener una clase. • Clases con un largo número de métodos pertenecen, con mayor probabilidad, a aplicaciones específicas, limitando la posibilidad de reutilización. • A mayor cantidad de métodos en una clase, mayor el impacto potencial en las clases hijas. • A mayor complejidad entre todos los métodos de una clase, mayor dificultad para la mantenibilidad. La complejidad de los métodos se puede determinar con la complejidad cognitiva. Métricas orientadas a objetos a nivel de diseño Profundidad del árbol de herencia de una clase (DIT, Depth of Inheritance Tree): Mide el nivel máximo de herencia a través de la longitud entre un nodo y el nodo raíz en una jerarquía de clases. Es decir, es una medida de cuántas clases ancestras pueden potencialmente afectar* a una clase (la que está siendo evaluada). En casos que involucran herencia múltiple, DIT será la longitud máxima desde el nodo (clase evaluada) hasta el nodo raíz. * Debido a que cuanto mayor sea el nivel de profundidad de herencia de una clase, mayor es el número de métodos y atributos que hereda de otras clases. Métricas orientadas a objetos a nivel de diseño Ejemplo 1: La clase C hereda de la clase B, la cual hereda de la clase A. Entonces: DIT(A) = 0 DIT(B) = 1 DIT(C) = 2 Métricas orientadas a objetos a nivel de diseño Ejemplo 2: DIT(A) = 0 DIT(B) = DIT(C) = 1 DIT(D) = DIT(E) = 2 Métricas orientadas a objetos a nivel de diseño Interpretación: • Cuanto más profunda es una clase en la jerarquía de clases, mayor el grado de herencia de métodos, haciendo más complicado predecir su comportamiento (el de la clase evaluada) • Árboles más profundos constituyen mayor complejidad de diseño, ya que más métodos y clases están involucrados. • Cuanto más profunda es una clase en la jerarquía de clases, mayor el potencial de reutilización de métodos heredados. Métricas orientadas a objetos a nivel de diseño Interpretación: Es decir, un valor alto para la métrica DIT indica que el nivel de complejidad es alto para esa clase. Esto también significa que hay un alto número de atributos y métodos que son heredados, lo cual indica que hay reutilización de código a través de la herencia pero puede ser más difícil entonces de predecir el comportamiento de la clase. Un valor menor para DIT indica menos complejidad pero también significa que hay menos reutilización de código por herencia. La herencia es un concepto poderoso de la POO y los diseños deben esforzarse por la reutilización de código. Por lo tanto, debe alcanzarse un balance entre la reutilización de código y la complejidad. Métricas orientadas a objetos a nivel de diseño Recomendación: Los valores ideales por convención son entre 2 y 3. Si para la mayoría de las clases, la métrica DIT está por debajo de 2, esto nos indica una pobre explotación de las ventajas del diseño orientado a objetos y la herencia. Métricas orientadas a objetos a nivel de diseño Número de hijos (NOC, Number of Children): Es el número de clases inmediatas subordinadas a una clase en la jerarquía, es decir, la cantidad de subclases que pertenecen a una clase. NOC es una medida de cuántas subclases van a heredar los métodos de una clase padre. Interpretación: Métricas orientadas a objetos a nivel de diseño • A mayor número de hijos, mayor la reutilización, ya que la herencia es una forma de reutilización. • A mayor el número de hijos, mayor es la probabilidad de una abstracción inapropiada de la clase padre. Si una clase tiene muchos hijos, puede ser un caso de mal uso de las subclases. • El número de hijos da una idea de la influencia potencial que tiene una clase en el diseño. Si una clase tiene un muchos hijos, puede requerir más pruebas de los métodos en esa clase (porque el impacto de un defecto en el padre trascendería a los hijos). Métricas orientadas a objetos a nivel de diseño Interpretación: Entonces, NOC es un indicador de: • El nivel de reutilización • La posibilidad de haber creado abstracciones erróneas • El nivel de pruebas requerido Métricas orientadas a objetos a nivel de diseño Recomendación: Los valores ideales por lo regular, son entre 2 y 3 ya que se suele aconsejar que se seleccionen los mismos valores ideales (y también los límites) que en DIT. Métricas orientadas a objetos a nivel de diseño Ejemplo: NOC(A) = NOC(B) = 2 NOC(C) = 1 NOC(D) = NOC(E) = 0 Métricas orientadas a objetos a nivel de diseño Acoplamiento entre objetos (CBO, Coupling Between Objects): CBO para una clase es el número total de otras clases con las cuales se encuentra acoplada. Recordemos que dos clases están acopladas cuando métodos declarados en una clase usan métodos o instancian (o acceden a) variables definidas por la otra clase. Si una clase A usa una clase B, se dice que A depende de B. Esto es, A no puede realizar su trabajo sin B, por lo tanto, existe acoplamiento entre las clases A y B. Interpretación: Métricas orientadas a objetos a nivel de diseño • Acoplamiento excesivo entre clases perjudica el diseño modular y previene la reutilización. Entre más independiente una clase es, más fácil será reutilizarla en otra aplicación. • Con miras a incrementar la modularidad y promover la encapsulación, el acoplamiento entre clases debe mantenerse al mínimo. A mayor número de acoplamiento, más sensitivo será realizar cambios en otras partes del diseño, y por lo tanto el mantenimiento es más difícil. • Una medida del acoplamiento es útil para determinar cuán complejo probablemente será realizar las pruebas a varias partes del diseño. A mayor acoplamiento entre clases, más rigurosas deben ser las pruebas y más serían más difíciles de realizar. Entonces es una métrica útil para predecir esfuerzo necesario en mantenimiento y pruebas. Métricas orientadas a objetos a nivel de diseño Recomendación: Un valor de 0 indica que una clase no tiene relación con ninguna otra clase en el sistema y, por lo tanto, no debe ser parte del diseño. Un valor entre 1 y 4 es bueno, ya que indica que la clase está débilmente acoplada. Un número más alto que esto puede indicar que la clase está demasiado acoplada con otras clases en el diseño, lo que complicaría las pruebas y modificaciones, y limitaría las posibilidades de reutilización. Métricas orientadas a objetos a nivel de diseño Ejemplo 1: CBO(Cuenta) = 0 CBO(Cliente) = 2 CBO(TarjetaCredito) = 2 CBO(AutorizacionTarjeta) = 0 Enfoque de Abreu y Melo, 1996 Métricas orientadas a objetos a nivel de diseño Ejemplo 2: CBO(A) = 2 CBO(B) = 2 CBO(C) = 0 CBO(D) = 1 CBO(E) = 0 Enfoque de Abreu y Melo, 1996 Métricas orientadas a objetos a nivel de diseño CBO(Professor) = 2 Proporción de acomplamiento entre clases (CF): Métricas orientadas a objetos a nivel de diseño CF se define como la proporción entre el máximo número posible de acoplamientos en el sistema y el número real de acoplamientos no imputables a herencia. 𝑪𝑭 = 𝑻𝑪 ∑𝑻𝑪 ∑ 𝒊"𝟏 𝒋"𝟏 𝒆𝒔_𝒄𝒍𝒊𝒆𝒏𝒕𝒆(𝒄𝒊 , 𝒄𝒋 ) 𝑻𝑪𝟐 − 𝑻𝑪 Donde: TC = número de clases La relación cliente-proveedor (𝐶! ⇒ 𝐶" ) representa que la clase cliente (𝐶! ) contiene al menos una referencia no heredada de la clase proveedor (𝐶" ).