En este tutorial nos enfocaremos en el cálculo, análisis y corrección de la complejidad del software desde la perspectiva de su calidad y mantenimiento. Para ello seguiremos un proceso que consta de cuatro pasos, similar al descrito en el tutorial para el cálculo del acoplamiento y la cohesión. El proceso de análisis incluye: (1) Una medida diagnóstica del cálculo de las métricas de complejidad de un programa de software. (2) A partir del resultado obtenido, realizaremos un análisis de las métricas identificando los puntos más críticos que presentan una alta complejidad dentro del programa. (3) Una vez identificados estos puntos, aplicaremos las técnicas de refactoring del código apropiadas, con el objetivo de reducir la complejidad del programa. (4) Finalmente, utilizaremos nuevamente las métricas para verificar el trabajo que los refactorings aplicados efectivamente tuvieron un efecto positivo.
Para desarrollar este tutorial utilizaremos un programa en Java para el manejo del sistema de transporte de una ciudad (transporte de buses) que consiste en los horarios de rutas por día.
BusTransport:
este es el proyecto que analizaremos desde la perspectiva de la complejidad del código. El proyecto está disponible en el siguiente link: (
https://github.com/MOOC-tutorials/TransportSystem
)
Sonargraph:
esta herramienta la utilizaremos para medir la calidad del programa desde la perspectiva de la complejidad (https://www.hello2morrow.com).Antes de comenzar con el análisis de la complejidad del programa ejemplo, debemos configurar el proyecto dentro de Sonargraph. En este caso, trabajaremos con un proyecto base de Java creado en Eclipse. El proceso para trabajar con otros lenguajes es similar al descrito a continuación.
Para ello crearemos un nuevo sistema siguiendo la ruta File > New > System > New System...
dentro del menú del programa. Una vez realizado esto, llenaremos el nombre y dirección del proyecto a ejecutar como se muestra en la Figura 1.
Figura 1. Creación del sistema.
Ahora que tenemos un proyecto vacío, debemos cargar los diferentes módulos del programa. Para ello diríjase a File > New > Module > New Java Module...
Ahora deben darle un nombre al módulo, Main
en nuestro ejemplo. La Figura 2 muestra este proceso.
Figura 2. Creación del módulo.
Ahora debemos buscar los Root Directories
del programa. Para ello, hagan clic derecho sobre el módulo Main
, buscando Manage Java Root Directories/Archives...
dentro del menú Root Directory
como se muestra en la Figura 3.
Figura 3. Selección de directorios para el módulo.
Para agregar los directorios podemos usar el botón de Detect
que mostrará todos los directorios asociados. Dentro de los directorios disponibles seleccionamos los directorios que queremos analizar (source y binary) y los arrastramos hasta el módulo Main
, como se muestra en la Figura 4.
Figura 4. Adición de los directorios al módulo.
Una vez finalizado el proceso de carga del Root Directory
, debemos refrescar el proyecto (utilizando el botón en la esquina superior derecha) para generar el análisis inicial del programa, como se muestra en la Figura 5.
Figura 5. Análisis inicial.
Para mejorar la calidad de un proyecto por medio de refactorings, primero es necesario comenzar con un análisis preliminar o diagnóstico, que provea información sobre el estado actual del programa.
En este caso nos concentramos en el análisis de la complejidad del programa a nivel de sus métodos y módulos. En Sonargraph, esta métrica está definida dentro de la vista de la complejidad, como se muestra en la Figura 6. En el caso particular de nuestra aplicación de transporte, observamos que el proyecto presenta una complejidad de 18.20, superior al perfil de calidad aceptado para la métrica de calidad (< 15), como se explica en el video Complejidad de Software. Adicionalmente, podemos ver que la cantidad de instrucciones en métodos complejos es de 265, que es muy alta para el tamaño del proyecto, 2467 LOC. Figura 6. Análisis de la complejidad del proyecto.
Teniendo en cuenta el diagnóstico de nuestro programa, podemos proceder a analizar el resultado de las métricas con el objetivo de identificar los puntos del programa que presentan problemas de complejidad.
Una vista detallada de las métricas de calidad de complejidad, como se muestra en la Figura 7, permite ver que el promedio de instrucciones por método es de 18.2, que es superior al perfíl de calidad aceptado.
Figura 7. Métricas del cálculo de la complejidad.
Para reducir la complejidad del código debemos reducir la cantidad de instrucciones promedio de los métodos. A continuación listamos algunos de los posibles refactorings a aplicar.
deleteNodeItem
, deleteNodeKnown
y add
en la clase DoublyLinkedList
es posible mover la instrucción que reduce la variable size
como la última instrucción del condicional.Dentro del constructor de la clase EdgeWeightGraph
, podemos desplazar la instrucción dentro del segundo ciclo al primer ciclo, eliminando así el segundo ciclo.
deleteNodeItem
y deleteNodeKnown
en la clase DoublyLinkedList
a una nueva función deleteNode
conteniendo el condicional. De forma similar, podemos extraer múltiples métodos del método check
en la clase DijkstraSP
. Los métodos a extraer corresponden a la funcionalidad descrita en cada uno de los comentarios del método original.
Podemos extraer múltiples métodos del método check
en la clase MST
. Los métodos a extraer corresponden a la funcionalidad descrita en cada uno de los comentarios del método original.
Dentro de la clase STSManager
podemos extraer el código de la impresión de errores de las diferentes funciones del programa a un nuevo método para simplificar la lectura de los métodos.
STSManager
podemos eliminar las variables auxTripsConStops
y rangoHoras
, y las librerías importadas que no son referenciadas.Dentro de la clase DoublyLinkedList
podemos eliminar la variable iter
y los métodos prior
y hasPrior
que nunca son referenciados.
loadStopTimes
de la clase STSManager
, podemos descomponer condicionales eliminando la rama del then
y desplazando la instrucción v0=v1
después del condicional.Su tarea es realizar las modificaciones correspondientes dentro del proyecto.
Ahora que ya hemos finalizado las tareas de refactoring de nuestro programa, podemos volver a realizar el análisis para observar si los refactorings efectivamente tuvieron un efecto positivo reduciendo la complejidad del software. La Figura 8 muestra una reducción de la complejidad a 11.99, que ya se encuentra a un nivel aceptable de acuerdo al perfil de calidad para esta métrica. Noten que para reducir aún más la complejidad, podríamos reestructurar algunas de las clases del programa (e.g., (Min/Max)PriorityQ
).
Figura 8. Análisis del proyecto después del refactoring.
Noten que el proceso entre la refactorización del código y la verificación de la mejora de las métricas es iterativo, repitiendo los pasos de refactoring hasta que la verificación alcance el nivel aceptado del perfil de calidad que se está evaluando.
© - Derechos Reservados: La presente obra, y en general todos sus contenidos, se encuentran protegidos por las normas internacionales y nacionales vigentes sobre propiedad Intelectual, por lo tanto su utilización parcial o total, reproducción, comunicación pública, transformación, distribución, alquiler, préstamo público e importación, total o parcial, en todo o en parte, en formato impreso o digital y en cualquier formato conocido o por conocer, se encuentran prohibidos, y solo serán lícitos en la medida en que se cuente con la autorización previa y expresa por escrito de la Universidad de los Andes.
De igual manera, la utilización de la imagen de las personas, docentes o estudiantes, sin su previa autorización está expresamente prohibida. En caso de incumplirse con lo mencionado, se procederá de conformidad con los reglamentos y políticas de la universidad, sin perjuicio de las demás acciones legales aplicables.
Recursos Digitales
Profesor Nicolás Cardozo
Facultad de Ingeniería
Departamento de Ingeniería de Sistemas y Computación
Universidad de los Andes
Bogotá, Colombia
Octubre, 2022