TESIS DE GRADO EN INGENIERÍA DE SISTEMAS
COMPARACIÓN DE DOS HERRAMIENTAS PARA
REFACTORIZACIÓN DE
BRAIN METHODS
Por
Scafati, Diego Ariel
Director: Diaz-Pace, Andres Co-Director: Vidal, Santiago A.
FACULTAD DE CIENCIAS EXACTAS
UNIVERSIDAD NACIONAL DEL CENTRO DE LA PROVINCIA DE BUENOS AIRES
Índice
Índice ... 1
Índice de Figuras ... 3
Índice de Códigos fuente ... 4
Índice de Tablas ... 4
Índice de Fórmulas ... 4
Capítulo 1: Introducción ... 5
1.1 Motivación ... 5
1.2 Objetivos ... 6
1.3 Estructura de la tesis ... 8
Capítulo 2: Marco Teórico ... 9
2.1 Calidad de software ... 9
2.1.1 Definición de calidad de software ... 9
2.1.2 Mantenibilidad ... 9
2.2 Evolución y mantenimiento de sistemas ... 11
2.2.1 Definición de evolución y mantenimiento de software ... 11
2.2.2 Problemas en la evolución y mantenimiento de software ... 11
2.2.3 Código legado ... 12
2.2.4 Soluciones propuestas ... 12
2.3 Code Smell ... 13
2.3.1 Definición de Code Smell ... 13
2.3.2 Impacto de los Code Smells ... 13
2.3.3 Surgimiento de Code Smells ... 13
2.3.4 Identificación de Code Smells ... 14
2.3.4.1 Métricas ... 14
2.3.4.2 Umbrales ... 14
2.3.4.3 Utilidad de las métricas ... 15
2.3.5 Long Method y Brain Method ... 15
2.4 Refactoring ... 18
2.4.1 Definición ... 18
2.4.2 Aplicación de refactorizaciones ... 19
2.4.3 Extract Method ... 19
2.4.4 Problemas de la refactorización de código ... 20
2.4.5 Minimización de riesgos ... 20
3.1 Automatización del refactoring mediante herramientas de software ... 21
3.1.1 Descomposición del proceso de refactoring ... 21
3.2 Trabajos relacionados ... 21
3.2.1 Automed ... 21
3.2.2 PMD ... 22
3.2.3 Checkstyle ... 23
3.2.4 Herramientas integradas en entornos de desarrollo ... 23
3.2.5 JSpIRIT y Bandago ... 24
3.2.5.1 JSpIRIT ... 24
3.2.5.2 Bandago ... 24
3.2.5.2.1 Simulated Annealing ... 25
3.2.6 JDeodorant ... 27
3.2.6.1 Program Slicing ... 28
3.2.7 Benefactor... 31
3.2.8 Comparación de las herramientas disponibles ... 31
3.2.9 Trabajos relacionados sobre la comparación de la efectividad de las herramientas ... 34
3.3 Conclusiones ... 35
Capítulo 4: Experimento #1 - Comparación inicial entre Bandago y JDeodorant ... 36
4.1 Estructura del experimento ... 36
4.2 Resultados ... 38
4.3 Discusión ... 43
Capítulo 5: Extensiones de Bandago y de JDeodorant ... 45
5.1 Extension en JDeodorant ... 45
5.1.1 Simulated Annealing para la solución automática de Long Methods ... 45
5.1.2 Búsqueda con Backtracking ... 48
5.1.3 Búsqueda con Backtracking sobre Brain Methods ... 50
5.2 Extensión en Bandago ... 53
Capítulo 6: Experimento #2 - Comparación de las Herramientas ... 56
6.1 Estructura del experimento ... 56
6.2 Resultados ... 58
6.3 Conclusión ... 63
Capítulo 7: Conclusiones ... 64
7.1 Contribuciones... 64
7.2 Limitaciones ... 65
Referencias ... 67
Anexos ... 71
Anexo 1: Resultados de la ejecución manual de la herramienta JDeodorant sin
modificaciones, por cada método en cada uno de los 10 proyectos. ... 72
Anexo 2: Resultados de la ejecución automática de la herramienta JDeodorant post
modificaciones, por cada clase en cada uno de los 10 proyectos. ... 86
Anexo 3: Resultados de la ejecución automática de la herramienta Bandago post
modificaciones, utilizando el operador Random, por cada clase en cada uno de los 10
proyectos. ... 97
Índice de Figuras
Figura 1.1 Diagrama de contexto del funcionamiento de la herramienta JDeodorant. ... 7 Figura 1.2 Diagrama de contexto del funcionamiento de la herramienta Bandago. ... 7 Figura 1.3 Diagrama de contexto del funcionamiento de la herramienta JDeodorant
Figura 6.3 Cantidad de Brain Methods corregidos por JDeodorant y Bandago extendidos, por proyecto, expresada en porcentajes. ... 60 Figura 6.4 Cantidad de Brain Methods corregidos por JDeodorant y Bandago extendidos, expresada en porcentajes. ... 60 Figura 6.5 Tiempos de ejecución de la herramienta JDeodorant extendida. ... 61 Figura 6.6Tiempos de ejecución de la herramienta Bandago extendida. ... 62
Índice de Códigos fuente
Código Fuente 2.1 Ejemplo de Long Method ... 17
Código Fuente 3.1 Pseudocódigo para el algoritmo de Simulated Annealing implementado
en Bandago. ... 26 Código Fuente 5.1 Pseudocódigo para la generación de estados vecinos ... 47
Código Fuente 5.2 Pseudocódigo para la función de Backtracking ... 50
Código Fuente 5.3 Pseudocódigo para un operador soportando conjuntos de sentencias no contiguas. ... 55
Índice de Tablas
Tabla 3.1 Clasificación de las herramientas de acuerdo a su enfoque. ... 32 Tabla 3.2 Comparación de las herramientas de acuerdo a su disponibilidad. ... 33 Tabla 3.3 Comparación de las herramientas de acuerdo al enfoque de identificación, nivel de automatización, y validez de las extracciones. ... 34 Tabla 4.1 Detalle de las herramientas analizadas. ... 37 Tabla 5.1Ejemplo de los valores para las métricas de los primeros 4 estados analizados en la clase CalendarImporter del proyecto Columba. ... 52 Tabla 5.2 Ejemplo de los valores normalizados para las métricas de los primeros 4 estados analizados en la clase CalendarImporter del proyecto Columba. ... 52 Tabla 6.1 Comparación de los tiempos de ejecución de JDeodorant y Bandago antes y después de ser extendido. ... 63
Índice de Fórmulas
Fórmula 2.1 Definición de la métrica LOC mediante la utilización de umbrales. ... 17 Fórmula 2.2 Composición de Métricas para definir el Code Smell de Tipo Brain Method. ... 18 Fórmula 3.1 Cálculo del beneficio de una extracción. ... 22 Fórmula 5.1 Definición de la función para determinar el mejor estado parcialmente
Capítulo 1: Introducción
La tarea de desarrollar software de calidad para grandes sistemas conlleva problemas y desafíos, a pesar de los numerosos esfuerzos realizados para mejorar la habilidad de las personas para producir software de forma predecible y eficiente en términos de costos [1]. En general, estos problemas se deben a la inevitable evolución de los sistemas de software, causada por cambios en el entorno del sistema. Estos cambios incluyen, por ejemplo, cambios de requerimientos, cambios de tecnología, y cambios de personal, entre otros. Esta evolución lleva a la necesidad de efectuar un proceso de mantenimiento que modifica el código fuente del software.
La evolución del software fue estudiada por medio de “leyes” en el trabajo de Lehman [2, 3], las cuales hacen hincapié en el hecho de que el software debe evolucionar continuamente para seguir siendo útil, y que dicha evolución viene acompañada de un aumento de la complejidad y del esfuerzo que tendrán que ser invertidos para controlar este crecimiento. Esto se debe a que, a medida que los desarrolladores realizan modificaciones en el código de un sistema, el código tiende a perder su estructura y se hace más difícil comprender el diseño mediante la simple lectura del código. Esto ocurre principalmente cuando los cambios se realizan sin una plena comprensión del diseño del código. La pérdida de la estructura del código tiene un efecto acumulativo: cuanto más difícil se hace ver el diseño en él, más difícil es preservarlo, y más rápidamente dicho diseño tiende a erosionarse [4].
Entre los problemas que afectan la comprensibilidad del código y su diseño se encuentran: la duplicación de código y los métodos excesivamente largos (en términos de líneas de código o cantidad de sentencias). Estos dos problemas fueron identificados por Fowler y Beck [5, 6] como problemas que indican la necesidad de refactorizar una pieza de software, para mejorar su calidad y evitar la degradación del sistema.
1.1 Motivación
Una pregunta importante es cómo se puede controlar la complejidad del software. Una respuesta es a través del diseño orientado a soportar el cambio, el cuál abarca: técnicas de
separación de concerns, ocultamiento de información, uso de mecanismos de herencia y
polimorfismo, separación del diseño en capas, mantenimiento actualizado de la documentación, y desarrollo orientado a pruebas, entre otras estrategias.
Una estrategia común es la reingeniería de software, en la cual se modifica un sistema existente para mejorar su diseño y reducir la complejidad. Uno de los enfoques más utilizados consiste en la identificación de potenciales problemas de software por medio de los llamados
“Code Smells”. Un Code Smell es un conjunto de características presentes en el código, las
cuales pueden cuantificarse mediante métricas, y sugieren la existencia de un problema de
diseño en el código. Para eliminar estos Code Smells, se han identificado técnicas de
refactorización de código (refactoring) las cuales están normalmente asociadas a cada Code
Smell en particular. Estas técnicas consisten en distintas formas de modificar el código fuente
Varias herramientas existentes (por ej., JDeodorant, Bandago, AutoMed, entre otras) asisten a los desarrolladores en diferentes partes del proceso de refactorización, aunque no proveen una automatización completa de dicho proceso. Esta automatización es ideal y necesaria, dado que a menudo resulta inviable o poco práctico realizar refactorizaciones de forma manual, principalmente en grandes bases de código, por el tiempo que esto demora y por la probabilidad de introducir nuevos problemas o realizar accidentalmente refactorizaciones
inválidas. En particular, las herramientas existentes suelen enfocarse solamente en el Code
Smell de tipo Long Method y no así en el Code Smell Brain Method, el cual se define como
como una extensión del primero e indica problemas de forma más precisa.
1.2 Objetivos
El objetivo de este trabajo es realizar un estudio sobre dos herramientas relevantes de la
literatura para refactorización de Code Smells. Estas son las herramientas JDeodorant y
Bandago [9, 10].
La herramienta Bandago automatiza la mayor parte del proceso de refactorización, desde la
detección de Code Smells hasta la eliminación de los mismos vía refactoring. Adicionalmente,
Bandago se enfoca en el Code Smell Brain Method, el cual es más específico que el Code
Smell Long Method y suele indicar problemas más complejos. Por otro lado, la herramienta
JDeodorant proporciona muy buenos resultados con respecto a otras herramientas de
eliminación de Long Methods [59]. Esto se debe a su enfoque de detección de oportunidades
de refactorización basado en la técnica conocida como Program Slicing [52]. Una limitación
de JDeodorant es su bajo nivel de automatización.
La idea de este trabajo es realizar una comparación entre Bandago y JDeodorant para poder determinar cuál de los dos enfoques es más efectivo, y realizar las modificaciones necesarias en ambas herramientas para lograr la automatización del proceso de refactorización de las
mismas, enfocado en la eliminación de Brain Methods. Con esto, se busca crear una
herramienta capaz de eliminar dichos Code Smells de forma automática y de la manera más
eficiente y eficaz posible.
Para realizar esta comparación, se analizaron 10 proyectos Java de código abierto. Sobre estos proyectos se ejecutaron las herramientas JDeodorant, JSpirit y Bandago para obtener
métricas relacionadas a la cantidad de Long Methods y Brain Methods presentes en el código,
cantidad de Long Methods y Brain Methods eliminados luego de la ejecución de las
herramientas, y también a los tiempos de ejecución de estas herramientas o cantidad de pasos manuales realizados en el caso de JDeodorant. En base a estos resultados, se estudió el diseño interno de las herramientas Bandago y JDeodorant y se realizaron modificaciones en pos de lograr una comparación más pareja entre ambas. Estas modificaciones incluyen el
agregado de una etapa de detección de Brain Methods en JDeodorant, la implementación de
un algoritmo para automatizar la ejecución manual en JDeodorant, y la implementación del
algoritmo de Program Slicing en la herramienta Bandago más una función de toma de
En las Figura 1.1 y 1.2 se observa el funcionamiento de las herramientas JDeodorant y Bandago desde el punto de vista del desarrollador durante el proceso de mantenimiento de un sistema. En ambos casos se observa la falta de automatización total del proceso.
Figura 1.1 Diagrama de contexto del funcionamiento de la herramienta JDeodorant.
Figura 1.2 Diagrama de contexto del funcionamiento de la herramienta Bandago.
En las Figuras 1.3 y 1.4 se observa el funcionamiento de las herramientas JDeodorant y Bandago desde el punto de vista del desarrollador, durante el procseo de mantenimiento de un sistema, luego de aplicar las mejoras mencionadas en ambas herramientas. Se destaca la automatización total del proceso.
Figura 1.4 Diagrama de contexto del funcionamiento de la herramienta Bandago mejorada.
1.3 Estructura de la tesis
El esquema general del trabajo final está organizado de la siguiente manera.
En el capítulo 2 se explica de manera más detallada el marco teórico que permitirá comprender el resto del trabajo aquí realizado. Luego en el capítulo 3 se incluye una descripción del estado del arte detallando particularmente las dos herramientas que mejor
intentan resolver el problema de la eliminación de Code Smells, y que serán de gran ayuda
para tratar de alcanzar los objetivos de este trabajo (JDeodorant y Bandago).
En el capítulo 4 se muestra el primer análisis realizado sobre ambas herramientas en su estado original (sin realizar modificaciones aún). Este análisis busca comparar ambas herramientas para determinar cuál de ellas resulta más útil para resolver el problema que intentan solucionar y cuál de las dos resultaría más útil para alcanzar los objetivos de este
trabajo, el cual consiste en la eliminación automatizada de Code Smells de tipo Brain Method
en proyectos de software.
El capítulo 5 presenta una serie de mejoras realizadas sobre ambas herramientas, con el objetivo de poder realizar nuevamente un experimento que compare el enfoque de ambas herramientas en el contexto de aplicación perseguido en este trabajo.
En el capítulo 6 se muestran y analizan los resultados obtenidos de este nuevo experimento con el objetivo de evaluar ambas herramientas en el contexto de aplicación mencionado y poder determinar finalmente cuál de las dos herramientas logra solucionar el problema.
Capítulo 2: Marco Teórico
En este capítulo se definen y explican los conceptos asociados a calidad de software, evolución de sistemas de software, y mantenimiento de los mismos. Estos conceptos se encuentran relacionados estrechamente entre sí.
2.1 Calidad de software
2.1.1 Definición de calidad de software
La Organización Internacional para la Estandarización (ISO) define a la calidad, dentro del vocabulario para sistemas de administración de calidad (QMS), como el grado con el cual un conjunto de características inherentes de un objeto (producto, proceso, servicio, etc) satisface sus requerimientos [12].
La calidad del software se divide en dos importantes grupos según el objeto del que se trate, a saber: calidad de proceso y calidad de producto [1].
La calidad de producto refleja el carácter esencial, características, y propiedades de los artefactos de software, y es un reflejo de que tan bien soportan las necesidades de los stakeholders. Un stakeholder es un grupo, individuo, u organización que es afectada por, o puede afectar, un producto, proyecto u organización [1]. La calidad del producto es asociada generalmente a la ausencia de defectos en un artefacto de software. Sin embargo, esta calidad está relacionada también a otras propiedades, características y atributos que las personas valoran, como son: la disponibilidad, eficiencia, mantenibilidad, portabilidad, confiabilidad, reusabilidad, y usabilidad [1].
La calidad de proceso, por otro lado, refleja cómo es desarrollado un producto. Está relacionada con la forma en la que las personas desarrollan productos, y existen 3 atributos de calidad de proceso claves: eficacia (producción de productos listos para el servicio), eficiencia (aprovechamiento de recursos minimizando su gasto), y predictibilidad (estimación de recursos, costos y tiempos) [1].
2.1.2 Mantenibilidad
La mantenibilidad se define como el grado en el cual un artefacto de software facilita su modificación. Este término es muy amplio pero puede ser estudiado como 5 características individuales que componen la mantenibilidad, que son: extensibilidad, modularidad, simplicidad, testeabilidad y comprensibilidad [1]. Estas características se describen a continuación.
Extensibilidad: Es la medida en la cual un software puede ser expandido o mejorado. En el mundo real son varios los factores que cambian (por ej., necesidades de los clientes, tecnología, personal, etc.) por lo tanto esta característica debe ser planeada en el desarrollo de todo sistema de software no trivial [1].
sistemas modulares se descomponen en componentes más pequeños, cada uno resolviendo un fragmento de un problema más grande. La modularidad tiende a reducir defectos, reducir esfuerzos en trabajos de mantenimiento posterior, e incrementar la posibilidad de reutilizar módulos, asumiendo que estos tienen interfaces bien definidas y poco acoplamiento con otros módulos [1].
Simplicidad: Es la medida en la cual un sistema de software contiene solamente complejidad esencial. Así, los sistemas de software deberían incluir únicamente aquellas características que necesita para lograr su propósito. Agregar características no esenciales crea riesgos más grandes dado que estas no suelen encajar bien con el diseño general e incrementan su complejidad, reduciendo su prolijidad. Es decir, los sistemas de software deben tener integridad conceptual, lo que refleja un conjunto de ideas integradas que funcionan bien entre sí. Además, debido a la complejidad asociada con el desarrollo, la mayoría de los sistemas de software rápidamente agotan las limitaciones intelectuales de las personas, las mejores soluciones son generalmente más simples, cuestan menos en ser desarrolladas y tienen menos defectos [1].
Testeabilidad: Es la medida en que el software facilita la verificación de sus operaciones. Si la testeabilidad de un artefacto de software es alta, entonces es más fácil encontrar fallos en
el sistema (si los hay) por medio de pruebas o “tests”.
Comprensibilidad: Es la medida en que las personas pueden comprender el comportamiento de un sistema de software, que generalmente resulta en el desarrollo de sistemas más confiables. La comprensibilidad de los sistemas de software puede aumentarse, por ejemplo, mediante el uso de metodologías mejoradas de diseño y documentación [1].
2.2 Evolución y mantenimiento de sistemas
2.2.1 Definición de evolución y mantenimiento de software
El fenómeno de evolución de software [13] fue identificado por primera vez como tal a comienzo de los 70s [14,2]. Esta evolución es inevitable en proyectos de software exitosos y fue formulada por medio de 3 leyes en base a la información obtenida del estudio del proceso de desarrollo de software en un trabajo realizado por Lehman [2, 3]. Pese a que este análisis se basa en un estudio que data de 1968, aún se considera vigente, y se han llegado a identificar un total de 8 leyes.
Estas leyes hacen hincapié en el hecho de que el software debe evolucionar continuamente para seguir siendo útil, y que dicha evolución se acompaña de un aumento de la complejidad y el esfuerzo que tendrán que ser invertidos para controlar este crecimiento. La evolución de un software lleva a que el código deba ser modificado. Estos cambios pueden tener distintas razones, como por ejemplo: la corrección de errores, nuevas tecnologías, cambios en el hardware utilizado, mejoras en la eficiencia, nuevos requerimientos, etc.
La ley de cambio continuo [15] explica que la evolución se lleva a cabo en un proceso de mejora continua controlado y dirigido por feedback. Esta actividad es definida por ISO [16] como Mantenimiento:
La modificación del código y la documentación asociada de un producto de software debido a un problema o la necesidad de mejoras. El objetivo es modificar el producto de software existente preservando su integridad.
Similar es la definición dada por la IEEE [17]:
El proceso de modificar un sistema de software o un componente, después de que éste fue entregado, con el fin de corregir errores, mejorar la performance u otros atributos, o adaptarlo a un cambio en el ambiente.
2.2.2 Problemas en la evolución y mantenimiento de software
A medida que los desarrolladores modifican el código fuente (ya sean cambios para alcanzar objetivos a corto plazo, o cambios realizados sin una plena comprensión del diseño del código), el mismo pierde su estructura, y se hace más difícil ver el diseño mediante la lectura del código [4]. La pérdida de la estructura del código tiene un efecto acumulativo. Cuanto más difícil es ver el diseño en el código, más difícil es preservarlo, y más rápidamente se deteriora [4].
Este aumento de la complejidad es descrito en la regla de complejidad incremental [15] y estudiado por algunos autores bajo el nombre de fenómeno de envejecimiento, llamado así debido a la similitud con el proceso de envejecimiento humano. Si bien este fenómeno es una consecuencia del fenómeno de evolución, también puede surgir por la falta de mantenimiento.
Estas características de los proyectos de software han sido identificadas por la mayoría de las metodologías de desarrollo de software propuestas en las últimas décadas. En este contexto, ya no se considera el diseño como “grabado en piedra” sino que debe cambiar y ser revisado en distintas iteraciones debido a la llegada de nuevos requerimientos, y como parte del proceso de mantenimiento y mejoras para combatir el envejecimiento y degradación del sistema.
2.2.3 Código legado
El software legado (legacy software) es el software que fue heredado de un tercero o que existe dentro de la misma compañía. El hecho de ser heredado significa que el software puede ser anticuado, puede haber sido desarrollado utilizando lenguajes de programación desactualizados, o métodos de desarrollo obsoletos. Lo más común es encontrar signos de muchas modificaciones y adaptaciones debido a múltiples cambios de mano [11]. Estos sistemas poseen normalmente un alto costo de actualización o reemplazo. Por esto es que surgen técnicas de reingeniería para reducir su complejidad de forma tal que pueda seguir siendo utilizado y adaptado a un costo aceptable [11].
En estos casos, en lugar de tomar un enfoque de mantenimiento y evolución, se suele tomar una actitud de “No arreglar lo que funciona”. El problema con este enfoque es que falla en reconocer las múltiples maneras en la que un sistema puede no “funcionar”. Desde un punto de vista funcional, un software funciona si logra cumplir con la funcionalidad para la cual fue diseñado. Pero desde un punto de vista de mantenimiento o de calidad, un software no funciona si no puede seguir siendo mantenido [11] o si no posee una calidad aceptable (más allá de que funcione).
2.2.4 Soluciones propuestas
Una pregunta importante es cómo se puede controlar la complejidad del software. Una estrategia es la reingeniería de software, en la cual se modifica un sistema existente para mejorar su diseño reduciendo la complejidad. Esta estrategia incluye el uso de la tecnica de
refactorizacion o refactoring [5].
La mantenibilidad, como un atributo de calidad del producto de software, puede ser tratada mediante el uso de patrones de diseño y arquitectura desde las etapas de diseño de software. Esto es, preparar el diseño para soportar la mantenibilidad y evolución a través de la
utilización de patrones previamente estudiados con dicho objetivo en mente. Este “diseño
orientado a soportar el cambio” abarca técnicas de separación de concerns, ocultamiento de
información, uso de mecanismos de herencia y polimorfismo, interfaces, separación del diseño en capas, mantenimiento actualizado de la documentación, y desarrollo orientado a pruebas entre otras.
diseño, llamados Code Smells. En este tema se destaca el trabajo realizado por Lanza y Marinescu [4], y será estudiado en la Sección 2.3.
Si bien se suele atacar el problema de la calidad de software mediante: metodologías, mejoras de procesos, desde la concepción del diseño, o por medio de métricas, no hay que olvidar que existen otros factores que también pueden indicar futuros problemas. Estos son la falta de documentación actualizada, la falta de pruebas (tests), falta de conocimiento del proyecto o la salida del proyecto del personal que posea conocimientos no documentados, cambios simples que llevan demasiado tiempo de desarrollo o en salir a producción, alta tasa de bugs detectados, etc. [11].
2.3
Code Smell
2.3.1 Definición de
Code Smell
Los Code Smells o Bad Smells son características estructurales de software que pueden
indicar un problema de diseño o de código que hace al software difícil de evolucionar y mantener, y puede desencadenar en refactorizaciones de código [18]. Estos representan síntomas de decisiones de implementación o diseño pobres [5]. El concepto fue introducido
por Fowler [5], quien catalogó 22 tipos de Code Smells, y asoció cada uno con
transformaciones de refactorización correspondientes. Otros autores también han identificado
Code Smells (por ej. Mäntylä [19]), y otros podrían ser identificados en el futuro.
2.3.2 Impacto de los
Code Smells
Se han realizado diversos estudios para investigar la relevancia que los Code Smells tienen
para los desarrolladores [25, 26], la medida en la cual los Code Smells tienden a permanecer
en un sistema de software por largos periodos de tiempo [35,36,37,33], así como también los efectos secundarios de los mismos, como incrementos en la probabilidad de errores [27, 28] o bajas en la mantenibilidad del software [24, 29, 30], en particular debido una menor comprensibilidad [31].
Un estudio importante es el realizado por Palomba [25] sobre la percepción que tienen los
desarrolladores sobre los Code Smells. De este estudio se concluye que no todos los Code
Smells indican realmente problemas a futuro, de acuerdo a la percepción de los
desarrolladores. Esto puede variar de un smell a otro y según la complejidad o gravedad del
mismo. La experiencia y el conocimiento influyen en la identificación de Code Smells por parte
de los desarrolladores [25]. Según este estudio, Long Method es para los desarrolladores uno
de los 2 Code Smells percibidos como amenaza más importantes.
De acuerdo a Fontana [18], la eliminación de un Code Smell tiene que ser siempre evaluada
por una persona que pueda tomar la decisión en base al sistema en el cual este fue encontrado.
2.3.3 Surgimiento de
Code Smells
Los Code Smells son introducidos en los sistemas de software generalmente por
o porque no se preocupan por diseñar apropiadamente la solución debido a plazos de tiempo de entrega estrictos [25].
De acuerdo a Tufano [22], la mayoría de las veces los artefactos de código son afectados por
Code Smells desde su creación. Sin embargo, también se los suele introducir no solo con la
implementación de nuevas funcionalidades, sino también con la realización de actividades de mantenimiento como operaciones de refactorización, las cuales se llevan a cabo justamente
para evitar estos smells. Esto no es necesariamente un problema de los nuevos
desarrolladores en un proyecto, los desarrolladores más propensos a introducir Code Smells
son aquellos con altas cargas de trabajo y con más presión en la entrega de artefactos.
2.3.4 Identificación de
Code Smells
2.3.4.1 MétricasRecientemente se han realizado varios estudios sobre la evolución histórica de Code Smells
en sistemas de software [32, 33] así como también sobre el origen de los Code Smells [22].
Estos estudios revelan que los artefactos de software a medida que se vuelven “smelly” como
consecuencia de las actividades de mantenimiento, son caracterizados por tendencias peculiares de sus métricas.
Estas métricas pueden ser utilizadas para evaluar la calidad de software y predecir futuros esfuerzos de desarrollo. Sin embargo no son una herramienta perfecta, dado que hay varios aspectos del diseño de software y su calidad que son difíciles de medir, pero sirven como un punto de partida para un análisis más profundo. Por ejemplo, pueden utilizarse métricas propias de la empresa y de los procesos como la cantidad de bugs detectados en dicha sección de código [4].
Las métricas utilizadas para la identificación y detección de Code Smells son llamadas
métricas de diseño [4].
2.3.4.2 Umbrales
Al trabajar con métricas resulta necesario saber qué se entiende por “demasiado alto”, “demasiada cantidad”, “demasiado pequeño”, entre otros. Estos puntos de referencia
llamados umbrales o “thresholds” sirven como conexión entre el valor de las métricas y una
semántica útil. Un umbral divide el espacio del valor de una métrica en regiones, y dependiendo de en qué región se encuentre un valor particular, se pueden realizar estimaciones sobre la entidad medida basadas en la información tomada [4].
Los umbrales tampoco son perfectos, pero igualmente son útiles en la práctica. Estos se
suelen obtener de análisis estadísticos generalmente aceptados [4]. En general, el valor de
2.3.4.3 Utilidad de las métricas
Una forma de obtener métricas que provean información real y no solamente números es la utilización del modelo Goal-Question-Metric (GQM) [23], que obliga a definir objetivos antes de realizar mediciones. Primero se definen los objetivos para los cuales se necesitan hallar las métricas, luego por cada objetivo se derivan preguntas que deben ser contestadas para determinar si un objetivo es cumplido o no, y finalmente se define qué métricas deben recolectarse para responder dichas preguntas. Al recolectar las métricas se aplica el mecanismo de filtro o “filtering”, en el cual se comparan los valores de las métricas obtenidos con valores de umbrales para descartar aquella información que no es relevante para el estudio en cuestión [4]. Esta operación devuelve un valor booleano y es utilizada para definir métricas más complejas utilizando el mecanismo de composición (mediante el uso de operadores lógicos AND y OR). El resultado de una composición es también un filtro y se
suele utilizar directamente para afirmar o no la existencia de un Code Smell.
Algunos ejemplos de métricas que son importantes para la comprensión de los Code Smells
tratados en este trabajo son las siguientes:
● Cantidad de líneas de código (LOC): Indica la cantidad de líneas de código que componen un método, clase, función, o cualquier fragmento de código en cuestión. ● Complejidad ciclomática (CYCLO): Indica el número de caminos independientes
dentro de un fragmento de código. Esta métrica mide la complejidad de un método en base a la aplicación de la teoría de grafos [20].
● Máximo nivel de anidamiento (MAXNESTING): Indica el máximo nivel de anidamiento de estructuras de control dentro de un metodo o funcion.
● Cantidad de variables utilizadas (NOAV): Indica la cantidad de variables locales declaradas, parametros, asi como tambien la cantidad de atributos y variables globales utilizadas.
2.3.5
Long Method
y
Brain Method
Un Long Method es un método que es demasiado largo en términos de cantidad de líneas de
código. Este Code Smell es indeseado debido a que afecta la testeabilidad y comprensibilidad
del código, y en consecuencia la mantenibilidad del mismo [4]. Como se mencionó
anteriormente, es uno de los 2 Code Smells críticos para la percepción general de los
desarrolladores. En el Código Fuente 2.1 se observa un ejemplo de un Code Smell de tipo
Long Method. El método en cuestión (makeQualifier) presenta un total de 125 líneas de
código, lo cual hace difícil su comprensión con la simple lectura de las mismas.
static Expression makeQualifier(EOObjEntity entity, Map qualifierMap) {
if (isAggregate(qualifierMap)) {
// the fetch specification has more than one qualifier
int aggregateClass = aggregateExpressionClassForQualifier(qualifierMap); // AND, // OR,
// NOT
if (aggregateClass == Expression.NOT) {
// NOT qualifiers only have one child, keyed with // "qualifier"
Map child = (Map) qualifierMap.get("qualifier");
// build the child expression
Expression childExp = makeQualifier(entity, child);
// the // result
} else {
// AND, OR qualifiers can have multiple children, keyed with // "qualifiers"
// get the list of children
List children = (List) qualifierMap.get("qualifiers");
if (children != null) {
ArrayList<Expression> childExpressions = new ArrayList<>();
// build an Expression for each child Iterator<Map> it = children.iterator();
while (it.hasNext()) {
Expression childExp = makeQualifier(entity, it.next()); childExpressions.add(childExp);
}
// join the child expressions and return the result
return ExpressionFactory.joinExp(aggregateClass, childExpressions); }
}
} // end if isAggregate(qualifierMap)...
// the query has a single qualifier // get expression selector type
String qualifierClass = (String) qualifierMap.get("class");
// the key or key path we're comparing String key = null;
// the key, keyPath, value, or parameterized value against which // we're
// comparing the key
Object comparisonValue = null;
if ("EOKeyComparisonQualifier".equals(qualifierClass)) { // Comparing two keys or key paths
key = (String) qualifierMap.get("leftValue"); comparisonValue = qualifierMap.get("rightValue");
// FIXME: I think EOKeyComparisonQualifier sytle Expressions are // not
// supported...
return null;
} else if ("EOKeyValueQualifier".equals(qualifierClass)) { // Comparing key with a value or parameterized value key = (String) qualifierMap.get("key");
Object value = qualifierMap.get("value");
if (value instanceof Map) {
Map<String, String> valueMap = (Map<String, String>) value; String objClass = valueMap.get("class"); // can be a
// qualifier class // or java type
if ("EOQualifierVariable".equals(objClass) && valueMap.containsKey("_key")) {
// make a parameterized expression String paramName = valueMap.get("_key");
comparisonValue = new ExpressionParameter(paramName); } else {
Object queryVal = valueMap.get("value");
if ("NSNumber".equals(objClass)) { // comparison to NSNumber -- cast comparisonValue = queryVal; } else if ("EONull".equals(objClass)) {
// comparison to null comparisonValue = null;
} else { // Could there be other types? boolean, date,
// etc.??? // no cast comparisonValue = queryVal; }
} else if (value instanceof String) { // value expression
comparisonValue = value;
} // end if (value instanceof Map) else...
}
// check whether the key is an object path; if at least one // component is not,
// switch to db path..
Expression keyExp = ExpressionFactory.exp(key);
try {
entity.lastPathComponent(keyExp, Collections.emptyMap()); } catch (ExpressionException e) {
try {
keyExp = entity.translateToDbPath(keyExp); } catch (Exception dbpathEx) {
return null;
} }
try {
Expression exp =
ExpressionFactory.expressionOfType(expressionTypeForQualifier(qualifierMap));
exp.setOperand(0, keyExp);
exp.setOperand(1, comparisonValue);
return exp;
} catch (ExpressionException e) {
return null;
} }
Código Fuente 2.1 Ejemplo de Long Method.
De hecho, en un estudio sobre el efecto de los Code Smells en el esfuerzo de mantenimiento
[29], se concluye que para reducir el esfuerzo de mantenimiento, un enfoque consistente en reducir la cantidad de código y prácticas de trabajo que reduzcan el número de cambios
puede resultar más beneficioso que la refactorización de Code Smells en general.
Además de afectar la comprensibilidad del código, se introduce una carga extra de esfuerzo mental para el lector (desarrollador) de un método al “cambiar de contexto” mentalmente mientras se desarrolla software, al tratar de entender lo que hace dicho método [5].
La detección de Long Methods resulta sencilla. La métrica necesaria es la cantidad de líneas
de código (LOC) la cual puede compararse con valores de umbral estadísticos para crear filtros como se ve por ejemplo en la Fórmula 2.1. Esta expresión al ser verdadera indica que el método es “excesivamente largo”.
𝐿𝑂𝐶 >𝐻𝐼𝐺𝐻(𝐶𝑙𝑎𝑠𝑠) 2
Si bien medir un Long Method puede ser una tarea sencilla, confiar en una métrica tan simple definitivamente conlleva a resultados equivocados. Por ejemplo, los métodos de inicialización
(initiation methods) suelen ser largos, pero no tiene sentido realizar refactorizaciones dado
que son normalmente fáciles de entender y modificar. Varios autores [19, 4] sugieren la utilización de otras métricas como la complejidad ciclomática y el máximo nivel de anidamiento.
Por ejemplo la composición presentada en la Fórmula 2.2 se asocia a la detección del Code
Smell Brain Method [4].
𝐿𝑂𝐶 > 𝐻𝐼𝐺𝐻(𝐶𝑙𝑎𝑠𝑠)
2 ∧ 𝐶𝑌𝐶𝐿𝑂 ≥ 𝐻𝐼𝐺𝐻 ∧ 𝑀𝐴𝑋𝑁𝐸𝑆𝑇𝐼𝑁𝐺 ≥ 𝑆𝐸𝑉𝐸𝑅𝐴𝐿 ∧ 𝑁𝑂𝐴𝑉 > 𝑀𝐴𝑁𝑌
Fórmula 2.2 Composición de Métricas para definir el Code Smell de Tipo Brain Method.
Los Brain Method son Code Smells más restrictivos que los Long Methods, que no solamente
indica que un método es excesivamente largo, sino que también tiende a centralizar la funcionalidad de la clase. Estos métodos son difíciles de entender, depurar, y prácticamente imposibles de reutilizar [4].
La estrategia de detección propuesta por [4] se basa en la composición de varias métricas definida por [5] y es la siguiente:
● El método es excesivamente largo: Su valor de LOC es mayor que la mitad del valor de umbral estadístico de dicha métrica considerado como alto para una clase.
● El método tiene muchas ramificaciones condicionales (CYCLO): Computadas usando la métrica de complejidad ciclomática de McCabe’s [20]
● El método tiene un nivel de anidamiento profundo: Computada utilizando la métrica de MAXNESTING
● El método utiliza demasiadas variables: Se computa utilizando la métrica NOAV que toma en cuenta variables locales, parámetros, atributos y variables globales. El valor de dicha métrica se considera excesivo si utiliza más variables de las que un humano puede mantener en la memoria de corto plazo.
Los Code Smells que implican aspectos de longitud o duplicación de código suelen
considerarse “Desarmonías de identidad” (Identity disharmony) [4]. Suelen corregirse
eliminando primero la duplicación de código, variables temporales o no utilizadas, y en
particular para los Long Methods o Brain Methods mediante la técnica de refactoring conocida
como “Extract Method”.
2.4
Refactoring
2.4.1 Definición
El proceso de refactorización o refactoring se define como el proceso de cambio de un
refactorización es un cambio hecho en la estructura interna del software para hacerlo más fácil de entender y de modificar, sin cambiar su comportamiento observable [5].
2.4.2 Aplicación de refactorizaciones
Si bien no se define la refactorización como una etapa del proceso de desarrollo sino más bien como una actividad de mantenimiento, es importante saber cuándo debe realizarse. El proceso de refactorización debería realizarse cuando se agrega funcionalidad, cuando se necesita corregir un bug, cuando se hace revisión de código, o cuando uno se topa varias veces con la necesidad de duplicar el mismo código (también conocida como la Regla del Tres) [5]. Sin embargo Fowler sugiere realizar las refactorizaciones cuando se detecta un
Code Smell, y detenerse al finalizar la eliminación del mismo [5].
Algunas metodologías proponen incluso no realizar ninguna etapa de diseño por adelantado (o realizar en su lugar un diseño pensado vagamente), sino implementar el código con el primer enfoque que se viene a la mente del desarrollador hasta que funcione, y luego utilizar refactorizaciones para darle forma a dicho código. Por ejemplo, estos lineamientos se discuten en la metodología Extreme Programming [6].
El presente trabajo es agnóstico respecto a la metodología o proceso de desarrollo utilizado, y por lo tanto se tratará el tema de refactorizaciones solamente como una herramienta para
solucionar Code Smells particulares que han sido detectados en el código por medio de
métricas, y no como una actividad general de mantenimiento. Fowler en [5] asocia las técnicas
de refactorización con el Code Smell que ayudan a solucionar. En el caso de Long Method,
la refactorización asociada es conocida como Extract Method.
2.4.3
Extract Method
Extract Method es una de las refactorizaciones más comunes, y su mecanismo consiste en
los siguientes pasos:
1. Crear un nuevo método y nombrarlo apropiadamente según la intencionalidad del mismo.
2. Copiar el código a extraer del método original en el nuevo método.
3. Buscar referencias a variables locales utilizadas en el código extraído que fueron declaradas en el método original y definirlas como parámetros del nuevo método. 4. Si se utilizan variables temporales en el código extraído, declararlas en el nuevo
método.
5. Revisar si alguna de estas variables locales fue modificada por el código extraído. Si una variable es modificada, ver si se la puede tratar como una “consulta” y asignar el resultado a la variable en cuestión. Si esto es raro, o si hay más de una variable de este tipo, el método no puede ser extraído de esta forma. En este caso será necesario
usar otras técnicas como “Split Temporary Variable” (Dividir variable temporal) o
“Replace Temp with Query” (Reemplazar variable temporal con consulta).
6. Invocar al nuevo método pasando como argumento las variables locales requeridas, reemplazando el código extraído en el método original por dicho llamado.
2.4.4 Problemas de la refactorización de código
Si bien Fowler afirma que las refactorizaciones ayudan a mejorar la comprensibilidad del código, encontrar bugs, y mejorar la performance del programa entre otras ventajas [5], en la práctica se presentan tendencias no consistentes, y se encuentran casos donde la refactorización manual mejora un atributo de calidad en algunas clases pero debilita el mismo atributo de calidad en otras clases del mismo sistema. Por lo tanto, no se podría confirmar la generalización de que el proceso de refactorización es siempre una práctica que mejora la calidad del software [21]. Otro ejemplo es el estudio realizado por Tufano en el cual se encontraron casi 400 casos en los cuales las operaciones de refactorización manual
introdujeron nuevos smells (entre el 4% y 11% en promedio de las refactorizaciones
realizados) [22].
Aun así, se puede utilizar la refactorización de forma manual como un proceso para ayudar a entender y validar el entendimiento actual sobre cómo funciona un sistema de software [5, 11].
Otros escenarios donde no resulta conveniente realizar refactorizaciones es cuando intervienen bases de datos o cuando la refactorización implica modificar interfaces. En el primer caso, la mayoría de las aplicaciones de negocio están ligadas a los datos y su estructura que le dan soporte. Además del riesgo de introducir errores, las migraciones de los cambios resultan difíciles de realizar. En el segundo caso, se puede perder la noción de donde se utiliza el método o clase que está siendo modificado y por lo tanto introducir errores [5].
2.4.5 Minimización de riesgos
Una forma de minimizar el riesgo de introducir defectos consiste en utilizar un proceso basado en tests automatizados, que puedan ser repetidos y almacenados. Estos tests, si son bien diseñados, deberían exhibir las siguientes propiedades:
- poder ejecutarse de forma automática;
- poder ser almacenados (persistentes);
- ser repetibles;
- poder asociarse a componentes de software individual (unit testing);
- y ser independientes entre sí [11].
Capítulo 3: Estado del arte
En este capítulo se realiza un repaso sobre las herramientas de software existentes en la
actualidad para la eliminación del Code Smell de tipo Long Method y Brain Method. Antes de
comenzar dicho repaso, en la sección 3.1 se realiza una explicación del proceso de refactorización automática para ayudar a comprender mejor el grado de automatización de cada una de las herramientas presentadas luego en la sección 3.2.
3.1 Automatización del
refactoring
mediante herramientas de
software
Como se mencionó en el Capítulo 2, varios de los problemas asociados a la refactorización de código se deben a la ejecución manual de este proceso. Además de consumir demasiado tiempo, la refactorización manual es propensa a errores y se vuelve difícil de manejar en grandes cantidades de código. Para esto se sugiere la utilización de herramientas automáticas, las cuales deben ser consideradas cuidadosamente dado que no todas presentan el mismo grado de automatización, o automatizan distintas partes del proceso de refactorización.
3.1.1 Descomposición del proceso de
refactoring
Antes de analizar las distintas herramientas disponibles para la refactorización es importante pensar el proceso de refactorización en etapas para poder analizar el ámbito de aplicación de cada herramienta y entender mejor las ventajas y desventajas de cada una. Tourwe [38] identifica 3 etapas asociadas al proceso de refactorización:
1) Identificación de cuándo una aplicación debe ser refactorizada.
2) Propuesta de refactorizaciones que podrían ser aplicadas y en qué lugar. 3) Aplicación de la refactorización seleccionada.
La última etapa suele dividirse en dos fases: chequear si las pre-condiciones apropiadas de la refactorización se cumplen para asegurar que la refactorización es válida (el código seguirá siendo compilable) y se preserva el comportamiento, y aplicar los cambios necesarios (debido a la refactorización).
Como se puede ver, el proceso de refactorización automatizado no es simplemente una forma de realizar extracciones seguras o válidas, sino que también incluye la identificación de problemas y la posibilidad de elegir (de manera automática, o bien por medio de entrada del usuario) entre varias alternativas de refactorización para solucionarlos. La etapa de identificación de problemas suele implementarse mediante los mecanismos de identificación
de Code Smells.
3.2 Trabajos relacionados
3.2.1 Automed
de refactorizar los Long Method al facilitar la identificación de extracciones en el código. La identificación consiste en identificar qué sentencias de código de un método extraer sin que esto modifique la funcionalidad y no cambie el flujo de ejecución. Identificarlos es una tarea tediosa para el desarrollador ya que tiene que comprobar que no se modifique el comportamiento del código manualmente. Al facilitar la identificación, se mejora el proceso de refactorización en un 40% (aproximadamente) con respecto al costo del tiempo que lleva
ejecutar todo el proceso de refactoring.
Para la identificación de los fragmentos de código a extraer se aplican técnicas que recorren
el código de manera recursiva en búsqueda de statements que estén separados en bloques
con comentarios, o simplemente separados por espacios en blanco. La justificación de realizar esto es que los desarrolladores programan de una manera que dejan porciones de código separados del resto porque tienen una funcionalidad diferente o son más complejos.
Además, en la identificación, también se incluyen statements del tipo If, For, While o cualquier
otro tipo de statement que podrían ser extraídos.
Luego, estos statements se priorizan según un criterio de costos y beneficios. El costo
considerado en el enfoque es el acoplamiento entre el nuevo método creado y el original. Este se calcula a partir de la cantidad de parámetros que tiene el método extraído sumado a la cantidad de variables que retorna el método. En el contexto de proyectos de Java, las variables de retorno pueden ser una o ninguna, porque el lenguaje no permite que se retornen varias variables en un método. El beneficio se considera como la cantidad de líneas que se extraen en el nuevo método. Este se expresa en la Fórmula 3.1.
𝑅(𝑓) =
𝐵𝑒𝑛𝑒𝑓𝑖𝑐𝑖𝑜(𝑓)
𝐴𝑐𝑜𝑝𝑙𝑎𝑚𝑖𝑒𝑛𝑡𝑜(𝑓)
=
|𝑓|
(|𝑃𝑒𝑛𝑡𝑟𝑎𝑑𝑎(𝑓)| + |𝑃𝑠𝑎𝑙𝑖𝑑𝑎(𝑓)|)
Fórmula 3.1 Cálculo del beneficio de una extracción.
3.2.2 PMD
PMD es un proyecto de software libre diseñado para inspeccionar código Java y resaltar estructuras ineficientes, como: variables locales no utilizadas, sentencias de importación duplicadas, o bloques try/catch vacíos. PMD le da a los programadores un enfoque preventivo para limpiar su código. PMD inspecciona código Java utilizando un enfoque basado en reglas. Estas reglas pueden ser definidas tanto por XPath (una sintaxis basada en XML) o clases Java. PMD incluye una serie de reglas consideradas comunes en toda aplicación Java, todas incluidas en su distribución principal. Estas están organizadas en conjuntos de reglas (rulesets) dependiendo de su funcionalidad [40].
PMD chequea el código fuente de acuerdo a reglas y produce un reporte, de la siguiente manera [41]:
● Se pasa un nombre de archivo y nombre de conjunto de reglas a PMD ● PMD entrega un InputStream del archivo al parser de JavaCC generado ● PMD obtiene del parser una referencia a un árbol sintáctico abstracto (AST)
● PMD entrega este AST a la capa de tabla de símbolos que construye los alcances, busca las declaraciones, y busca los usos de estos símbolos.
● Cada regla en el conjunto de reglas atraviesa el AST y comprueba la existencia de problemas. Estas reglas pueden también explorar la tabla de símbolos y nodos del DFA
● El reporte es completado con las violaciones a las reglas, y es generado como un documento XML, o HTML entre otros
En particular, para soportar la detección de Long Methods PMD incluye la regla
ExcessiveMethodLength.
3.2.3 Checkstyle
Checkstyle es una herramienta de desarrollo para ayudar a los programadores a escribir código Java que adhiere a estándares de código. Esta herramienta automatiza el proceso de chequear código Java para liberar a las personas de esta importante tarea que resulta tediosa. Esto la hace una herramienta ideal para proyectos que buscan cumplir un estándar de código [42].
Checkstyle puede chequear muchos aspectos del código fuente. Puede encontrar problemas de diseño de clases y problemas de diseño de métodos. Cuenta también con la habilidad de chequear problemas relacionados al formato y estructura visual del código.
Dentro de los chequeos provistos por la herramienta se encuentra el llamado MethodLength, el cual comprueba que el número de líneas de un método no exceda un determinado valor. Este chequeo puede ignorar líneas en blanco y comentarios, pero no cuenta la cantidad de sentencias reales. Sin embargo, existe un chequeo para verificar que solo exista una sentencia por línea de código.
Esta herramienta se encuentra disponible como una aplicación de línea de comandos, como
una tarea para la biblioteca Ant, y también como plugin para diferentes entornos de desarrollo
o herramientas de compilación.
3.2.4 Herramientas integradas en entornos de desarrollo
La mayoría de los entornos de desarrollo actuales cuentan con soporte para la realización de refactorizaciones. Por ejemplo el IDE Eclipse cuenta con una herramienta que asiste en la
aplicación de refactorizaciones Extract Method (entre otros refactorings). Esta cuenta con una
interfaz para interactuar con el usuario y realiza el chequeo de pre y post-condiciones correspondientes para asegurar que la extracción es válida. Además puede ser utilizada por
plugins de Eclipse por medio de la API LTK (Language Toolkit)
Otro entorno de desarrollo de proyectos Java muy popular es Intellij IDEA, el cual además de contar con un asistente de extracción de métodos, provee herramientas de inspección de
código incluyendo la detección de Long Methods.
estos ofrecen una vista local del código, orientada a archivos, lo cual dificulta la navegación de una jerarquía de clases entera y las diferentes implementaciones de un método a lo largo de esta. Como consecuencia, dichos entornos son incapaces de presentar una vista global de la estructura general y diseño de la aplicación.
3.2.5 JSpIRIT y Bandago
3.2.5.1 JSpIRIT
JSpIRIT (Java Smart Identification of Refactoring opportunITies) [43] es un plugin para el
entorno de desarrollo Eclipse que asiste a los desarrolladores en la identificación y
priorización de Code Smells. Este soporta la identificación de 10 Code Smells siguiendo las
estrategias de detección presentadas por Lanza y Marinescu [4]: ■ Brain Class
■ Brain Method
■ Data Class
■ Disperse Coupling ■ Feature Envy ■ God Class
■ Intensive Coupling ■ Refused Parent Bequest ■ Shotgun Surgery
■ Tradition Breaker
Para priorizar los Code Smells, JSpIRIT provee varios tipos de clasificaciones que usan
diferentes criterios como: la relevancia del tipo de Code Smell, o escenarios de modificabilidad
entre otros. JSpIRIT soporta además la identificación de 2 tipos de aglomeraciones de Code
Smells. Las aglomeraciones son grupos de Code Smells interrelacionados (por ejemplo, Code
Smells relacionados sintácticamente dentro de un componente) que probablemente indican
juntos la presencia de un problema arquitectural [43].
3.2.5.2 Bandago
Bandago [10] es una herramienta implementada sobre JSpIRIT cuyo enfoque consiste en
remover Brain Methods por medio de la extracción de fragmentos de código (refactorizaciones
de tipo Extract Method) mientras se consideran aspectos de legibilidad y extensibilidad del
código resultante. Bandago asiste a los desarrolladores con técnicas de búsqueda automática
para corregir las instancias de Brain Methods. Esto se debe a que seleccionar los fragmentos
de código a extraer puede ser desafiante por diversas razones [44]. Algunos ejemplos son: es difícil identificar sentencias relacionadas cohesivamente, puede haber dependencias entre los fragmentos seleccionados y el resto del código, el comportamiento del método debe ser preservado, entre otros.
Bandago sigue un ciclo de trabajo iterativo, que toma como entrada el código fuente de un
método identificado como Brain Method por JSpIRIT y produce como salida soluciones
alternativas para el smell. Estos Brain Methods son identificados por medio de una estrategia
Internamente Bandago realiza una búsqueda heurística usando un algoritmo de tipo
Simulated Annealing [45]. El enfoque involucra las siguientes 4 actividades:
1. Obtener las sentencias de un método: Dada una instancia de Brain Method, esta
actividad provee un conjunto de sentencias que pueden ser extraídas del mismo. Las
sentencias son filtradas y agrupadas de acuerdo a su tipo (Ejemplo: Sentencias “if”,
sentencias “while”, etc.)
2. Obtener una sentencia candidata: Las sentencias son evaluadas basándose en diferentes criterios, llamados operadores, como por ejemplo la longitud o complejidad de la misma.
3. Extraer la sentencia candidata: La sentencia elegida es extraída en un nuevo método
vía una refactorización Extract Method. Esta refactorización es aplicado en un modelo
del programa almacenado en memoria, lo que se conoce como refactorización virtual [46,47]. Una vez que la solución es generada y evaluada, los cambios introducidos en memoria son descartados. Una refactorización es aplicada directamente en el código fuente cuando el desarrollador escoge una solución en concreto de entre todas las propuestas por Bandago.
4. Chequear la existencia de Brain Methods: Después de aplicar una refactorización,
Bandago chequea que ambos métodos (el método afectado y el nuevo método
creado) han dejado de ser Brain Methods. En caso contrario se exploran
refactorizaciones adicionales
5. Evaluar solución general: Cuando una solución para un Brain Method es encontrada,
un conjunto de métricas son computadas con el fin de evaluar su “goodness”. Estas
métricas ayudan al desarrollador a elegir una “buena” solución de entre las propuestas.
Luego de realizar la actividad 5, se itera un número predefinido de veces, desde la actividad 1 hasta la actividad 5, con el fin de devolver un número predeterminado de soluciones al
desarrollador. Por el momento Bandago solo soporta el Code Smell de tipo Brain Method.
3.2.5.2.1 Simulated Annealing
Simulated Annealing es un algoritmo de búsqueda metaheurístico basado en una analogía
con los procesos de calentamiento y enfriamiento de materiales en metalurgia [48]. Ha sido usado para resolver diferentes problemas de optimización con múltiple óptimos locales [49, 48], y en particular, ha sido utilizado para buscar refactorizaciones [50, 51]. En este contexto un óptimo local es la mejor solución encontrada por el algoritmo, pero no es necesariamente la mejor solución al problema, la cual es llamada óptimo global.
Simulated Annealing sigue un método iterativo que empieza por considerar un estado I. En
Bandago utiliza Simulated Annealing para buscar opciones de refactorización de Brain
Methods dado que este algoritmo permite explorar un amplio rango de diferentes soluciones
para corregir el Brain Method, usando solamente una transformación. Incluso el algoritmo
puede aceptar soluciones parcialmente satisfactorias, con el objetivo de encontrar soluciones más adelante por medio de una pequeña secuencia de transformaciones.
En el Código fuente 3.1 se observa el pseudocódigo utilizado por Bandago, el cual es una
adaptación del algoritmo Simulated Annealing considerando Brain Methods y
refactorizaciones Extract Method.
1. generateSolutions(BrainMehod bm){
2. while(solutions.size()<numberOfSolutions){ 3. if(satisficing(simulatedAnnealing(bm)) 4. solutions.add(simulatedAnnealing(bm)); 5. }
6. //activity5
7. measureSolutions(solutions); 8. return(solutions);
9. }
10. simulatedAnnealing(BrainMethod bm){ 11. state=bm.getCode();
12. temperature=initialTemperature; 13. actualIteration=0;
14. while(actualIteration<maxIterations){ 15. //activity1
16. statementSet=state.getStatements(); 17. //activity2
18. candidateStatement=statementSet.getPriorizedStatement(); 19. //activity3
20. newState=state.applyExtractMethod(candidateStatement); 21. //activity4
22. if(!stillBrainMethods(newState))//evaluation function 23. return newState;//satisfying solution
24. elseif(random()<tempFunct(state,newState,temperature,actualIteration)) 25. //cooling schedule
26. state=newState;
27. temperature=coolDownFactor∗temperature; 28. actualIteration++;
29. }
30. return(state); 31. }
Código Fuente 3.1 Pseudocódigo para el algoritmo de Simulated Annealing implementado en Bandago.
Notar que la función de “fitness” de este algoritmo no busca soluciones óptimas sino
soluciones satisfactorias (es decir, soluciones válidas con respecto a Brain Method).
En la Figura 3.1 se pueden observar los Code Smells detectados por la herramienta JSpIRIT
Figura 3.1 Captura de pantalla de la herramienta JSpirit.
Luego de seleccionar un Code Smell a eliminar por Bandago, se observa una lista como la
que se puede apreciar en la Figura 3.2, con las distintas soluciones generadas por la herramienta. Cualquiera de estas soluciones puede ser aplicada por el desarrollador.
Figura 3.2 Captura de pantalla de la herramienta Bandago.
3.2.6 JDeodorant
JDeodorant [9] es una herramienta que permite identificar oportunidades de refactorización
para 4 tipos de Code Smells en sistemas Java, a saber: Feature Envy, State Checking, God
El enfoque de la herramienta para la remoción del Code Smell Long Method es orientado a refactorizaciones. Esto quiere decir que la herramienta no se enfoca en la detección y
corrección del Code Smells, sino simplemente en la aplicación de refactorizaciones Extract
Method donde sea posible, respetando un mínimo número de líneas a extraer por
refactorización. Este enfoque presenta tres ventajas sobre los enfoques existentes [9]: ● Provee una solución completa al problema de mejorar la calidad de diseño tomando
como ventaja el hecho de que los Bad Smells son mapeados directamente a
soluciones de refactorización específicas
● Tiene la habilidad de producir soluciones factibles y que preservan el comportamiento, mediante la observación de un conjunto de precondiciones que deben cumplirse ● Tiene la habilidad de pre-evaluar el efecto de las oportunidades de refactorización
identificadas en calidad de diseño, y así proveer un ranking de soluciones de
refactoring permitiendo la priorización de los efectos de mantenimientos en las partes
de un programa que más lo necesitan
El enfoque tomado para la búsqueda de oportunidades de refactoring Extract Method se basa
en el concepto de Program Slicing. Algo para destacar de este enfoque es que permite
realizar extracciones de código no consecutivos, lo que lleva a aumentar la cantidad de oportunidades de refactorización encontradas. Esto lleva a una complejidad más alta cuando
se tienen que realizar los chequeos de Program Slicing [52], ya que hay que controlar todas
las ramas de ejecución. Mediante los chequeos nombrados, JDeodorant mantiene la principal premisa de las refactorizaciones, que es que no se modifique el comportamiento del código una vez aplicada la refactorización.
Al seguir un enfoque orientado a refactorizaciones, la herramienta no sigue fielmente el modelo de proceso de refactorización propuesto por Tourwe [38]. En el caso particular de la
búsqueda de oportunidades de refactorización para Extract Method, estas oportunidades son
buscadas independientemente de si el método en análisis presenta o no un Code Smell de
tipo Long Method. La herramienta simplemente intenta encontrar oportunidades de
refactorización donde se extraiga una cantidad de sentencias mayor a un umbral predefinido
por el usuario. Como es de esperarse de esta falta de identificación de smells, tampoco se
revisará luego de aplicarse una refactorización si el Code Smell fue eliminado o no. Esta etapa
de búsqueda de oportunidades se realiza sin intervención del usuario, y produce como salida un conjunto de posibles grupos de sentencias a extraer, diferenciadas por la variable utilizada como criterio.
La herramienta JDeodorant se encuentra implementada como un plugin de Eclipse.
3.2.6.1 Program Slicing
El enfoque tomado para la búsqueda de oportunidades de refactoring Extract Method se basa
en el concepto de Program Slicing. Este enfoque maneja algoritmos de dos categorías
principales. El primer algoritmo identifica oportunidades de refactorización donde la
computación completa de una variable local o parámetro (complete computation slice) puede
ser extraída, significando que todos los slices resultantes contendrán todas las sentencias de
asignación modificando el valor de la variable local. El segundo algoritmo identifica oportunidades de refactorización donde todas las sentencias afectando el estado de un objeto