Revista Tecnología y Ciencia - Universidad Tecnológica Nacional
ISSN 1666-6933 / Septiembre-Diciembre 2018 / Año 16 - Nº 33
Claudia Marcos
ISISTAN-UNICEN, Tandil, Argentina - CIC
cmarcos@exa.unicen.edu.ar
Santiago Vidal
CONICET
svidal@exa.unicen.edu.ar
J. Andrés Diaz Pace
CONICET
adiaz@exa.unicen.edu.ar
Presentación: 11/2016.
Aprobación: 07/2017.
Los
Palabras clave:
Keywords:
Para facilitar la evolución de un sistema de software se debe realizar un proceso de mantenimiento que prevenga su “envejecimiento” [1]. Este proceso, generalmente, implica la reestructuración del sistema para solucionar problemas en el diseño o errores. La reestructuración del sistema se realiza mediante la técnica de refactoring [2], la cual permite modificar la estructura interna del mismo sin alterar el comportamiento externo.
El proceso de refactorización de un sistema consta de tres etapas [3]. En primer lugar, se deben identificar las partes del sistema a reestructurar. Para esto se emplean los
Uno de los problemas que surge al aplicar un conjunto de refactorings, es el orden en que se deben aplicar. Esto se debe a que, un refactoring puede depender de la reestructuración que realiza otro refactoring. Entonces, se debe aplicar primero el refactoring que realiza la reestructuración de la cual dependen otros refactorings. Esto es lo que se conoce como refactorización en cascada [5]. Otro problema que puede surgir al aplicar un conjunto de refactorings, es que un refactoring interfiera con la aplicación de otros refactorings. Esto puede suceder debido a que un refactoring hace referencia a un artefacto del sistema que fue modificado previamente por otro refactoring, y quedó mal referenciado [6].
Si bien existe un conjunto de herramientas [7, 8, 9, 10, 11] que buscan refactorizar
En este trabajo se presenta RAtool (Refactoring Analysis tool), la cual realiza un análisis de dependencia entre los refactorings recomendados para solucionar los problemas identificados por los
La herramienta recibe como entrada el código del sistema, junto con los
Para demostrar los beneficios del enfoque se realizó un caso de estudio sobre el sistema SportsTracker. Para este sistema, se llevó a cabo el proceso de refactorización, se detectaron los
El resto de este trabajo está organizado de la siguiente manera. La Sección 2 presentan conceptos de evolución del software y mantenimiento de los sistemas. La Sección 3 describe los trabajos relacionados. La Sección 4 discute los principales problemas para lograr automatizar el refactoring de
Los sistemas de software inevitablemente sufrirán cambios, una vez entregados, motivados por nuevos requerimientos funcionales, cambios en el negocio, cambios en el ambiente, correcciones a fallos encontrados en el funcionamiento, actualizaciones de los requerimientos existentes, etc. [1]. Una de las soluciones que se han propuesto tradicionalmente es diseñar para el cambio, con lo cual se tratan de prevenir los eventuales problemas que pueden surgir con la evolución del software. Con este propósito algunas de las técnicas que se han utilizado son [12]: interfaces, ocultamiento de información, separación de concerns, capas, herencia, polimorfismo, patrones de diseño, documentación actualizada.
Para que el sistema no quede obsoleto, éste debe evolucionar junto con sus requerimientos. Con este fin, se debe realizar un proceso de mantenimiento. Este proceso, generalmente, implica la reestructuración del sistema, para mejorar el diseño del sistema o solucionar errores. La reestructuración del sistema se realiza mediante la técnica de refactoring, la cual permite mejorar la estructura interna del sistema, sin modificar su funcionalidad, antes de introducir los cambios necesarios. El objetivo es lograr un balance entre una buena codificación y un buen diseño, permitiendo mantener el sistema evolucionable [2].
El proceso de refactorización de un sistema consta de tres etapas [3]. En primer lugar, se deben identificar las partes del sistema a reestructurar. Para esto se emplean los
Una vez identificados los
Existen varias herramientas que se enfocan en las distintas etapas del proceso de refactorización. Algunas identifican
Por ejemplo, JSpIRIT [7] es una herramienta que permite identificar los
Otros enfoques se centran, particular- mente, en aplicar los refactorings de acuerdo a los
Mens y Tourwé [3][5] analizan la posibilidad de refactorizar automáticamente
Similarmente, Liu et al. [6] proponen un enfoque para el análisis de refactorings con el objetivo de resolver los problemas del refactoring en cascada. Este enfoque propone realizar un análisis entre refactorings para planificar el orden en que se deben aplicar en el código. Para esto, se propone realizar una matriz de conflictos entre refactorings que posteriormente se traduce a un grafo direccional. Mediante algoritmos heurísticos que recorren el grafo, se busca un correcto orden de aplicación para los refactorings. El enfoque propuesto en [6] destaca la importancia del análisis de refactorings y su validez, pero no presenta herramienta que lo materialice.
En este contexto, sería interesante proveer un enfoque que permita materializar el análisis de dependencia entre refactorings. Mediante este análisis, sería posible encontrar un correcto orden de aplicación de los refactorings, respetando el principio de refactorización en cascada, y resolver los conflictos existentes. De esta manera, se permitiría aplicar el conjunto de refactorings, necesarios para solucionar los problemas indicados por los
Una forma de identificar los refactorings a utilizar, es a través de los
Dado que un refactoring puede depender de la reestructuración que realiza otro refactoring, pueden surgir problemas al momento de aplicar refactorings en cascada. La Figura 1 muestra un ejemplo de este concepto. El método getArea() de la clase Cylinder está afectado por el code smell Feature Envy. Este smell consiste de métodos que utilizan más datos de otras clases que los de la propia. Por esta razón, se aconseja mover el método a la clase en la cual se encuentran los datos que utiliza. Adicionalmente, parte del método getArea() debería ser extraído para que éste resulte más legible. En este contexto, considere que es necesario utilizar el refactorings Move Method [2] para mover el método getAreaBase() a la clase CylinderData y el refactoring Extract Method [2] para extraer las 2 primeras líneas del método getArea() a uno nuevo denominado getAreaBase(). Si primero se intenta aplicar el Move Method se genera un conflicto, debido a que el método getAreaBase() no existe inicialmente en la clase Cylinder. El método referenciado es generado por Extract Method. Entonces, el Move Method depende del Extract Method, y por lo tanto se debe realiza primero la reestructuración indicada por éste último para poder aplicar el Move Method.
Figura 1: Ejemplo de refactoring de code smell
Otro problema que puede surgir al aplicar un conjunto de refactorings, es que un refactoring puede hacer referencia a un artefacto del sistema que fue modificado previamente por otro refactoring, y quedar mal referenciado [6]. Por ejemplo, esto ocurriría si en la clase Cylinder, se aplica el Extract Method descripto anteriormente, y luego un Rename para renombrar la variable local radiusSquare, dentro del método getArea(). En este caso, el conflicto se genera porque el artefacto referenciado por el Rename fue modificado por el Extract Method.
A partir de los ejemplos presentados anteriormente, se puede inferir que para aplicar un conjunto de refactorings se debe llevar a cabo un análisis de dependencias. Esto se debe a que las dependencias entre refactorings pueden generar conflictos que impidan que se apliquen refactorings en cascada.
En este trabajo, se propone un enfoque llamado Refactoring Analisys (RA) que permite analizar las dependencias entre refactorings para solucionar los
Figura 2: Esquema conceptual del enfoque
El enfoque se divide en tres fases: fase de análisis, fase de resolución de conflictos y fase de generación de código. La fase de análisis, recibe la lista de refactorings e identifica los conflictos que se generan por la aplicación en cascada de los mismos. Una vez concluida la fase de análisis, se obtiene una lista ordenada de refactorings y los conflictos que existen entre ellos. Los conflictos, se toman como parámetro de entrada de la segunda fase, denominada fase de resolución de conflictos. Durante esta fase, se utiliza un conjunto de reglas para corregir los conflictos identificados en la fase anterior. Esta fase genera como salida una lista ordenada sin conflictos de refactorings. En la última fase, la de generación de código, se aplican los refactorings sobre el código, generando como salida el código refactorizado. El enfoque ha sido materializado en la herramienta RAtool la cual es un plugin para Eclipse.
A continuación se detallan cada una de dichas fases. El enfoque es ilustrado con la refactorización de los
Se utiliza JSpIRIT [7] para identificar los
CodeSmell |
Cantidad |
BrainClass |
4 |
BainMethod |
8 |
Data Class |
1 |
DispersedCoupling |
42 |
FeatureEnvy |
62 |
GodClass |
10 |
IntensiveCoupling |
23 |
RefusedParentBequest |
16 |
ShotgunSurgery |
25 |
TOTAL |
191 |
Tabla 1.
En base a los
Figura 3: Lista de
La fase de análisis tiene como objetivo analizar las dependencias entre los refactorings de los
Los conflictos entre refactorings, se pueden producir por el orden de refactorización o porque un refactoring referencia un artefacto del sistema que fue modificado por otro refactoring, aplicado anteriormente, y quedó mal referenciado [6]. Si el conflicto se produjo porque el artefacto fue modificado previamente o porque no existe, pero es creado por otro refactoring que no se aplicó, se lo considera una incompatibilidad que tiene solución. Por el contrario, si el artefacto nunca existió en el código y ningún otro refactoring lo genera, es una incompatibilidad que no tiene solución posible. Por esta razón, se distinguen los siguientes tipos de incompatibilidades:
•Incompatibilidades que tienen solución: son los conflictos detectados durante el análisis que pueden ser resueltos por la herramienta. Dentro de éstos existen dos tipos:
•Parámetros mal definidos: El refactoring referencia un artefacto del sistema que fue modificado previamente y quedó mal referenciado. También denominado warning.
•Orden de aplicación incorrecto: Se trata del principio de refactorización en cascada. Un refactoring trata de modificar un artefacto que no existe en el sistema, pero éste es creado por otro refactoring.
•Incompatibilidades que no tienen solución: Son aquellos conflictos que no pueden ser resueltos por la herramienta ya que hacen referencia a un artefacto que no existe en el sistema y no es generado por ningún refactoring. También denominadas error.
En la Figura 4 se muestra el pseudocódigo del algoritmo de análisis, a través del cual se analizan los refactorings para determinar si son aplicables (Actividad 1).
Si es aplicable, es decir, no presenta conflicto, se agrega a una lista de refactorings ordenados (Actividad 2). En caso contrario, se lo agrega a una lista de refactorings con conflictos (Actividad 4).
En este punto es importante destacar que, se considera aplicable al primer refactoring que se pueda aplicar al código fuente. Esto se debe a que, se considera importante reducir los tiempos de análisis, debido a que la actividad de mantenimiento para facilitar la evolución de un sistema, generalmente no es una actividad solicitada por el cliente.
La lista de refactorings con conflictos almacena los refactorings cuyas incompatibilidades no tienen solución o pueden solucionarse por un orden de aplicación correcto. Entonces, por cada refactoring aplicable se chequea si, con el nuevo artefacto generado, se pueden resolver los conflictos de los refactorings incluidos en dicha lista.
La Figura 5 muestra un pseudocódigo que detalla el proceso utilizado para determinar si un refactoring es aplicable (Actividad 1). Para determinar si un refactoring es aplicable, se identifica si el artefacto referenciado se encuentra en la estructura de historial de cambios (Actividad 1.1). Si el artefacto es encontrado significa que ya fue modificado por un refactoring anterior y en este caso se registra el nuevo cambio, indicando si existen incompatibilidades de parámetros mal definidos (Actividad 1.2). Si existen incompatibilidades de este tipo, el refactoring igualmente es considerado aplicable, ya que éstas se pueden solucionar en la siguiente fase.
Si no existe el artefacto en el historial de cambios, se chequea que exista en el código fuente (Actividad 1.3). Si se encuentra en el código, se crea el cambio en el historial de cambios y se toma el refactoring como aplicable (Actividad 1.4). En caso que el artefacto no exista en la estructura de cambios ni en el código, se ha identificado una incompatibilidad por orden de aplicación incorrecto o que no tiene solución, y el refactoring es considerado no aplicable.
Figura 4:Pseudocódigo del algoritmo de análisis.
Figura 5: Pseudocódigo de la actividad de análisis.
Las incompatibilidades generadas por un orden de aplicación incorrecto se generan dependiendo del orden en que se aplican los refactorings de entrada. Por lo tanto, no se puede anticipar este tipo de incompatibilidades. Por el contrario, las incompatibilidades de parámetros mal definidos son provocadas por un conjunto fijo de conflictos, que pueden ser descriptos independientemente de los refactorings de entrada. Estos conflictos surgen por dependencias entre los refactorings, las cuales pueden ser preestablecidas. Por ejemplo, si se realiza un Rename-Package, todos los refactorings posteriores que afectan elementos dentro del paquete renombrado, deben tener en cuenta que el nombre de éste cambió.
Además, existen incompatibilidades que no tienen solución, que también surgen por las dependencias preestablecidas entre refactorings. Por ejemplo, dos refactorings Extract Method intentan extraer un conjunto de líneas que se sobreponen, por lo que uno de los dos no se puede aplicar. En este caso se aplica el primer refactoring tomado como aplicable.
Para analizar las dependencias entre refactorings y su posible solución se ha definido un catálogo de incompatibilidades [13].
En la Figura 6 se muestran las dependencias que existen entre los refactoring incluidos en la herramienta RAtool.
En la primera columna se listan los refactorings precedentes y en la primera fila los que se aplican luego de los precedentes. El contenido de la matriz indica si la aplicación, en conjunto, del par de refactorings correspondiente, puede generar incompatibilidades.
Es decir, si los refactorings pueden tener una dependencia preestablecida, por modificar un mismo artefacto, y por lo tanto requieren análisis.
Figura 6: Dependencias preestablecidas entre refactorings.
El resultado de la fase de análisis de SportsTracker es mostrado en la Figura 7, donde se puede observar la lista ordenada de refactorings (Ordered Refactoring List) y la lista de refactorings con incompatibilidades que no pudieron solucionarse (Conflicting Refactoring List).
La lista de refactorings ordenados contiene los refactorings que se aplicarán al código. Dentro de la lista ordenada, se distinguen con un tick aquellos refactorings que no presentan conflictos, o presentaban incompatibilidades por orden de aplicación incorrecto, pero ya fueron resueltas. Además, se identifican los warning, que se corresponden con incompatibilidades de parámetros mal definidos. Estos últimos conflictos se resuelven antes de aplicar los refactorings al código.
Figura 7: Resultado del análisis de dependencia entre refactorings (vista parcial)
Para resolver los conflictos generados por un orden de aplicación incorrecto, en primer lugar se agrupan todos los Extract Methods.
Esto se realiza así, debido a que el refactoring Extract Method recibe como parámetro el start position (sp) y el length de las selecciones de statements a extraer.
Estos valores no son relativos al método desde donde se extraen los statements, sino que corresponden a la posición que ocupan estos statements dentro de la clase.
Ante esta situación, cualquier modificación que se realice en la clase, antes de un Extract Method, cambia estos valores y puede impedir que el refactoring se aplique.
Por este motivo, también se los ordena por mayor valor de sp.
De este modo, un Extract Method no interfiere con los demás, ya que se empieza por el fin de la clase, extrayendo los nuevos métodos debajo.
De esta manera, no se ve afectado el sp y el length de los demás Extract Methods de la clase.
Luego, se realizan los Extract Class necesarios para realizar los Move Methods, y posteriormente se aplican los Move Methods correspondientes.
Aquellos refactorings que no tienen dependencias con los demás, se aplican en cualquier orden luego de los Extract Methods.
Los conflictos detectados por parámetros mal definidos, para el caso presentado, se generan debido a los refactorings de tipo Rename.
Por ejemplo, el refactoring 116.3 Rename (Figura 7) intenta renombrar el método drawItem de la clase StackedRenderer.
Para esto, en primer lugar, se chequea en la estructura de historial de cambios si existe el método a renombrar. En este caso, el método es encontrado en dicha estructura, debido a que fue movido a otra clase en un refactoring previo.
Ante esta situación, se detecta en el Rename una incompatibilidad de parámetro mal definido. Ésta se identifica mediante el código correspondiente (ChangeW04).
Este código indica que el método fue movido (Tabla 2).
De manera similar, se detectan las incompatibilidades de parámetros mal definidos en el resto de los refactorings (Tabla 2).
Tabla 2. Incompatibilidades de parámetros
Las incompatibilidades que no pudieron ser resueltas son listadas en Conflicting Refactoring List. Por ejemplo, el refactoring 86.15 MoveMethod (Figura 7) intenta mover el método convertUnitToEnglish de la clase OverviewDialogController.
Para esto, en primer lugar, se chequea en la estructura de historial de cambios si existe el método a mover.
En este caso, el método es encontrado en dicha estructura, y debido a que fue movido a otra clase en un refactoring previo, no puede moverse por el refactoring 86.15 MoveMethod. Ante esta situación, se detecta una incompatibilidad que no puede ser resuelta.
Ésta se identifica mediante el código correspondiente (ChangeE04). Este código indica que el método fue movido.
De manera similar, se detectaron otras 8 incompatibilidades que no pudieron ser resueltas por RAtool.
Esta fase tiene como objetivo resolver las incompatibilidades de parámetros mal definidos detectadas durante la fase anterior y los conflictos generados por los cambios (Figura 8).
Se recibe el refactoring con las incompatibilidades de parámetros mal definidos y el código que identifica cada incompatibilidad.
En base al refactoring y al código recibido, se emplea la regla correspondiente.
La regla lee los datos de la estructura de historiales de cambios y modifica los parámetros mal definidos del refactoring, con los datos obtenidos.
De esta manera, se obtiene el refactoring sin incompatibilidades.
En la Tabla 3 se listan todas las posibles incompatibilidades analizadas por nuestro enfoque. Por cada una se detalla el código al que se asocia la regla de incompatibilidad, una descripción del problema que indica y el tipo de incompatibilidad que describe, si tiene solución (warning) o si no tiene solución (error).
Las reglas redefinen los parámetros del refactoring en conflicto, en base al problema que indica su código. Los códigos que indican incompatibilidades de parámetros mal definidos son las del tipo warning. Por ejemplo, se tiene como entrada un Rename, que renombra el nombre de un método, y un conflicto representado por el código ChangeW01, que indica que el nombre del método referenciado cambió. En este caso, lo que hace la regla es cambiar el atributo del refactoring que hace referencia al nombre del método origen, con el último nombre asignado al método. Para esto, se utiliza la estructura de cambios, en donde se tiene registro de todos los cambios realizados a los artefactos del sistema. De igual manera se definen las reglas para todos los refactorings, en relación a los códigos que pueden aparecer en cada tipo.
Figura 8: Resolución de conflictos entre refactorings
Las reglas que resuelven los conflictos introducidos por los cambios generados por los refactorings, renombran el artefacto generado. Estas reglas, agregan al nuevo nombre asignado, el identificador del refactoring que generó el conflicto y el del
Luego de aplicar todas las reglas a la lista de refactorings, se obtiene una lista ordenada de refactorings libres de conflictos. Esta lista es utilizada en la siguiente fase de generación de código refactorizado.
Solo se encontraron tres incompatibi-lidades de parámetros mal definidos en SportsTracker (Tabla 2). En los primeros dos casos, se genera el conflicto debido a que se intenta renombrar un field que fue extraído. En estos casos, RAtool recibe el código de warning correspondiente (ChangeE02), junto con el refactoring. Luego, empleando las reglas definidas para el refactoring Rename y la estructura de cambios, que almacena todos los cambios realizados a los artefactos del sistema, modifica el parámetro que hace referencia a la clase en la que estaban contenidos estos field, con el de la clase a la que fueron movidos.
En el caso del 116.3 Rename (Figura 7), el conflicto aparece debido que se intenta renombrar un método que fue movido a otra clase por un refactoring previo, y quedó mal definido. En este caso, RAtool recibe el refactoring y el código ChangeW04. Luego, haciendo uso de las reglas modifica el parámetro que hace referencia a la clase en la que se encuentra el método, con el de la clase a la que fue movido.
Esta fase tiene como objetivo aplicar los refactorings al código de manera automatizada. Para llevar a cabo su funcionalidad recibe como parámetro el código fuente del sistema y la lista de refactorings ordenados.
Tabla 3: Incompatibilidades detectadas por el enfoque.
La herramienta utilizada para aplicar los refactorings en el plugin de Eclipse LTK [14]. Esta herramienta permite aplicar los pasos atómicos de un refactoring en el código de manera automática. Los refactorings en Eclipse siguen un procedimiento predefinido:
1. El refactoring es iniciado.
2. Se realiza un chequeo inicial para determinar si el refactoring es aplicable en el contexto deseado (checkInitialConditions()).
3. Se provee la información adicional de ser necesaria.
4. Luego de tener toda la información necesaria para aplicar el refactoring, se hace un nuevo chequeo (checkFinalConditions()) y se calculan los cambios individuales.
5. Se crean los cambios y se aplican en el código.
Para hacer los chequeos iniciales no se requiere que el refactoring este provisto de todos los parámetros, pero sí aquellos que hacen referencia al contexto en el que se aplicará el mismo.
En el caso de este trabajo, los refactorings son provistos de todos sus parámetros en primer lugar y luego se hacen los chequeos de las condiciones iniciales y finales. Por último, se crea el cambio y se aplica en el código.
En la Figura 9 se muestra un ejemplo de la creación del refactoring Rename. En primer lugar, se proveen los parámetros necesarios por el refactoring y luego se lleva a cabo el ciclo de vida del mismo.
Como resultado de aplicar todos los refactorings, de la lista de entrada de esta fase, se obtiene el código del sistema refactorizado.
Figura 9: Refactoring Rename automatizado
Con el objetivo de realizar una validación inicial de nuestro enfoque, en esta sección se presentan los resultados de aplicar RATool a SportsTracker (los detalles del proceso de aplicación fueron presentados en la Sección 5).
En la Tabla 4 se listan los
Además, se muestra la cantidad de refactorings aplicados para cada code smell, la cantidad de warnings detectados y la cantidad de incompatibilidades que no pudieron ser resueltas.
De esta tabla, se puede deducir lo compleja que resulta la tarea de refactorización de un sistema.
Para dos clases tomadas del sistema SportsTracker, fueron necesarios 73 refactorings para solucionar los problemas detectados.
Además, se puede observar que mediante el análisis de dependencias entre refactorings, el número de refactorings que se pueden aplicar son 63.
Dentro de los refactoring que se pueden aplicar, se detectaron 3 warnings, es decir, incompatibilidades de parámetros mal definidos.
Por otro lado RAtool identificó 10 incompatibilidades que no pudo resolver.
Teniendo en cuenta la totalidad de refactorings propuestos (para las dos clases tomadas del sistema SportsTracker) y los aplicados, se puede decir que RAtool realizó un 86% de las refactorizaciones.
Mediante los refactorings propuestos se logró reducir la cantidad de líneas de la clase OverviewDialogController de 1061 a 746, y de la clase StackedRenderer de 554 a 390, lo que significa que se redujo su complejidad.
Además, su funcionalidad quedó mejor encapsulada y se resolvieron la mayoria de los
|
Cantidad de refactoring seleccionados |
Cantidad de refactorings aplicados |
Cantidad de warnings detectados |
Cantidad de incompatibilidades no resueltas |
Clase Overview Dialog Controller |
||||
76. Brain Method |
8 |
7 |
0 |
1 |
78. Dispersed Coupling |
1 |
1 |
0 |
0 |
79. Brain Method |
7 |
7 |
0 |
0 |
81. Dispersed Coupling |
3 |
3 |
0 |
0 |
82. Dispersed Coupling |
2 |
2 |
0 |
0 |
85. Intensive Coupling |
10 |
5 |
0 |
5 |
86. Brain Class |
30 |
26 |
0 |
4 |
SUBTOTAL |
61 |
51 |
0 |
10 |
Clase Stacked Renderer |
||||
114. Brain Method |
6 |
6 |
2 |
0 |
116. God Class |
6 |
6 |
1 |
0 |
SUBTOTAL |
12 |
12 |
3 |
0 |
TOTAL |
73 |
63 |
3 |
10 |
Tabla 4.
En la Tabla 5 se listan los refactorings seleccionados en base al tipo de refactoring.
Adicionalmente, se indica la cantidad propuesta de cada tipo y la cantidad aplicada.
Se puede inferir que, para el caso de estudio, los refactorings que mayor inconveniente presentan son los Move Methods.
Para este tipo de refactoring, RAtool aplica el 76% de los seleccionados.
Refactorings |
Cantidadpropuesta |
Cantidadaplicada |
Extract Method |
30 |
29 |
Extract Class |
7 |
6 |
Move Method |
33 |
25 |
Rename |
3 |
3 |
TOTAL |
73 |
63 |
Tabla 5. Refactorings utilizados para la refactorización.
Luego los Extract Method y Extract Class son los que menor inconvenientes presentan, aplicando el 97% y el 86% respectivamente.
Por último, los Renames se aplican en el 100% de los casos dado que no generan incompatibilidades que no puedan ser resueltas.
Clase |
|
|
|
|
|
Overview Dialog Controller |
13 |
7 |
9 |
1 |
8 |
Stacked Renderer |
3 |
2 |
2 |
0 |
0 |
Tabla 6.
En la Tabla 6 se presentan los
Además, se presenta la cantidad de
Se observa que, para la clase OverviewDialogController se seleccionaron un total de 13
Como resultado de aplicar los refactorings, se resolvieron completamente 9 de los 13
De los
Con respecto a la clase StackedRenderer, se seleccionaron 3
Como resultado de aplicar los refactoring se solucionaron los 2
Por otro lado, utilizando la métrica LoC (Lines of Code) [15] se puede determinar qué tan complejo y propenso a errores es un sistema, en termino de cantidad de líneas.
Generalmente, cuanto más grande sea el tamaño del código de una clase, más complejo y propenso a errores será.
En el caso de estudio, para ambas clases analizadas, se redujo el número de líneas en un 30%, lo cual da indicios de que la complejidad de estas clases disminuyó.
Además, al dividir la funcionalidades de los Brain Methods en métodos con responsabilidad más limitada, y separar la funcionalidades de las God Class y BrainClass en nuevas clases, se mejoró el encapsulamiento de datos.
Al mejorarse el encapsulamiento de datos, se mejora la cohesión de las clases, ya que ahora las clases refactorizadas ofrecen servicios más relacionados a su propósito.
Otra métrica que puede utilizarse es CBO (Coupling Between Objects) [15]. Esta métrica permite conocer el número de clases acopladas a una clase.
Cuanto más alto es este valor más dependencias existen entre las clases, ya que utilizan más métodos o variables de otra clase.
Esto afecta negativamente al diseño modularizado del sistema. Para el caso de estudio, si bien se mejoró la modularidad dividiendo funcionalidad en distintas clases, el acoplamiento aumentó.
Esto se debe a que, ahora las clases deben utilizar variables de instancias de otras clases y los métodos que fueron encapsulados, para realizar la funcionalidad que antes era propia.
Al aplicar un conjunto de refactorings para solucionar un code smell pueden surgir problemas que impiden que éstos se apliquen. Estos problemas pueden surgir porque el orden de refactorización no es el correcto.
Es decir, se intenta aplicar un refactoring que depende de una reestructuración realizada por otro refactoring, que todavía no se aplicó.
Además, se puede presentar un problema cuando un refactoring referencia un artefacto del sistema que fue modificado por otro refactoring, aplicado anteriormente, y quedó mal referenciado.
Por estos motivos, para aplicar un conjunto de refactorings, se deben analizar las dependencias que existen entre estos.
Este trabajo presenta un enfoque que aborda estas problemáticas.
El enfoque se soporta en una herramienta denominada RAtool, la cual es un plugin para Eclipse.
Esta herramienta toma como entrada los
Luego, tomando los refactorings de entrada, se realiza un análisis de dependencia entre refactorings, con el objetivo de identificar los conflictos existentes.
De esta manera, se encuentra un correcto orden de aplicación de los refactorings y se resuelven estos conflictos.
El caso de estudio presentado, si bien es acotado, permite demostrar la factibilidad del enfoque para resolver incompatibilidades y automatizar la aplicación de conjuntos de refactorings.
Como limitación puede mencionarse el hecho de que actualmente el enfoque soporta un conjunto de 8 refactorings por lo que no es posible resolver cualquier tipo de code smell.
Por esta razón, como trabajo futuro se espera extender el enfoque con nuevos tipos de refactorings.
Adicionalmente, se pretende validar la efectividad del enfoque con un mayor número de sistemas.
Parnas, D. L. (1994).
Fowler, M. (2009).
Mens, T., Tourwé T. and Munoz F. (2003).
Lanza, M., and Radu M. (2007).
Tourwé, T., and Tom M. (2003).
Liu, H., Li, G., Ma, Z., y Shao, W. (2007).
Vidal, S., Marcos C., y Díaz-Pace, J. A. (2014).
Tsantalis, N. (2010). E
Herbold, S., Jens, G., y Helmut, N. (2009).
Roberts, D., Brant J., y Johnson, R. (1997).
Seguin, C. y Atkinson, M. J. Refactory. http://jrefactory.sourceforge.net/
Sommerville, I. (2004).
Ditz, L. M., Vidal, S., y Marcos C. (2016).
Frenzel, L. (2006).
Chidamber, S. R., y Kemerer, C. F. (1994).
1El plugin puede ser descargado en https://db.tt/zm5Q6oJ6
2 http://www.saring.de/sportstracker/index.html