Está en la página 1de 113

Aplicación de un Proceso de Refactoring

guiado por Escenarios de Modificabilidad y


Code Smells

Trabajo final entregado para el grado de


Ingeniería en Sistemas
en la Facultad de Ciencias Exactas

Por

Manuel Alonso
Facundo H. Klaver

Bajo la supervisión de
Dr. Andrés Díaz-Pace
Dr. Santiago Vidal

Universidad Nacional del Centro de


la Provincia de Buenos Aires
Tandil, Argentina
Noviembre 2015
A mis padres, Claudia y Daniel.
A mi hijo Fermín y su mamá.

MA

A mis padres, Susana y Daniel.


A mi madrina Alicia.
A mi compañera de la vida Eliana.

FHK

También queremos agradecer a nuestros directores, Andrés y Santiago,


que nos ayudaron en todo lo posible para sacar adelante este trabajo.

Y en especial, a todos nuestros cumpas,


al Estado Nacional y la Universidad Pública,
por el apoyo y todo el aprendizaje de estos años.
Indice de Contenidos
1.Introducción.......................................................................................................7
1.1.Modificabilidad como objetivo de calidad.....................................................................7
1.2.Proceso de refactoring...................................................................................................8
1.3.Caso de estudio...............................................................................................................9
1.4.Esquema general........................................................................................................... 10
2.Marco teórico...................................................................................................11
2.1.Evolución de los sistemas de software.......................................................................11
2.2.Refactoring.................................................................................................................... 12
2.2.1.Beneficios de refactorizar........................................................................................13
2.2.1.1.Mejora el Diseño de Software...................................................................................13
2.2.1.2.Hace al software fácil de entender...........................................................................13
2.2.1.3.Ayuda a encontrar errores........................................................................................13
2.2.2.Momentos para refactorizar.....................................................................................14
2.2.2.1.Al agregar una funcionalidad....................................................................................14
2.2.2.2.Cuando se necesita corregir un error.......................................................................14
2.2.2.3.Al revisar el código....................................................................................................15
2.2.3.Refactoring y diseño................................................................................................15
2.2.3.1.Refactoring como una alternativa de diseño............................................................15
2.2.3.2.Cambio de énfasis....................................................................................................15
2.2.3.3.Flexible......................................................................................................................16
2.3.Calidad en el desarrollo de software...........................................................................16
2.3.1.Atributos de calidad.................................................................................................16
2.3.2.Funcionalidad y arquitectura....................................................................................17
2.3.3.Modificabilidad......................................................................................................... 18
2.3.3.1.Artefacto a modificar.................................................................................................18
2.3.3.2.Ambiente en que se realiza la modificación.............................................................18
2.3.4.Escenarios de Modificabilidad..................................................................................19
2.3.5.Tácticas de modificabilidad......................................................................................21
2.3.5.1.Localización de modificaciones................................................................................22
2.3.5.2.Prevención de efecto dominó...................................................................................22
2.3.5.3.Retraso de tiempo de Binding..................................................................................22
2.3.6.Deuda Técnica.........................................................................................................22
2.3.6.1.Retrospectiva de deuda técnica...............................................................................23
2.4.Métricas y code smells.................................................................................................24
2.4.1.Code Smells............................................................................................................ 25
2.4.1.1.Desarmonías de identidad........................................................................................26
2.4.1.2.Desarmonías de Colaboración.................................................................................28
2.4.1.3.Desarmonías de Clasificación..................................................................................29

2
2.5.Testing............................................................................................................................ 29
2.6.Resumen........................................................................................................................ 30
3.Trabajos Relacionados...................................................................................32
3.1.Aspectos generales del refactoring de software........................................................32
3.1.1.Formalización de los procesos de refactoring..........................................................32
3.1.2.Desafíos y beneficios de refactorizar.......................................................................32
3.1.3.Dos tácticas de refactoring.......................................................................................34
3.1.4.Herramientas para cada táctica de refactoring.........................................................34
3.1.5.Refactoring guiado por métricas..............................................................................34
3.2.Enfoques de refactoring...............................................................................................35
3.2.1.Identificación de oportunidades de refactoring.........................................................35
3.2.2.Refactoring basado en herramientas de visualización.............................................35
3.2.3.Refactoring orientado por features...........................................................................36
3.2.4.Modelo matemático para refactoring........................................................................37
3.3.Impacto de refactorings en la calidad del software....................................................37
3.3.1.Efectos del refactoring en la calidad del software....................................................37
3.3.2.Acoplamiento y cohesión como indicadores de buena calidad................................38
3.3.3.Relación entre calidad y refactoring en casos de estudio........................................38
3.3.4.Modificabilidad de arquitecturas de software...........................................................38
3.3.5.Impacto de técnicas de refactoring en las métricas de calidad................................39
3.4.Refactorings guiados por code smells........................................................................39
3.4.1.Evaluación de sistemas a partir del análisis objetivo y el análisis subjetivo.............40
3.4.2.Detección automatizada de code smells..................................................................40
3.4.3.Identificación de Architectural Smells.......................................................................40
3.4.4.Detección de code smells relevantes para la arquitectura.......................................41
3.4.5.Orden apropiado de resolución de code smells.......................................................41
3.4.6.Una experiencia con code smells en Extreme Programming (XP)...........................42
3.5.Resumen........................................................................................................................ 43
4.Proceso de Refactorización...........................................................................44
4.1.Definición del Proceso..................................................................................................44
4.1.1.Refactoring iterativo por etapas...............................................................................45
4.1.1.1.Etapa 1: Análisis e identificación del problema ¿Qué refactorizar?.........................47
4.1.1.2.Etapa 2: Planteo de solución y definición de escenarios.........................................49
4.1.1.3.Etapa 3: Priorización de backlog. ¿Por dónde conviene empezar?.........................50
4.1.1.4.Etapa 4: Implementación de escenario. ¡Refactorizar!.............................................51
4.1.1.5.Etapa 5: Medición de resultados. ¿Es válida la refactorización?.............................53
4.1.1.6.Etapa 6: Evaluación. ¿Seguir refactorizando?.........................................................54
5.Caso de Estudio..............................................................................................55
5.1.SocialGraph, una herramienta para analizar redes sociales.....................................55
5.1.1.Sobre el Análisis de Redes Sociales........................................................................55

3
5.2.Definición del sistema...................................................................................................56
5.2.1.Versiones de SocialGraph.......................................................................................56
5.2.2.Arquitectura actual de SocialGraph.........................................................................56
5.2.3.Funcionalidades de SocialGraph.............................................................................60
5.2.4.Requerimientos de SocialGraph..............................................................................60
5.3.Aplicación del proceso de refactorización a SocialGraph.........................................61
5.4.Iteración 0: Preliminar...................................................................................................61
5.4.1.Análisis general del problema..................................................................................61
5.4.1.1.Anomalía 1: Estructura del código del proyecto.......................................................62
5.4.1.2.Anomalia 2: Acoplamiento entre capas....................................................................62
5.4.1.3.Anomalía 3: Diseño ligado a correos electrónicos...................................................62
5.4.2.Priorización de las anomalías..................................................................................62
5.4.3.Cobertura de test de la aplicación............................................................................64
5.4.4.Observaciones de la Iteración 0...............................................................................65
5.5.Iteración 1: Re-estructuración del Proyecto...............................................................65
5.5.1.Etapa 1: Análisis e identificación del problema........................................................65
5.5.1.1.Documentación del proyecto....................................................................................65
5.5.1.2.Detección de code smells.........................................................................................65
5.5.1.3.Identificación de los problemas en el diseño............................................................66
5.5.2.Etapa 2: Planteo de solución y definición de escenarios..........................................67
5.5.2.1.Definir refactorings a realizar....................................................................................67
5.5.2.2.Especificar Escenarios de Modificabilidad................................................................67
5.5.2.2.1.Escenario 1.1. Mavenizar Proyecto.........................................................................................67
5.5.2.2.2.Escenario 1.2. Reestructurar dependencia con aplicación WordCram...................................68
5.5.2.2.3.Escenario 1.3. Reestructurar dependencia con aplicación GATE...........................................69
5.5.2.3.Agregar al backlog....................................................................................................69
5.5.3.Etapa 3: Priorización de backlog..............................................................................69
5.5.3.1.Nivel de Impacto.......................................................................................................70
5.5.3.2.Nivel de Esfuerzo......................................................................................................70
5.5.3.3.Ubicación en cuadrantes..........................................................................................70
5.5.4.Etapa 4: Implementar Escenarios............................................................................71
5.5.4.1.Escenario 1.1: Mavenizar proyecto..........................................................................71
5.5.4.1.1.Verificar cobertura de test........................................................................................................71
5.5.4.1.2.Implementar Escenario............................................................................................................71
5.5.4.1.3.Ejecutar casos de test..............................................................................................................75
5.5.4.2.Escenario 1.2: Reestructurar dependencia con aplicación WordCram Proyecto.....76
5.5.4.2.1.Verificar cobertura de test........................................................................................................76
5.5.4.2.2.Implementar Escenario............................................................................................................76
5.5.4.2.3.Ejecutar casos de test..............................................................................................................76
5.5.4.3.Escenario 1.3. Reestructurar dependencia con aplicación GATE............................76
5.5.4.3.1.Verificar cobertura de test........................................................................................................76
5.5.4.3.2.Implementar Escenario............................................................................................................77
5.5.4.3.3.Ejecutar casos de test..............................................................................................................78

4
5.5.4.3.4.Notas sobre la implementación de los escenarios...................................................................78
5.5.5.Etapa 5: Medición de Resultados............................................................................79
5.5.5.1.Análisis basado en métricas y documentación.........................................................79
5.5.5.2.Análisis subjetivo y valoración del grupo de desarrollo............................................79
5.5.5.3.Validación de escenarios..........................................................................................80
5.5.6.Etapa 6: Etapa 6: Evaluación...................................................................................80
5.6.Iteración 2: Diseño ligado a correos electrónicos......................................................80
5.6.1.Etapa 1: Análisis e identificación del problema........................................................80
5.6.1.1.Documentación del proyecto....................................................................................80
5.6.1.2.Detección de code smells.........................................................................................82
5.6.1.3.Identificación de los problemas en el diseño............................................................83
5.6.2.Etapa 2: Planteo de solución y definición de escenarios..........................................83
5.6.2.1.Definir refactorings a realizar....................................................................................83
5.6.2.2.Especificar Escenarios de Modificabilidad................................................................83
5.6.2.2.1.Escenario 3.1: Abstraer el modelo para la construcción del grafo...........................................83
5.6.2.2.2.Escenario 3.1.1: Aplicar patrón Strategy a la clase GraphBuilder...........................................84
5.6.2.2.3.Escenario 3.1.2: Abstraer la clase CommunicationEdge de la clase Mail en el modelo..........85
5.6.2.2.4.Escenario 3.1.3: Abstraer la clase PersonVertex de la clase Mail en el modelo.....................85
5.6.2.2.5.Escenario 3.1.4: Abstraer la clase FileVertex y AttachmentEdge de la clase Mail en el modelo.
................................................................................................................................................................ 86
5.6.2.2.6.Escenario 3.1.5: Abstraer la clase TagFinder de la clase Mail en el modelo...........................87
5.6.2.2.7.Escenario 3.1.6: Refactorizar God Class en GraphPersistenceHelper...................................88
5.6.2.2.8.Escenario 3.2: Abstraer la clase Mail en la interfaz gráfica de la herramienta........................88
5.6.2.2.9.Escenario 3.3: Refactorizar sección de código de “importación de datos”..............................89
5.6.2.3.Agregar al backlog....................................................................................................90
5.6.3.Etapa 3: Priorización de backlog..............................................................................90
5.6.3.1.Nivel de Impacto.......................................................................................................90
5.6.3.2.Nivel de Esfuerzo......................................................................................................91
5.6.3.3.Ubicación en cuadrantes..........................................................................................91
5.6.4.Etapa 4: Implementación de escenarios..................................................................92
5.6.4.1.Escenario 3.1.1: Aplicar patrón Strategy a la clase GraphBuilder............................92
5.6.4.1.1.Verificar cobertura de test........................................................................................................92
5.6.4.1.2.Implementar Escenario............................................................................................................92
5.6.4.1.3.Ejecutar casos de test..............................................................................................................94
5.6.4.2.Escenario 3.1.2: Abstraer la clase CommunicationEdge de la clase Mail en el
modelo..................................................................................................................................94
5.6.4.2.1.Verificar cobertura de test........................................................................................................94
5.6.4.2.2.Implementar Escenario............................................................................................................94
5.6.4.2.3.Ejecutar casos de test..............................................................................................................96
5.6.4.3.Escenarios 3.1.3, 3.1.4 y 3.1.5.................................................................................96
5.6.4.4.Escenario 3.1.6: Refactorizar God Class en GraphPersistenceHelper (GPH).........96
5.6.4.4.1.Verificar cobertura de test........................................................................................................96
5.6.4.4.2.Implementar Escenario............................................................................................................96
5.6.4.4.3.Ejecutar casos de test..............................................................................................................97

5
5.6.4.5.Escenario 3.2: Abstraer la clase Mail en la interfaz gráfica de la herramienta.........98
5.6.4.5.1.Verificar cobertura de test........................................................................................................98
5.6.4.5.2.Implementar Escenario............................................................................................................98
5.6.4.5.3.Ejecutar casos de test..............................................................................................................99
5.6.4.6.Escenario 3.3: Refactorizar sección de código de “importación de datos”..............99
5.6.4.6.1.Verificar cobertura de test........................................................................................................99
5.6.4.6.2.Implementar Escenario............................................................................................................99
5.6.4.6.3.Ejecutar casos de test............................................................................................................103
5.6.5.Etapa 5: Medición de resultados............................................................................103
5.6.5.1.Análisis basado en métricas y documentación.......................................................103
5.6.5.2.Análisis subjetivo y valoración del grupo de desarrollo..........................................104
5.6.5.3.Validación de escenarios........................................................................................105
5.6.6.Etapa 6: Evaluación...............................................................................................105
6.Conclusiones.................................................................................................106
6.1.Evaluación de la solución...........................................................................................106
6.2.Aportes y beneficios...................................................................................................107
6.3.Limitaciones................................................................................................................ 108
6.4.Trabajos futuros.......................................................................................................... 109
7.Referencias....................................................................................................110

6
1. Introducción
Una característica intrínseca de los sistemas de software es su necesidad de evolucionar. A
medida que el software se ha mejorado, modificado y adaptado a las nuevas exigencias, el
código se vuelve más complejo y se aleja de su diseño original, lo que reduce la calidad del
software [Mens2004]. El objetivo de este trabajo es brindar un enfoque de refactorización que
garantice una mejora en la modificabilidad del software.

1.1. Modificabilidad como objetivo de calidad


Los sistemas de software son construcciones complejas de ingeniería [Lanza2007]. Un
sistema de software moderno es desarrollado por muchas personas al mismo tiempo, y esto
puede dar lugar a problemas de comunicación y problemas de complejidad. Los proyectos de
desarrollo de software evolucionan en el tiempo y necesitan mantenimiento constante. Un
cambio en una parte del sistema puede afectar otras partes del mismo. Por eso, establecer el
diseño adecuado de un sistema es la clave para que sea más entendible y soporte fácilmente
cambios en el futuro. Para mantener un equilibrio en el sistema es necesario una mejora
continua de la calidad del diseño de dicho sistema.

Los atributos de calidad son características que permiten verificar y medir el grado de
satisfacción de los usuarios y/o diseñadores con respecto al sistema de software [ISO9126].
Consideraciones de la lógica de negocio determinan qué cualidades deben ser tenidas en
cuenta en la arquitectura del sistema. Estas cualidades están por encima de la funcionalidad,
que es la declaración básica de las capacidades, los servicios y el comportamiento del sistema
[Bass2003].

El desarrollo y mantenimiento de un sistema de software, sobre todo el caso de proyectos


grandes, requieren de un proceso ordenado, guiado por atributos de calidad, definidos y
priorizados de manera que garanticen tanto la satisfacción de los stakeholders como la
sustentabilidad del proyecto. Si el proceso de desarrollo es guiado solamente por
requerimientos funcionales, a fin de cumplir con los features solicitados por el cliente, no
siempre se garantiza un nivel de calidad que permita la escalabilidad del proyecto en términos
de performance, trazabilidad, seguridad, modificabilidad, etc. Esta problemática es común en
muchos sistemas de software.

Con la evolución del sistema y la introducción de nuevos features, se suelen tomar


decisiones de diseño que no van en concordancia con la idea original. Asi, se incurre en un
desvío en la arquitectura que puede traducirse en un deterioro de la modificabilidad del
sistema.

La modificabilidad tiene que ver con el costo del cambio [Bass2003]. Cada vez que se desea
introducir un cambio en el sistema se debe tener en cuenta el impacto del mismo y el esfuerzo
que se requiere para realizarlo exitosamente. Estos cambios en el sistema deben poder ser
testeados y desplegados sin afectar el comportamiento observable del sistema. Para
representar posibles cambios en la arquitectura del sistema, se utilizan escenarios de

7
modificabilidad. De esta manera, un arquitecto puede especificar en un lenguaje común a todos
los desarrolladores, las tareas a realizar y los resultados a obtener.

Los resultados de la implementación de estos escenarios deben ser validados, y


preferentemente, la mejora de calidad del sistema debe poder medirse. Durante el ciclo de
desarrollo del sistema es importante contar con métricas que brinden una noción de la
evolución de la calidad a través del tiempo. Las métricas miden elementos estructurales y,
como tales, pueden revelar problemas ocultos. En este trabajo se utilizó una serie de code
smells que permiten detectar, a nivel de implementación y diseño, síntomas de baja
modificabilidad. Sin embargo, siempre habrá una brecha entre los síntomas detectados y la
evaluación profunda que un experto en diseño puede hacer en base a esos síntomas
[Fowler1999]. Por lo tanto, es importante combinar y complementar estos indicadores con el
análisis subjetivo y cualitativo que aportan los desarrolladores del proyecto y responsables del
refactoring.

1.2. Proceso de refactoring


Refactoring es el proceso de cambiar un sistema de software de manera tal que se no altere
su comportamiento externo, pero se mejore su estructura interna [Fowler1999]. Bajo esta
definición, cualquier cambio, por pequeño sea, si representa una mejora y no afecta la
funcionalidad del sistema, podría considerarse una “refactorización”.

El objetivo en este trabajo es ir más allá de esas pequeñas mejoras que a diario realizan los
programadores para pensar en la refactorización como un proceso enmarcado en el desarrollo
de software que permita mejorar la calidad del sistema a partir de un incremento de su
modificabilidad. Esto significa que las refactorizaciones son realizadas con un enfoque
ordenado, guiado por objetivos concretos, que lleva a modificaciones definidas y validadas, con
el testeo de las áreas de código afectadas y el análisis de los resultados.

En este contexto, se utilizó un proceso de refactorización en el cual se pueden identificar un


conjunto de etapas iterables de manera incremental, que conforman un proceso guiado por
escenarios de modificabilidad y code smells. Las etapas se pueden clasificar de la siguiente
manera:

• Etapas de Análisis: al comienzo del proceso es necesario el análisis del código del
sistema para detectar errores y síntomas de problemas asociados al desarrollo y las
malas prácticas de diseño.

• Etapas de Implementación: con el diagnóstico del sistema, se pasa a la proposición y


realización de modificaciones con el objetivo de solucionar esos problemas detectados.

• Etapas de Validación: finalmente se realiza una evaluación de los cambios


desarrollados y su impacto en la calidad del sistema. Una vez realizada la evaluación se
decide si conviene llevar adelante una nueva iteración sobre el proceso para refinar la
refactorización.

8
En la Figura 1.1 se puede ver un resumen del proceso definido en este trabajo.

Figura 1.1. Proceso de refactorización resumido.

1.3. Caso de estudio


Este proceso se validó sobre un caso de estudio: el sistema SocialGraph. La instanciación
del proceso en esta sistema de software resultó en una mejora de calidad significativa.
SocialGraph es una herramienta que permite estudiar el comportamiento de grupos de trabajo
tomando como fuente de información los correos electrónicos y aplicando técnicas de Análisis
de Redes Sociales (SNA por sus siglas en inglés). Se puede decir que SocialGraph ha
evolucionado con distintas versiones a fin de agregar nuevos features. Dicha evolución ha sido
"no planeada" conduciendo a un deterioro de la calidad del software. La utilidad de esta
herramienta ha llevado a los stakeholders a requerir nuevas funcionalidades, cuyo desarrollo se

9
ha visto estancado por la erosión del diseño original y la falta de documentación durante el
proceso de desarrollo.

Estos nuevos requerimientos ponen en evidencia la falta de planificación del sistema, su


deuda técnica y un bajo nivel de modificabilidad. Es por esto que utilizó SocialGraph como caso
de estudio y se instanció el proceso de refactorización en el desarrollo de esta herramienta. Así,
se logró una mejora cualitativa en términos de modificabilidad, que permite desarrollar la
funcionalidad requerida y mejorar considerablemente la arquitectura del sistema.

1.4. Esquema general


Este trabajo final está organizado de la siguiente manera:

En el capítulo 2 se introducen los conceptos tomados como referencia para este trabajo. Se
sientan las bases de cómo evolucionan los sistemas de software, cuáles son las implicancias
de realizar un refactoring, el análisis a través de code smells y la especificación de los
escenarios de calidad.

En el capítulo 3 se describen los trabajos y experiencias relacionados con aspectos


generales del refactoring de software, diferentes enfoques de refactoring, refactorings guiados
por code smells. Estos enfoques son estudiados y evaluados con el fin de contextualizar el
presente trabajo.

En el capítulo 4 se describe un proceso de refactoring centrado en la mejora de calidad


guiado por escenarios de modificabilidad y code smells. Se detallan las etapas propuestas en el
proceso y las actividades correspondientes a cada una de ellas.

En el capítulo 5 se pone a prueba el proceso detallado en el capítulo anterior, tomando como


caso de estudio la aplicación SocialGraph. A partir del estado actual del caso de estudio y sus
objetivos de desarrollo, se instancia el proceso de refactorización con sus etapas y actividades.

Finalmente, el capítulo 6 resume las conclusiones describiendo la evaluación de la solución,


los aportes y beneficios, las limitaciones y los posibles trabajos futuros.

10
2. Marco teórico
En este capítulo se introducen los conceptos básicos de la evolución de sistemas de
software y los problemas que esto trae con el paso del tiempo y la introducción de nuevos
features. Durante el desarrollo del sistema se suelen realizar cambios en la arquitectura que no
van en concordancia con su idea original. De esta manera, se cae en un deterioro de la calidad
a partir de la pérdida de modificabilidad, mantenibilidad, flexibilidad y testeabilidad, entre otros
atributos. La implementación de refactorings aparece como una herramienta de desarrollo que
puede ayudar a mejorar la calidad sin alterar el comportamiento observable del sistema. A partir
de la experiencia de expertos en la materia, se introducen las principales características de los
refactorings, sus ventajas y desventajas, el impacto en el código y el diseño del sistema y la
adopción del refactoring como una práctica regular en el proceso de desarrollo.

El principal objetivo del refactoring es mejorar la calidad del sistema, en particular, su


modificabilidad. Por lo tanto, se introducen los conceptos de atributos de calidad, calidad de la
arquitectura, deuda técnica, tácticas de modificabilidad y escenarios de modificabilidad, que
son utilizados en el proceso de refactorización definido en este trabajo.

Para refactorizar, es importante apoyarse en métricas que permitan evaluar la calidad del
diseño. En este trabajo las principales métricas utilizadas son los code smells: un indicador de
síntomas de malas prácticas a nivel de código y diseño. Se presenta una clasificación y las
características de los principales code smells.

La implementación de los cambios a realizar en el refactoring no debe afectar a la


funcionalidad del sistema. Esto trae aparejado ciertos riesgos que deben mitigarse a través del
testeo sistemático del sistema. Al final de este capítulo se presentan algunos lineamientos para
realizar el testeo y la validación de que el refactoring se realizó sin modificaciones en la
funcionalidad observable del sistema.

Todos estos conceptos son utilizados durante el desarrollo de este trabajo para definir y
validar un proceso de refactoring guiado por escenarios de calidad y code smells.

2.1. Evolución de los sistemas de software


A medida que el software se va mejorando, modificando y adaptando a las nuevas
exigencias, el código se vuelve más complejo y se aleja de su diseño original, lo que reduce la
calidad del software [Mens2004].

Los proyectos de desarrollo de software evolucionan en el tiempo y necesitan mantenimiento


constante. Gran parte del costo del ciclo de desarrollo es dedicado a realizar mejoras,
modificaciones y adaptaciones sobre el sistema. Por eso, encontrar el diseño adecuado de un
sistema es la clave para que sea más entendible y soporte fácilmente cambios en el futuro
[Fowler1999].

11
Los requerimientos evolucionan y es por eso que el sistema tiene que hacer lo propio para
no quedar obsoleto [Lehman2001]. Lehman en tres de sus leyes hace referencia a esta
problemática relacionada con el mantenimiento del software:

● Decremento de la calidad: la calidad de los sistemas software comenzará a disminuir


a menos que dichos sistemas se adapten a los cambios de su entorno de
funcionamiento.
● Cambio continuo: un sistema que se utiliza en un ambiente del mundo real debe
cambiar o progresivamente será menos útil en ese ambiente.
● Complejidad creciente: a medida que un sistema evoluciona, su estructura se vuelve
más compleja y se necesita de recursos adicionales para preservar y simplificar su
estructura.

Para que los sistemas de software evolucionen en el tiempo sin que su calidad sufra un
deterioro se deben realizar cambios progresivos y continuos para que el mismo se adapte a los
nuevos requerimientos y al paso del tiempo. Si las sucesivas modificaciones no son efectuadas
correctamente, el código se vuelve difícil de mantener, se pueden introducir nuevos errores y se
vuelve más difícil adaptar el sistema a los nuevos requerimientos.

Dados los tiempos de desarrollo actuales es importante tener un análisis de cuáles son las
áreas del sistema que deben mejorar su calidad y cuales están estables o no necesitan
cambios según los objetivos a futuro del sistema. Esto permitirá priorizar los cambios a realizar
y lograr una planificación más certera de los tiempos que demandaran estos cambios.

2.2. Refactoring
Refactoring es el proceso de cambio de un sistema de software de manera tal que no altera
el comportamiento externo del código pero mejora su estructura interna. Es una manera
disciplinada de limpiar código que minimiza las posibilidades de introducir errores. En esencia
cuando se refactoriza se está mejorando el diseño del código después de que ha sido escrito
[Fowler1999].

El propósito de la refactorización es hacer el software más fácil de entender y modificar. Sólo


los cambios realizados para hacer el software más fácil de entender son refactorizaciones. Un
software fácil de entender es un software fácil de modificar [Fowler1999]. Un buen contraste es
la optimización del rendimiento. Al igual que la refactorización, la optimización del rendimiento
no suele cambiar el comportamiento de un componente (que no sea su velocidad); sólo altera
la estructura interna. Sin embargo, el propósito es diferente. La optimización del rendimiento a
menudo hace que el código más difícil de entender, pero hay que hacerlo para obtener el
rendimiento que se necesita.

Otro punto a destacar es que la refactorización no cambia el comportamiento observable del


software. El software lleva a cabo la misma función que hacía antes. Un usuario final del
sistema no debiera notar un refactoring en el sistema al utilizarlo.

12
2.2.1. Beneficios de refactorizar
Al refactorizar un sistema no se pueden solucionar todos los problemas del mismo. Sin
embargo, es una herramienta valiosa, si se aplica regularmente. El refactoring es una
herramienta que puede, y debe, ser utilizada para diversos fines. Las motivaciones para
realizar un refactoring pueden ser varias. A continuación se discuten algunas de ellas.

2.2.1.1. Mejora el Diseño de Software


Sin refactorización, el diseño del programa decaerá. Los programadores desarrollan nuevos
requerimientos con la finalidad de alcanzar objetivos a corto plazo o cambios en el sistema
realizados sin una comprensión completa del diseño del sistema. Este pierde su estructura con
el transcurso del tiempo y se hace más difícil ver el diseño mediante la lectura del código.

El refactoring es en parte poner orden en el código. La pérdida de la estructura de un sistema


tiene un efecto acumulativo. Cuanto más difícil es ver el diseño en el código del sistema, más
difícil es mantenerlo. Un código mal diseñado generalmente toma más líneas para que
funcione. Reducir la cantidad de código no hará que el sistema funcione más rápido, sin
embargo, puede hacer una gran diferencia en la modificabilidad del código. Refactorizar
regularmente ayuda a conservar la estructura del sistema.

2.2.1.2. Hace al software fácil de entender


El programador escribe código que indica a la computadora qué hacer, y esta, responde
haciendo exactamente lo que le dice. Hay una brecha entre lo que el programador quiere que
haga y lo que el código realmente hace [Fowler1999].

A la hora de programar, es una mala práctica que no se piense en que otro programador o el
mismo en un futuro tenga que modificar el código. Este es un problema que impacta cuando un
programador necesita una semana para hacer un cambio que habría tomado sólo una hora si el
código fuera más entendible.

El refactoring ayuda a hacer el código más legible. Cuando existe código que funciona, pero
no está muy bien estructurado, un poco de tiempo dedicado a refactorizar puede hacer que sea
más entendible y rápido de modificar.

Se pueden empezar realizando pequeños refactorings corrigiendo detalles. Como el código


va quedando más claro, se pueden ver aspectos del diseño que no se podían ver antes.

2.2.1.3. Ayuda a encontrar errores


Al ayudar a comprender el código también ayuda a localizar errores puntuales. Al refactorizar
código se trabaja profundamente en la comprensión de lo que hace el código, y se puede
volcar ese nuevo entendimiento de nuevo en el código. Tomar como un hábito la refactorización
permite escribir código robusto de forma más eficiente.

13
2.2.2. Momentos para refactorizar
Se recomienda refactorizar todo el tiempo en pequeñas ráfagas. El desarrollador no decide
refactorizar, refactoriza porque quiere hacer algo más, y refactorizando ayuda a preparar el
sistema para el cambio. A continuación se discuten algunas guías de cuándo es recomendable
refactorizar.

2.2.2.1. Al agregar una funcionalidad


El momento más común para refactorizar es cuando se quiere agregar una nueva
funcionalidad a un sistema. El código puede haber sido escrito por el mismo programador al
que le toca implementarla o por otro. Al momento de agregarla, el programador ve que si se
hubiera diseñado el código de otra manera, la implementación sería más fácil. Se debe
refactorizar el diseño para que el sistema se más fácil de modificar en el futuro y por la tanto el
proceso de agregar una nueva funcionalidad sea más rápido.

Figura 2.1. Ejemplo de refactorización.

Supongamos el caso de un sistema en cual se realiza la construcción de un grafo a partir de


una base de datos de correos electrónicos (Mails). Esta funcionalidad se encuentra en la clase
GraphBuilder como se puede ver en la figura 2.1a. A futuro se requiere que el sistema pueda
construir grafos tanto a partir de Mails como de otras fuentes de información. Es momento de
refactorizar, se utiliza el patrón strategy para refactorizar el diseño del sistema, como se puede
ver en la figura 2.1b. Con esta refactorización, el sistema ya está preparado para incorporar de
forma sencilla y rápida la nueva funcionalidad requerida. En la figura 2.2 se aprecia el diseño
luego de la incorporación de una estrategia para construir el grafo a partir de mensajes de
chats (GraphBuildingStrategyChat).

2.2.2.2. Cuando se necesita corregir un error


Cuando se mira el código al tratar de entenderlo, se refactoriza para ayudar a mejorar la
comprensión. Este proceso activo de trabajar con el código ayuda a encontrar errores. Si se
reporta un error en el sistema, es una clara señal de que se necesita una refactorización, ya
que el código no era lo suficientemente claro para que el desarrollador vea que había un error
en el área en la que trabajó.

14
2.2.2.3. Al revisar el código
Las revisiones de código ayudan a difundir el conocimiento a través de un equipo de
desarrollo. Los desarrolladores más experimentados pasan conocimiento a otros con menos
experiencia. También son muy importantes para la escritura de código claro. El código puede
parecer claro para el desarrollador que lo escribió, pero no para el resto del equipo. Las
revisiones también dan la oportunidad para que más personas sugieren ideas útiles.

Esta idea de la revisión de código activo es llevada a su límite con la Programación Extrema
(XP) y su práctica de la programación en parejas [Beck2000]. Con esta técnica todo el
desarrollo importante se hace con dos programadores en una sola máquina. En efecto se trata
de una revisión de código continua e integrada en el proceso de desarrollo, y la refactorización
que tiene lugar se integra también.

2.2.3. Refactoring y diseño


El refactoring tiene un papel especial como complemento del diseño. Lo más probable en
desarrollos donde no se dedica tiempo a pensar en el diseño del sistema antes de implementar,
es recaer en la re-implementación de funcionalidades que puede ser un costo considerable
para el proyecto.

Muchas personas consideran que el diseño debe ser la pieza clave de un proyecto y la
programación sólo algo mecánico. La analogía en la que se cae con esta afirmación es la del
diseño de un plano de ingeniería y la programación vendría a ser el trabajo de construcción.
Pero el software es muy diferente de una construcción de un edificio, ya que es mucho más
maleable.

2.2.3.1. Refactoring como una alternativa de diseño


Un argumento utilizado es que la refactorización puede ser una alternativa al diseño inicial.
En este escenario no se hace ningún diseño en absoluto. Se codifica la primera solución que le
viene al programador a la cabeza, y al conseguir que funcione, refactorizar el código hasta que
quede mejor estructurado. Este enfoque puede funcionar y se le ve mucho en los desarrollos
con metodologías ágiles preferentemente XP. Sin embargo este enfoque no es la forma más
eficiente de trabajar. Incluso los programadores extremos hacen algún diseño primero. Ellos
evalúan diversas ideas con tarjetas CRC o similares hasta que tengan una primera solución
plausible. Sólo después de la generación de un primera solución van a codificar y luego
refactorizar. El punto es que la refactorización cambia el papel del diseño inicial. Si no se tiene
pensado refactorizar, hay mucha presión para conseguir que el diseño inicial sea el correcto. La
idea que acompaña a poner tanto énfasis en diseñar antes de codificar es que cualquier
cambio que se deba realizar, cuanto mas tarde mas caro resultará. De este modo se pone más
tiempo y esfuerzo en el diseño inicial para evitar la necesidad de tales cambios.

2.2.3.2. Cambio de énfasis


Realizando una refactorización cambia el énfasis. Todavía se hace el diseño inicial del
sistema, pero no se intenta encontrar la mejor solución posible. En su lugar lo que se busca es

15
una solución razonable. A medida que se construye la solución, más se entiende sobre el
problema, puede ser que la mejor solución sea distinta a la que originalmente se pensó. Con
refactorización esto no es un problema, porque ya no es caro hacer los cambios.

2.2.3.3. Flexible
Las soluciones flexibles son complejas. El software resultante es más difícil de mantener, en
general, a pesar de que es más fácil de cambiar la dirección que se tenía en un principio. En
caso de que se quiera adaptar el sistema, se debe entender cómo cambiar el diseño. La
construcción de flexibilidad en todo el sistema lo hace mucho más complejo y costoso de
mantener. El tema en este aspecto es un trade-off entre el costo de mantener un sistema con
una gran flexibilidad y el beneficio si estamos preparados para adaptarnos fácilmente a los
cambios, lo difícil es saber cual es la medida justa.

Con la refactorización se tratan los riesgos del cambio de forma diferente. Todavía se piensa
en los posibles cambios, todavía se consideran soluciones flexibles. Pero en lugar de aplicar
estas soluciones flexibles, se deben realizar las siguientes preguntas, "¿Qué tan difícil va a ser
refactorizar una solución sencilla a una solución flexible?" Si sucede como la mayoría de las
veces, la respuesta es "bastante fácil", entonces se debe implementar la solución simple.

La refactorización puede conducir a diseños más simples sin sacrificar la flexibilidad. Esto
hace que el proceso de diseño más fácil y menos estresante. Se apunta a construir la
funcionalidad más simple con la que se es posible trabajar y refactorizar de ser necesario. En
cuanto al diseño flexible, complejo, la mayoría de las veces no se va a necesitar.

2.3. Calidad en el desarrollo de software

2.3.1. Atributos de calidad


De acuerdo con el modelo ISO 9126 los atributos de calidad son características que permiten
verificar y medir el grado de satisfacción de los usuarios y/o diseñadores con respecto al
sistema de software [ISO9126].

Consideraciones de la lógica de negocio determinan cualidades que deben ser tenidas en


cuenta en la arquitectura de un sistema. Estas cualidades están por encima de la funcionalidad,
que es la declaración básica de las capacidades, los servicios y el comportamiento del sistema.

Aunque la funcionalidad de un sistema y las cualidades o atributos de calidad están


estrechamente relacionados, casi siempre, el proceso de desarrollo está guiado solamente por
la funcionalidad. Sin embargo, esta es una guía sesgada ya que los sistemas no se rediseñan
porque son funcionalmente deficientes sino que los cambios son a menudo funcionalmente
idénticos pero se realizan debido a que son difíciles de modificar, portar o escalar, o son
demasiado lentos, o ha sido comprometida su seguridad [Bass2003].

16
2.3.2. Funcionalidad y arquitectura
Los atributos de funcionalidad y los de calidad son ortogonales. Si no fuera así, la elección
de la función dictaría el nivel de seguridad o el rendimiento o la disponibilidad o la facilidad de
uso [Bass2003].

Claramente, es posible elegir independientemente un nivel deseado de cada uno. Ahora


bien, esto no quiere decir que cualquier nivel de cualquier atributo de calidad se puede lograr
con cualquier feature. El arquitecto toma diferentes opciones que determinarán el nivel relativo
de la calidad, estas darán lugar a una mejora en un atributo de calidad y otras conducirán en
otra dirección.

La importancia de los atributos de calidad debe ser considerada tanto en el diseño como en
la implementación, aunque ningún atributo de calidad es totalmente dependiente de uno ni otro
aspecto del sistema.

La funcionalidad es la capacidad del sistema para hacer el trabajo para el que fue diseñado.
Una tarea requiere que muchos o la mayoría de los elementos del sistema trabajen de manera
coordinada para completar el trabajo, tal como para construir una casa, albañiles, electricistas,
plomeros, pintores y carpinteros todos trabajan juntos cooperativamente. Por lo tanto, si a los
elementos no se le ha asignado las responsabilidades correctas o no han sido dotados con las
cualidades para poder coordinarse con otros (de modo que, por ejemplo, no saben cuándo es
el momento para que comienzan su parte de la tarea), el sistema será incapaz de ofrecer la
funcionalidad requerida.

La funcionalidad puede lograrse mediante el uso de una combinación de estructuras


posibles. De hecho, si la funcionalidad fuera el único requisito, el sistema podría existir como un
único módulo monolítico con ninguna estructura interna en absoluto. En lugar de ello, se
descompone en módulos para que sea comprensible y para apoyar una variedad de otros fines.
De esta manera, la funcionalidad es en gran medida independiente de la estructura.

Obtener resultados satisfactorios en el desarrollo de un sistema es una cuestión de


conseguir que el “big picture” de la arquitectura esté en concordancia con la implementación.
Por ejemplo: la modificabilidad es determinada por cómo la funcionalidad es dividida
(arquitectura) y por las técnicas de codificación dentro de un módulo (no arquitectural). Por lo
tanto, la modificabilidad de un sistema de software es la facilidad con la que se pueden
introducir cambios en el entorno, requisitos o especificación funcional.

Quedan claras dos cosas sobre esta sección:

1) La arquitectura es fundamental para la realización de muchas cualidades de interés


en un sistema, y estas cualidades pueden diseñarse y ser evaluadas a nivel
arquitectónico.

2) La arquitectura, por sí misma, es incapaz de lograr cualidades. Proporciona la base


para el logro de la calidad, pero esta base será en vano si no se presta atención a la
implementación.

17
El atributo de calidad que guiará este proceso de refactoring es la modificabilidad, ya que el
objetivo principal es atacar este atributo. De esta forma, con la mejora de la modificabilidad,
será más fácil y rápido adaptar nuevas funcionalidades en el futuro.

Siguiendo el ejemplo descrito en la figura 2.1, se puede notar que el refactoring impactó de
forma positiva en la modificabilidad del sistema. Es decir, al momento de implementar la
construcción de grafos a partir de otra fuente de información (Chats), no se requiere cambiar el
diseño.

Figura 2.2. Ejemplo de implementación de nueva funcionalidad sobre sistema refactorizado.

2.3.3. Modificabilidad
La modificabilidad tiene que ver con el costo del cambio. Esto trae a colación dos
preocupaciones [Bass2003]:

2.3.3.1. Artefacto a modificar


Un cambio puede ocurrir en cualquier aspecto de un sistema, más comúnmente en la
funcionalidad, el ambiente de ejecución, en la calidad de los artefactos que lo componen, su
capacidad (número de usuarios soportados, número de operaciones simultáneas, etc.), como
también en la interfaz de usuario.

2.3.3.2. Ambiente en que se realiza la modificación


Los desarrolladores hacen cambios, los prueban y generan una nueva versión en el sistema.
La cuestión de cuándo se hace un cambio se entrelaza con la cuestión de quién lo hace. Se
pueden hacer cambios a la aplicación durante la compilación, durante la construcción (por
elección de las bibliotecas), durante la configuración (parámetros) o durante la ejecución (por
ajuste de parámetros). A su vez, un cambio puede ser hecho por un desarrollador, un usuario
final, o un administrador de sistema. En el caso del usuario final en este trabajo no tiene
importancia ya que nos interesan las modificaciones en el código fuente del sistema.

18
Una vez que se ha determinado un cambio, la nueva aplicación debe ser diseñada,
implementada, probada y desplegada. Todas estas acciones toman tiempo y dinero, los cuales
se puede medir.

2.3.4. Escenarios de Modificabilidad


En un escenario de modificabilidad, una petición para una modificación llega (el estímulo) y
los desarrolladores deben aplicar la modificación, sin efectos secundarios, para luego probar y
desplegar la modificación [Bass2003].

Una colección de escenarios concretos se puede utilizar como requerimientos de atributos de


calidad de un sistema. Cada escenario es lo suficientemente concreto para ser significativo
tanto para el arquitecto como el programador. En la elicitación de requerimientos, normalmente
se organiza la discusión de escenarios generales por atributos de calidad; si el mismo
escenario es generado por dos atributos diferentes, uno puede ser eliminado.

En la tabla 2.1 se hace una descripción detallada de cada una de las partes de un escenario
y los posibles valores que pueden adquirir.

Porción Descripción Valores Posibles


del
Escenario

Fuente del Esta parte especifica quién hace los cambios: el desarrollador, Usuario final,
Estímulo administrador de sistemas o un usuario final. Claramente, debe desarrollador,
haber mecanismos para permitir que el administrador del sistema o administrador del
el usuario final puedan modificar un sistema, pero esto es una sistema
ocurrencia común.

Estímulo Esta parte específica los cambios a realizar. Un cambio puede ser la Desea agregar /
adición de una función, la modificación de una función existente, o la eliminar / modificar
eliminación de una función. También se puede hacer para las una funcionalidad,
cualidades del sistema por lo que es más sensible, aumentando su atributo de calidad,
disponibilidad, y así sucesivamente. La capacidad del sistema capacidad.
también puede cambiar. Aumentar el número de usuarios
simultáneos es un requisito frecuente.

Artefacto Define qué se va a cambiar: la funcionalidad de un sistema, su Interfaz de usuario,


plataforma, su interfaz de usuario, su medio ambiente, u otro sistema plataforma, entorno,
con el que interactúa. sistema que
interactúa con el
sistema objetivo.

Ambiente Especifica cuándo se puede realizar el cambio: en tiempo de diseño, Tiempo de ejecución,
en tiempo de compilación, en tiempo de construcción, e tiempo de compilación, build,
inicio, o en tiempo de ejecución. diseño,
implementación.

Respuesta Quien hace el cambio debe entender cómo hacerlo, y luego hacerlo, Localiza lugares de

19
probarlo y desplegarlo. la arquitectura a ser
modificados, realiza
modificaciones sin
afectar otras
funcionalidades,
testea
modificaciones,
despliega
modificaciones.

Medida de Todas las posibles respuestas toman tiempo y cuestan dinero, Costo en términos de
la tiempo y costo son las medidas más deseables. El tiempo no número de
Respuesta siempre es posible de predecir, sin embargo, se pueden pensar otras elementos afectados,
medidas que se utilizan con frecuencia, como la magnitud del cambio esfuerzo, dinero;
(número de módulos afectados). En relación con la modificabilidad, medida en que esto
la respuesta también puede medirse a partir de indicadores afecta a otras
estadísticos del código y el diseño o bien partir del criterio de un funciones o atributos
experto. de calidad.

Tabla 2.1. Descripcion y valores posibles de las partes de un escenario de modificabilidad.

Volviendo al ejemplo del grafo y la refactorización de su construcción, este requerimiento se


podría describir de la siguiente forma: “Se desea que el desarrollador implemente un cambio de
modo tal que se puedan soportar otras fuentes de información en la construcción del grafo. se
hará este cambio en el código en tiempo de diseño, se tardará menos de tres horas para hacer
y probar el cambio, y no ocurrirán efectos secundarios en el comportamiento.”

Figura 2.3. Ejemplo gráfico y resumido de escenario.

En la figura 2.3 se representa gráficamente el escenario del ejemplo y en la tabla 2.2 se


presenta la especificación formal del mismo.

20
Porción del Valores
Escenario

Fuente del Estímulo El cambio debe ser realizado por el desarrollador

Estímulo Poder soportar en un futuro varias fuentes de información

Artefacto Clase GraphBuilder

Ambiente Implementación.

Respuesta Realiza modificaciones sin afectar funcionalidades del sistema.

Medida de la Tiempo de realización: 3 horas/hombre.


Respuesta Mejora de la modificabilidad en el futuro

Tabla 2.2. Ejemplo de especificación de escenario de modificabilidad.

2.3.5. Tácticas de modificabilidad


Las tácticas de modificabilidad tienen como objetivo controlar el tiempo y costo de los
cambios en la implementación, pruebas y puesta en ejecución [Bass2003]. Los distintos tipos
de tácticas están organizadas acorde con sus metas específicas. En la figura 2.4 se aprecia un
esquema resumido de estas tácticas. A continuación se describe su clasificación.

Figura 2.4. Resumen de las tácticas de modificabilidad [Bass2003].

21
2.3.5.1. Localización de modificaciones
El principal objetivo de este conjunto de tácticas de modificabilidad es minimizar el número
de módulos que necesitarán ser modificados en iteraciones posteriores desarrollo. Las
siguientes tácticas de localización de modificaciones son empleadas durante la fase de diseño
e implementación del proceso de desarrollo [Bass2003].

2.3.5.2. Prevención de efecto dominó


Se refiere a la posibilidad de introducir errores a módulos que no son afectados directamente
por el cambio. Esta situación se da si es necesario modificar un componente de software pero
existen otros componentes que dependen de este. Este tipo de tácticas intenta minimizar el
número de módulos que necesitarán ser modificados por consecuencia de modificar el primer
componente [Bass2003].

2.3.5.3. Retraso de tiempo de Binding


Este tipo de tácticas de modificabilidad tienen como objetivo retrasar el momento en que se
realiza el enlace entre componentes, es decir la configuración del sistema es especificada
hasta el momento de su ejecución. Con el uso de esta táctica es posible que los
desarrolladores puedan modificar o sustituir componentes durante el proceso de desarrollo
para la realización de pruebas de los componentes o de módulos del sistema. Algunas de las
tácticas más utilizadas son [Bass2003].

2.3.6. Deuda Técnica


El término “technical debt” (deuda técnica) fue introducido en 1992 por Ward Cunningham.
Es una metáfora que viene a explicar que la falta de calidad en el código fuente del proyecto,
genera una deuda que repercutirá en intereses, tanto en el mantenimiento de un software,
como en la propia operativa funcional de la aplicación.

En esta metáfora, hacer las cosas de la manera rápida y no planeada acarrea una deuda
técnica, que es similar a una deuda financiera. Al igual que una deuda financiera, la deuda
técnica incurre en el pago de intereses, que vienen en la forma de un esfuerzo extra que habrá
que hacer en el futuro desarrollo. Se podrá optar por seguir pagando los intereses, o bien
resolver el problema de fondo, refactorizando el diseño. Aunque es costoso “pagar” (resolver) el
problema de fondo, se gana con pagos reducido de “intereses” en el futuro.

La gestión de la deuda técnica debe reservarse para los casos en los que se ha adoptado
una estrategia de diseño que no es sostenible en el largo plazo, pero se obtiene un beneficio a
corto plazo, tal como lanzar una release. El punto es que los rendimientos de la deuda se vean
pronto, pero la misma tiene que ser pagada tan pronto como sea posible [Fowler2009].

El punto a tener en cuenta a la hora de aplicar este concepto en el desarrollo de software no


es entre deuda o no deuda, sino, entre considerar una deuda prudente o imprudente. No sólo
hay una diferencia entre la deuda prudente e imprudente, también hay una diferencia entre la
deuda deliberada e inadvertida. El ejemplo de la deuda prudente es deliberada porque el
equipo sabe que está asumiendo una deuda, y por lo tanto pone un poco de pensamiento en

22
cuánto la recompensa por una versión anterior es mayor que los costos de pagarla. Un equipo
que ignora las prácticas de diseño está asumiendo su deuda imprudente sin siquiera darse
cuenta de en cuánto “interés” se está incurriendo.

Un problema con el uso de la metáfora de la deuda es que no se puede concebir un paralelo


con la toma de una deuda financiera prudente inadvertida. En el ambiente financiero sería difícil
de explicar a los directivos de un banco por qué esta deuda apareció. El tema es que en un
desarrollo de software esta clase de deuda es inevitable y por lo tanto se debe esperar. Incluso
los mejores equipos tendrán que lidiar con la deuda [Fowler2009].

Pero no siempre podemos adelantarnos y prevenir la deuda, por lo tanto, en estos casos sólo
se puede recurrir a un proceso de refactoring. Si se quiere que un proyecto llegue a buen
puerto, al final siempre se tendrá que pagar la deuda técnica que se ha generado.

2.3.6.1. Retrospectiva de deuda técnica


Se sabe que se debe evitar tanto como sea posible la deuda técnica, sin embargo la mayoría
de los proyectos de software terminan, en mayor o menor medida, incurriendo en ella. Por esto,
es importante saber cuánto se debe y la cantidad de interés que se está pagando. Para relevar
esta situación se puede utilizar la retrospectiva de deuda técnica.

Al tener un conocimiento previo del proyecto, se realiza una tormenta de ideas de todas las
deudas del proyecto en que se piensa que han incurrido los programadores y diseñadores que
trabajaron desde el inicio del proyecto. Luego se categorizan cada una de estas deudas según
esfuerzo e impacto. Vale aclarar que se trabaja sobre la deuda técnica conocida, es decir,
cuanto mayor conocimiento del sistema tengan las personas involucradas en la priorización,
mejor será el resultado de la retrospectiva.

En detalle, se prioriza según dos ejes o conceptos: primero el esfuerzo, que es el trabajo
relativo que el equipo tendría que invertir para pagar esta deuda; y segundo el impacto, que es
el efecto directo en la productividad que esta deuda provoca o la importancia que tiene para el
proyecto, resolver el problema.

Se debe pagar la deuda principal para refactorizar nuestro código lo antes posible de no ser
así, siempre se estará pagando sólo los intereses. El no pagar la deuda técnica repercute en la
reducción de la productividad a medida que se agregan nuevos requerimientos al sistema. Se
puede llegar al punto de que no se puede realizar una nueva funcionalidad requerida, ya que, el
sistema no está preparado para ser modificado fácilmente. La pregunta que se hace en este
momento es: ¿Qué parte de la deuda técnica se debe pagar primero?

Para sistematizar este proceso se realiza un diagrama de cuatro cuadrantes dados por la
intersección de los dos criterios en cuestión que se nombran anteriormente. Esto permite
identificar qué parte se puede pagar para tener el mayor retorno (costo / beneficio) y qué parte
no vale la pena pagar ya que implica un alto esfuerzo y baja repercusión. Por eso, el cuadrante
inferior derecho (Bajo esfuerzo / Alto impacto), es el primero que conviene pagar mientras que
la deuda ubicada en el cuadrante superior izquierdo (Alto esfuerzo / Bajo impacto) no vale la
pena ser pagada.

23
Los cuadrantes restantes requieren un mayor análisis, el cuadrante superior derecho (Alto
esfuerzo / Alto impacto) corresponde con la deuda que es compleja de refactorizar pero por la
cual se está pagando un alto interés. Es decir, en la mayoría de los casos son cambios que
involucran una modificación en la arquitectura del sistema, y por lo tanto afectan varios
componentes del sistema. Llegado el momento esta deuda debe ser pagada sin importar el
esfuerzo que se requiere. En contraposición, el cuadrante inferior izquierdo (Bajo esfuerzo/
Bajo impacto), es la parte de la deuda por la que se paga poco interés ya que no causa
demasiado problema en el diseño y más allá de que el esfuerzo por solucionarlo es
relativamente poco, sólo se realizará en caso de que se tenga tiempo disponible.

De acuerdo al escenario especificado en la tabla 2.2 se genera el cuadrante de retrospectiva


de la figura 2.5. A juicio de los programadores del grupo de desarrollo, se le asignó un lugar en
el diagrama según la valoración del grado de impacto en el sistema y el esfuerzo requerido
para su implementación.

Figura 2.5. Ubicacion del escenario de ejemplo en cuadrante de deuda técnica.

2.4. Métricas y code smells


Independientemente de las convicciones acerca de métricas de software que se puedan
tener, cada vez que se analiza un sistema de software, se quiere obtener una impresión del
tamaño y la complejidad del sistema de software para poder caracterizarlo. Algunas personas,
eligen para expresar el tamaño de un sistema diferentes aspectos como líneas de código, otros
utilizan cantidad de clases, e incluso otros sólo miden la cantidad de código fuente en

24
megabytes. Estos números no son más que los valores de algunos parámetros básicos. Para
los sistemas orientados a objetos las más comunes son las líneas de código, el número de
clases, el número de paquetes o subsistemas, el número de operaciones (métodos), etc.

Pero estas métricas por sí solas de forma aislada no permiten hacer una caracterización
acertada del sistema [Lanza2007]. Es importante entender y tener en claro cómo es la
correlación entre las distintas métricas y desde qué punto de referencia se realiza una
comparación para realizar un análisis cualitativo.

Una aplicación, una clase, un método y cualquier otro artefacto en un sistema de software
deben aplicarse de una manera armoniosa, por ejemplo, un clase tiene que implementar un
número apropiado de métodos con un apropiado tamaño, complejidad y funcionalidad. Esta
armonía global está compuesta por tres armonías distintas que afectan a todos los artefactos
de un programa:

• Armonía de Identidad - "¿Cómo me defino?" Cada entidad en un sistema de software


debe justificar su existencia: Poner en práctica un concepto específico y ¿Cómo lo
hace? ¿Está haciendo demasiadas cosas o nada en absoluto?

• Armonía de Colaboración - "¿Cómo me relaciono con los demás?" Cada entidad


colabora con otros para cumplir con sus tareas. ¿Que hace por su propia cuenta, o
utiliza otras entidades. ¿Cómo las utiliza? ¿Utiliza demasiadas?

• Armonía de Clasificación - "¿Cómo me defino con respeto a mis antepasados y


descendientes? ". Esta armonía combina elementos de armonía de identidad y
colaboración en el contexto de la herencia. Por ejemplo, una subclase usa todos los
servicios heredados, o se ignoran algunos de ellos?

Las métricas miden elementos estructurales y, como tales, pueden revelar síntomas ocultos.
Pero siempre habrá una brecha entre los síntomas y la evaluación profunda que un experto en
diseño orientado a objetos puede hacer usando estos síntomas. Por lo tanto es importante
tener en cuenta a las métricas como una herramienta y como con cualquier herramienta
conocer sus ventajas y desventajas.

Fowler remarca que “no hay un conjunto de métricas que sea rival para la intuición humana”
[Fowler1999]. Pero hay que tener en cuenta una desventaja: “la intuición humana no escala con
las dimensiones de los sistemas de la actualidad” [Lanza2007]. Sin embargo, tanto Fowler
como Lanza, coinciden en la utilización de code smells como indicadores de malas prácticas de
programación y en la identificación de oportunidades de refactoring.

2.4.1. Code Smells


Code smells (“olores del código”) son cualquier síntoma en el código de un programa que
posiblemente indica un problema más profundo. Los “smells” de código usualmente no son
errores, es decir, no son técnicamente incorrectos y en realidad no impiden que el programa
funcione correctamente. En cambio, indican deficiencias en el diseño que puede ralentizar el

25
desarrollo o aumentar el riesgo de errores o fallos en el futuro. Son el resultado de malas
prácticas en la programación: generación de código duplicado, métodos muy largos, métodos
que necesitan demasiados parametros, clases muy grandes, etc.

En base a los aspectos de armonía identificados anteriormente, Lanza y Marinescu clasifican


los tipos de code smells en tres categorías de desarmonías: de colaboración, de identidad y de
clasificación. En la figura 2.6 se muestra la relación entre los code smells y sus categorías y a
continuación se describe cada uno.

Figura. 2.6. Clasificación y correlación entre code smells [Lanza2007].

2.4.1.1. Desarmonías de identidad


Son los defectos de diseño que afectan a entidades individuales como clases y métodos. La
particularidad de estas anomalías es que su efecto negativo en la calidad de los elementos de
diseño se puede notar al considerar estos elementos de diseño en forma aislada.

God Class
En un buen diseño orientado a objetos la inteligencia de un sistema se distribuye
uniformemente entre las clases de más alto nivel. El defecto de diseño God Class se refiere a
las clases que tienden a centralizar la inteligencia del sistema. Una God Class hace demasiado
trabajo por cuenta propia, delega sólo detalles menores a un conjunto de clases triviales

26
utilizando los datos de otras clases. Esto tiene un impacto negativo en la reutilización y la
incomprensibilidad de esa parte del sistema.

La God Class es potencialmente dañina para el diseño de un sistema, ya que es una


agregación de diferentes abstracciones y mal uso de otras clases (Clases portadoras de datos
solamente) para llevar a cabo su funcionalidad. Esta situación va en contra de los principios
básicos del diseño orientado a objetos que es que una clase debe tener una sola
responsabilidad. En este punto es importante mencionar que una God Class es un verdadero
problema si obstaculiza la evolución del sistema. Pueden existir clases que tienen las
características estructurales de una God Class pero que residen en un área estable del sistema
y por lo tanto no plantean un problema.

Feature Envy
Los objetos son un mecanismo para mantener juntos los datos y las operaciones que
procesan esos datos. La anomalía de diseño Feature Envy se refiere a los métodos que
parecen más interesados en los datos de otras clases que en los de su propia clase. Estos
métodos acceden directamente o a través de métodos de acceso a una gran cantidad de datos
de otras clases. Esto podría ser una señal de que el método está fuera de lugar y que debe ser
trasladado a otra clase. Los datos y las operaciones que modifican o utilizan estos datos deben
estar lo más cerca posible. Esta proximidad entre operaciones y datos puede ayudar a
minimizar el efecto dominó, es decir, un cambio en un método provoca cambios en otros
métodos y así sucesivamente.

Brain Method
A menudo, un método que empieza como un método "normal" pero luego se le añade más y
más funcionalidad hasta que se sale de control, llegando a ser difícil de mantener o entender.
Los Brain Methods tienden a centralizar la funcionalidad de una clase, de la misma manera en
que una God Class centraliza la funcionalidad de todo un subsistema, o incluso a veces un
sistema entero.

En el caso de los Brain Method el problema se refiere a procedimientos demasiado largos,


que son difíciles de entender y depurar, y prácticamente imposible de reutilizar. Un método bien
escrito debe tener una complejidad adecuada en concordancia con el propósito del método.

Brain Class
Esta anomalía de diseño se da por clases complejas que tienden a acumular una cantidad
excesiva de inteligencia por lo general afectada por varios Brain Methods. Este smell puede
resultar parecido a God Class ya que los dos tienen la tendencia de centralizar la inteligencia
del sistema. Ambos smells hacen referencia a clases complejas pero los problemas son
distintos.

La caracterización de una God Class no es sólo por su complejidad, también se tiene en


cuenta que rompe con la regla de encapsulamiento y accede directamente a muchos atributos
de otras clases. Por otro lado, las Brain Class no hacen un acceso abusivo a clases satélite.
Las Brain Class no llegan a ser God Class pero poseen al menos un Brain Method. A nivel de
diseño tiene el mismo impacto que las God Class.

27
Data Class
Son clases contenedoras de datos sin funcionalidad compleja, aunque otras clases
dependen desmedidamente de ellas. La falta de métodos funcionalmente relevantes puede
indicar que los datos y el comportamiento asociado no se mantienen en un solo lugar; esto es
una señal de un diseño no orientado a objetos. Las Data Classes son la manifestación de un
mal encapsulamiento de los datos, y de una baja proximidad entre los datos y la funcionalidad.

Un ejemplo de desarmonía de identidad se puede ver ampliando el ejemplo de la figura 2.1.


En el sistema original, se detecta un Brain Method en el método buildMailGraph dada la gran
cantidad de tareas que resuelve este método. Al implementar la refactorización del
correspondiente escenario, el smell no desaparece, sino que se mueve al método build de la
clase GraphBuildingStrategyMail. De todas formas, esa refactorización permitió visualizar el
smell para aplicar una nueva refactorización sobre esa clase. Para eliminar esta desarmonía,
se realiza una extracción de métodos que permite modularizar mejor las responsabilidades de
la clase. Del método build se extraen los métodos createVertex y createEdge como se puede
ver en la figura 2.7b. Se trata de una refactorización simple que elimina el smell y hace el
software más comprensible y modificable.

Figura 2.7. Ejemplo de refactorización de anomalía de identidad.

2.4.1.2. Desarmonías de Colaboración


Son defectos de diseño que afectan al mismo tiempo a varias entidades por la forma en que
colaboran entre ellas para llevar adelante una funcionalidad [Lanza2007]. Se entiende la
colaboración de una entidad (clase) como las dependencias entrantes y salientes con otras
entidades. La regla de la armonía de colaboración establece que: “Las colaboraciones deben
ser sólo en términos de invocaciones de métodos y tener una extensión, intensidad y dispersión
limitada”. Este tipo de síntomas guarda relación directa con el grado de acoplamiento entre
varias entidades.

Intensive Coupling
Es uno de los casos frecuentes de acoplamiento excesivo en que un método está atado a
muchas operaciones de apenas una clase o un par de clases que guardan poca relación entre
ellas. Se trata de los casos en que simultáneamente se da que la función invoca muchos
métodos y a su vez esos métodos no están demasiado dispersos en muchas clases. Se
entiende que las clases “proveedoras” de métodos guardan poca dispersión o relación entre
ellas cuando pertenecen a la misma jerarquía de clase que la clase que contiene al método
invocante.

28
Dispersive Coupling
El acoplamiento disperso se da cuando una operación está excesivamente ligada a muchas
operaciones en el sistema y, adicionalmente, estos métodos proveedores están dispersos en
muchas clases. Se da una colaboración poco intensa con muchas clases poco relacionadas
entre sí en el sistema.

Este tipo de acoplamiento en las operaciones conduce a efectos dominó no deseados, dado
que un cambio en un método dispersamente acoplado potencialmente conlleva a realizar
cambios en todos los acoplamientos y por lo tanto en todas las clases de las que depende.

Shotgun Surgery
Esta disarmonía de colaboración tiene que ver con las dependencias “entrantes” de un
método, es decir, cuando un método es invocado por muchos otros que a su vez están
dispersos en el sistema. El principal problema que evidencia este smell es que si un cambio
ocurre en el método, es probable que haya que modificar o al menos revisar todos los métodos
que lo invocan desde distintos contextos del sistema. Este síntoma guarda tanta relación con la
intensidad de la colaboración entre entidades como con su dispersión.

2.4.1.3. Desarmonías de Clasificación


No es suficiente para una clase estar en armonía consigo misma; también tiene que estar en
armonía con su su antecesor y sus clases descendientes. La causa principal de la falta de
armonía de clasificación es la idea errónea de que la herencia es principalmente un vehículo
para la reutilización de código en lugar de un medio para asegurar que los objetos más
específicos pueden sustituir a los más generales.

Refused Parent Bequest


Este smell se presenta cuando a la hora de ampliar o añadir una subclase no se estudia y
determina qué código se puede reutilizar, lo que hay que sumar y finalmente lo que podría ser
empujado a las superclases para aumentar su generalidad.

2.5. Testing
A la hora de refactorizar es una precondición esencial tener casos de test sólidos. Más allá
que se tenga una herramienta de automatización de refactorings, igual se necesitan tests
[Fowler1999]. Escribir buenos casos de test agiliza la programación en general se esté o no
haciendo refactoring.

Si analizamos en qué gastan su tiempo los programadores, veremos que escribir código es
una pequeña fracción. Un tiempo se pasa descifrando qué es los que se tiene que hacer, otro
tiempo diseñando la solución, pero más tiempo se pasa depurando. Arreglar un bug es una
tarea sencilla, encontrar el bug es la tarea difícil. Todos los programadores han pasado largas
horas para encontrar un error alguna vez. Y existe la posibilidad de, al corregir el error, generar
otro que se descubrirá más tarde y se tenga que pasar de vuelta por el mismo problema.

De esta forma, se ve la necesidad de que cada clase pueda ser testeada cada vez que un
cambio en el sistema es realizado. El programador debe asegúrese de que todos los test son

29
totalmente automáticos, es decir, no debe ver la salida de cada uno de los test y verificar que
esta salida se encuentre en entre los resultados esperados. Los test deben verificar su
resultado e indicar si funciona bien o hay alguna falla en la clase. Se les deben pasar los
parámetros de entrada y chequear su salida para que arrojen los datos esperados.

Ejecutar los test cada vez que se compila el proyecto reflejará una mejora de la
productividad, ya que, en caso de que falle un test ya se sabe que el error está en el trabajo
que se ha hecho desde la última vez que se corrieron los tests. Debido a que el código está
fresco en la mente del programador y es una pequeña porción, el error es más fácil de
encontrar. Errores que podían tardar una hora o más para encontrarlos ahora toman un par de
minutos a lo sumo. Teniendo en cuenta la frecuencia en que se ejecutan, un conjunto de test es
un potente detector de errores que minimiza el tiempo que se tarda en encontrar un error.

Uno de los momentos más útiles para escribir casos de test es antes de empezar la
programación. Cuando se tenga que añadir una característica al sistema se puede comenzar
escribiendo el test. Al escribir antes el test se está pensando en qué resultado se espera
independientemente de cómo se implementa la nueva función. Escribir el test se concentra en
la interfaz en lugar de la aplicación. También significa que el programador tiene un punto claro
en el que termina de codificar: cuando el test funciona [Beck2000].

En el ejemplo de refactoring de la figura 2.1, un posible caso de test podría ser que dada una
misma fuente de información de correos electrónicos, ejecutar el método buildMailGraph de la
clase GraphBuilder y medir que la cantidad de arcos, vértices y las propiedades asociadas a los
mismos sean los esperados. Luego de realizado el refactoring, se vuelve a ejecutar el test para
comprobar que los resultados son los esperados. Este test validará que el cambio se ha
realizado exitosamente sin afectar el funcionamiento observable del sistema.

En síntesis, refactorizar una aplicación requiere testeo, si desea refactorizar se debe escribir
los tests. Aunque no se pueda hacer una cobertura del 100% del sistema, una pequeña
cantidad puede tener grandes beneficios. Para hacer más fácil este trabajo se pueden utilizar
frameworks especializados en automatización de testing, según la tecnología que se esté
utilizando para el sistema.

2.6. Resumen
En este capítulo se han introducido los conceptos que dan sustento a este trabajo. Teniendo
en cuenta cómo es la evolución de los sistema de software y los problemas que la adición de
nuevas funcionalidades pueden causar en la calidad con el paso del tiempo. Se encuentra en el
refactoring una herramienta útil para mantener y mejorar la modificabilidad del sistema,
preparándolo para que la adición de nuevas funcionalidades se haga de forma más rápida y
sencilla.

Estos refactorings, que son necesarios en el sistema, deben poder ser documentados y
especificados en un lenguaje común que puedan entender tanto el arquitecto que los especifica
como los programadores que los implementan. Los escenarios de modificabilidad dan soporte
para esta tarea. Teniendo en cuenta los cambios que el sistema requiere, es importante hacer
una valoración de la deuda técnica para poder planificar y priorizar los refactorings a realizar.

30
Para realizar los diagnósticos de calidad del sistema, para medir su evolución y el impacto de
las tareas de refactorización, es conveniente combinar las métricas con el criterio de los
desarrolladores. Es decir, complementar el análisis objetivo que puede realizarse con los
indicadores cuantitativos, con el análisis subjetivo que sólo pueden aportar los expertos. La
detección de automática de code smells resulta útil para detectar anomalías de diseño e
implementación. Estos indicadores, dan soporte a los desarrolladores para identificar
oportunidades de refactoring y evaluar la calidad del sistema.

31
3. Trabajos Relacionados
En esta sección se describe el estado del arte relacionado a la refactorización de sistemas
en la actualidad. Con el objetivo de presentar los distintos enfoques analizados, los mismos se
explicarán en función de los aportes que realizan en las temáticas que más interesan al
desarrollo de este trabajo final.

Los trabajos presentados pretenden mejorar la calidad de los sistemas principalmente


mediante la aplicación de soluciones para favorecer la modificabilidad de los artefactos,
intentando aumentar en consecuencia la legibilidad y facilidad de incorporar nuevas
funcionalidades en los mismos.

Algunos de estos trabajos brindan indicios de problemas que resultan útiles para guiar y
enfocar el proceso de refactoring sobre los sistemas. Algunos se basan exclusivamente en la
identificación de oportunidades de refactorización y otros evalúan las mejoras de calidad
obtenidas a partir de la implementación de distintos tipos de refactoring.

Este capítulo se organiza en cuatro secciones que permiten revisar distintas experiencias en
materia de refactorización de sistemas de software. Las mismas son: aspectos generales del
refactoring de software, enfoques de refactoring, impacto de los refactorings en la calidad del
software y refactoring guiados por code smells.

3.1. Aspectos generales del refactoring de software


El campo de la refactorización de sistemas ha evolucionado mucho en los últimos años. En
esta sección se tendrá una visión general de la investigación existente en materia de
refactorización de software en la actualidad.

3.1.1. Formalización de los procesos de refactoring


Existen relevamientos de refactoring basados en diferentes criterios como las actividades de
refactorización soportadas, técnicas específicas usadas para soportar esas actividades, tipos
de artefactos que se pueden refactorizar, asuntos importantes a tener en cuenta al desarrollar
herramientas de soporte a la refactorización y el efecto del refactoring en el proceso del
software [Mens2004]. En general, se ha identificado una necesidad de formalismos, procesos,
métodos y herramientas que se ocupan de la refactorización de una manera más consistente,
genérica, escalable y flexible. Por eso, el objetivo de este trabajo es definir un proceso de
refactorización.

3.1.2. Desafíos y beneficios de refactorizar


Se ha realizado también un estudio de campo de los beneficios de la refactorización y retos a
través de tres métodos de estudio complementarios: una encuesta, entrevistas semi-
estructuradas con los ingenieros de software profesionales y el análisis cuantitativo de los datos

32
del historial de versiones, revelando que la definición de refactorización en la práctica no se
limita a una definición rigurosa de las transformaciones de código y que los desarrolladores
perciben que la refactorización implica costos y riesgos considerables [Kim2012]. Las figuras
3.1 y 3.2 muestran los resultados de las encuestas a los desarrolladores respecto a los riesgos
y los beneficios de refactorizar respectivamente.

Figura 3.1. Factores de riesgo asociados a refactorizar [Kim2012].

Figura 3.2. Varios tipos de beneficios de refactorizar experimentados por los desarrolladores [Kim2012].

Mediante el análisis cuantitativo se pudo determinar que los módulos refactorizados


experimentaron una reducción significativa en el número de dependencias entre módulos y
defectos posteriores al lanzamiento del sistema, lo que indica un beneficio visible durante la
refactorización. Se muestra la experiencia de refactorizar un sistema a gran escala con ciertos

33
resultados positivos, aunque para esto no se utilizaron herramientas de análisis automáticas ni
de apoyo para refactorizaciones semi-automáticas.

3.1.3. Dos tácticas de refactoring


Para hacer el refactoring de software más eficiente, es bueno saber cómo son realizados los
refactorings en el mundo real. En general, existen dos grandes tácticas para realizar
refactorings. La primera es la utilizada en la metodología XP, realizando pequeños pasos,
llamada floss refactoring. Esta táctica es bien conocida, sobretodo entre los defensores de XP.
La otra táctica, llamada root canal refactoring, se trata de dedicar un periodo de tiempo de
desarrollo exclusivamente a la refactorización del sistema. A partir del análisis estadístico de
una gran cantidad de proyectos de software, se pudo determinar que aproximadamente un
11,5% de los refactorings son de tipo root canal, mientras que el restante 88.5% es de tipo floss
refactoring [Liu2012]. Este estudio, muestra las distintas tácticas, sus características y las
preferencias de los desarrolladores. Muestra también la falta de conocimiento sobre las tácticas
de tipo “root canal” que, en general, implican un análisis más profundo de las debilidades del
software para aplicar mejoras significativas de calidad.

Las herramientas de refactorización, si se utilizan, pueden mejorar la velocidad y la precisión


con la que creamos y mantenemos software. En la práctica, las herramientas no se utilizan
tanto como podría ser ya que a veces no se alinean con el floss refactoring, la táctica de
refactorización preferida por la mayoría de los programadores. Algunas herramientas se han
diseñado de una manera que las hace más apropiadas para la refactorización “root canal” que
para refactorización “floss”, es por esta razón que las herramientas de refactorización no se
utilizan tanto como se podría esperar.

3.1.4. Herramientas para cada táctica de refactoring


Con el fin de aprovechar mejor las herramientas de soporte, se han propuesto una serie de
principios que caracterizan a exitosas herramientas de floss refactoring, que pueden ayudar a
los programadores a elegir herramientas de refactorización más adecuadas y también ayudan a
diseñar herramientas que se ajusten mejor al propósito de los programadores [MurphyHill2008].
Si bien se aprecia una interesante perspectiva de la actualidad de la refactorización, sólo se
focaliza en el tipo de herramientas a utilizar dependiendo de qué táctica se aplique. Sería
bueno también evaluar las ventajas y desventajas de cada táctica y promover las mejores
prácticas de desarrollo.

3.1.5. Refactoring guiado por métricas


Los refactorings de sistemas de software pueden ser evaluados a partir de métricas.
Usualmente se utilizan dos tipos principales de métricas: internas, directamente observables
como las líneas de código, la cohesión o el acoplamiento; o externas, atributos de calidad
generalmente más difíciles de medir como la mantenibilidad, la reusabilidad y modificabilidad.
Se hace evidente, después de mirar algunos estudios de investigación, que hay ventajas y
desventajas claras que deben ser considerados al realizar la refactorización [Frier2015].

34
Ciertas acciones de refactorización como Extract Class tienden a crear más clases, lo que
aumenta la profundidad de la herencia del árbol, lo que aumenta la complejidad general. Otras
acciones de refactorización, tales como Extract Method, pueden ser utilizados para reducir
líneas totales de código y reducir la duplicación en el código, lo que disminuye el tamaño de la
aplicación y aumenta la mantenibilidad, con lo que aparentemente aumentar la calidad global
de software para la aplicación. Sabiendo esto, faltaría aún investigar en profundidad la relación
entre ambos tipos de métricas, estudiar qué técnicas, enfoques y herramientas de
refactorización se aplican mejor para optimizar un tipo u otro de métricas.

3.2. Enfoques de refactoring


En el presente trabajo final, se define un proceso de refactorización de sistemas de software
guiado por code smells y escenarios de calidad. A continuación se presentan y evalúan
enfoques de refactoring guiados por distintas características.

3.2.1. Identificación de oportunidades de refactoring


Algunos expertos, afirman que se pueden identificar tres pasos para el proceso de
refactorización [Tourwé2003].

● Detectar cuándo una aplicación debería ser refactorizada.

● Identificar qué refactoring o refactorings deberían ser aplicados y dónde.

● Implementar los refactorings. Etapa que en general se divide en 2 fases: verificar las
pre condiciones apropiadas para que el refactoring no afecte la funcionalidad del
sistema y efectivamente aplicar las refactorizaciones.

En general, los entornos de desarrollo modernos proveen soporte para la segunda fase del
paso 3, pero no para el resto. El soporte automatizado puede facilitar la identificación de
oportunidades de refactorización y más específicamente sugerir qué tipo de refactoring se debe
implementar. Si bien existen herramientas para la detección de malos síntomas en un sistema,
aún es necesario el análisis del desarrollador para discriminar y ponderar los problemas
detectados, que pueden ser una gran cantidad, distribuidos en todo el sistema.

En este trabajo final se tomó en cuenta esta serie de pasos para definir un proceso de
refactorización basado en el análisis objetivo de las métricas y en el análisis subjetivo realizado
por los desarrolladores.

3.2.2. Refactoring basado en herramientas de visualización


Una forma de guiar el proceso de refactorización, es a través de métricas. Las métricas
pueden ayudar a identificar anomalías particulares que sugieran aplicar refactorizaciones en
determinadas áreas del sistema. Si bien se coincide que el desarrollador debe tener la última
palabra sobre dónde y cuándo aplicar una refactorización, es posible asistir la intuición humana
con herramientas de soporte. Este enfoque se vale de herramientas de visualización basado en

35
estructuras estáticas de análisis que permiten al desarrollador apreciar de manera gráfica por
ejemplo el grado de acoplamiento o cohesión en un determinado conjunto de artefactos
[Simon2001]. En la figura 3.3 se puede apreciar un ejemplo del uso de una herramienta de
visualización que sugiere una refactorización. Si bien este tipo de herramientas puede ser útil
para la detección de oportunidades de refactoring, es necesario incluirlas en un proceso paso a
paso que realice un análisis del sistema, no sólo a nivel de código, sino de su arquitectura.

Figura 3.2. Herramienta VRML-World. Los métodos se representan con esferas y los atributos con cubos. Cada
clase tiene asignado un color. La herramienta sugiere una refactorización: un método de la clase “verde” debe
movido a la clase “azul” [Simon2001].

3.2.3. Refactoring orientado por features


Para guiar la refactorización, se pueden utilizar también los features del sistema. La
refactorización orientada por features (FOR por sus siglas en inglés) es el proceso de
descomponer un programa en características, donde cada característica es un incremento en la
funcionalidad del programa. Se ha desarrollado una teoría que explica las relaciones entre las
características y los módulos de la aplicación, y por qué las características de los diferentes
programas de una línea de productos puede tener diferentes implementaciones [Liu2006]. Se
describe una metodología y herramienta de refactorización basado en esta teoría, y se
presenta un caso de estudio para su validación. Se detalla una guía para que los usuarios
refactoricen un programa a través de un proceso de cinco pasos.

36
Tanto el proceso como la herramienta de refactorización semiautomática tienen como
objetivo la reutilización de features existentes del sistema para modificarlos y utilizarlos para
componer nuevos features. Sin embargo, nunca se hace hincapié en la calidad del sistema ni
en la deteccion y correccion en el proceso de posibles problemas de diseño.

3.2.4. Modelo matemático para refactoring


También se ha desarrollado un modelo matemático tipo grafo que permite especificar
sistemas orientados a objetos asi como esquemas de refactorización [Schulz2009]. Para
describir las instancias juntos con su esquema, se utilizan las estructuras que conforman el
grafo. Los nodos representan clases u objetos, los arcos representan asociaciones o
invocaciones. Los homomorfismos entre las estructuras del grafo representan typings, partes
del refactoring y migraciones. Luego, el grafo se traduce en una descripcion algebraica sobre la
cuál se pueden realizar ciertas operaciones. La figura 3.4 grafica las distintas representaciones
combinadas en este modelo. Si bien este modelo permite representar refactorizaciones para
mitigar riesgos antes de implementarlas, no permite detectar oportunidades de refactorización
ni medir su impacto en la calidad del software.

Figura 3.4. Distintas representaciones del mismo conjunto de clases.


(a) Esquema UML, (b) grafo y (c) representación algebraica [Schulz2009].

3.3. Impacto de refactorings en la calidad del software


El objetivo de las acciones de refactorización de aplicaciones es lograr una mejora notable
en la calidad del software, principalmente aquellos atributos ligados a la modificabilidad, la
flexibilidad y la tolerancia del sistema a la incorporación de nuevas funcionalidades. A
continuación se presentan algunos trabajos que analizan la relación entre refactorings y
mejoras de calidad.

3.3.1. Efectos del refactoring en la calidad del software


El refactoring es una valiosa herramienta utilizada para mejorar el diseño del software,
hacerlo entendible, encontrar errores y reestructurar el código de las clases de sistemas

37
orientados a objetos. El refactoring es a menudo utilizado como parte del desarrollo del
software, como es el caso de la metodología Extreme Programming (XP), donde el refactoring
es una parte central del ciclo de desarrollo.

Existe un esfuerzo y costo significante dedicado a refactorizar el software ya que se asume


que así se obtiene una mejora notable de calidad. Sin embargo, experiencias muestran que los
resultados son muy dispares y no logran demostrar que la aplicación de refactorings se
traduzca siempre en mejoras de extensibilidad, modularidad, reusabilidad, complejidad,
eficiencia y modificabilidad para el sistema [Alshayeb2009].

3.3.2. Acoplamiento y cohesión como indicadores de buena


calidad
Si bien es claro que se pueden utilizar refactorings para reestructurar sistemas de software,
no queda tan claro cómo usarlos para mejorar atributos de calidad específicos que den indicios
de un buen diseño. El acoplamiento -la intensidad de la asociación establecida en la conexión
entre dos clases- y la cohesión -el grado de relación entre los elementos de una clase- son
indicadores que permiten medir la distribución de responsabilidades en un conjunto de clases.
Más allá de las mejora de diseño, que son difíciles de medir, los refactorings permiten mejorar
los indicadores de acoplamiento y la cohesión del software [DuBois2004]. Por lo tanto, estos
dos métricas permiten, por un lado, identificar oportunidades de refactoring allí donde los
indicadores no sean muy buenos y, por otro, a través de su observación, evaluar los
refactorings implementados. Sin embargo, estas métricas deberían ser complementadas con
otras y sobretodo, someterse al análisis de los desarrolladores, ya que en algunos casos, las
métricas cuantificables no siempre se traducen en resultados cualitativos.

3.3.3. Relación entre calidad y refactoring en casos de estudio


A menudo los resultados indican que, en general en los sistemas de software existentes, las
métricas de calidad no guardan relación con los refactorings aplicados. Es decir, las
operaciones de refactorización generalmente están enfocadas en componentes de código para
los cuáles las métricas calidad no sugieren allí la necesidad de tales operaciones. Analizando la
evolución de distintos proyectos de desarrollo y los refactorings aplicados a lo largo de su ciclo
de vida, se ha podido comprobar que apenas el 42% de los refactorings se realiza sobre code
smells y apenas el 7% esos smells es eliminado [Bavota2015]. Sin embargo, en estas
experiencias, se han estudiado procesos de refactorizaciones que no se han centrado sobre los
code smells, sino que han sido calculados a posteriori. Es necesario que el proceso de
refactoring sea guiado por los code smells y la mejora la calidad del software.

3.3.4. Modificabilidad de arquitecturas de software


Los cambios en un sistema de software ocupan gran parte del costo del ciclo de vida del
sistema. Por eso, es importante contar con una buena arquitectura de software que permita
preparar el sistema tanto para cambios esperados como para cambios inesperados a futuro. en
la Figura 3.5 se aprecia la relación entre la arquitectura y la calidad del sistema.

38
Se ha desarrollado un método para el análisis de modificabilidad de arquitecturas de
software basado en escenarios de calidad [Bengtsson2000]. Este enfoque consta de 5 pasos:
(1) definir el objetivo, (2) describir la arquitectura, (3) elicitar los escenarios, (4) evaluar los
escenarios y (5) interpretar los resultados. El método desarrollado permite trabajar en tres tipos
de objetivos: predicción de mantenimiento, evaluación de riesgos y selección de arquitecturas
de software. Según el objetivo, se combinan distintas técnicas en los siguientes pasos del
proceso. Si bien, el método parece robusto para analizar la arquitectura de un software, no ha
sido probado en casos de estudio concretos. El método tampoco se aplica para analizar la
estructura interna del software, es decir, su diseño e implementación.

Figura 3.5. Análisis de arquitectura de software [Bengtsson2000].

3.3.5. Impacto de técnicas de refactoring en las métricas de


calidad
Se espera que los esfuerzos de la refactorización de un sistema impacten en un mejora de
su calidad facilitando, en particular, su extensibilidad, modularidad, reusabilidad, complejidad,
mantenibilidad y eficiencia. A partir de la evaluación de la evolución de sistemas de software se
ha podido medir esa mejora de calidad. Estudios han detectado una tendencia que muestra
que la complejidad promedio y el tamaño del código se reduce con el refactoring
[Shrivastava2008]. Por eso, aunque la tarea de refactorización requiere tiempo y dedicación,
tiene un impacto positivo en la calidad del software. Quedan demostradas las mejoras de
calidad a partir de la aplicación de ciertas técnicas de refactoring en un proyecto de desarrollo,
sin embargo, se reconoce que no son las técnicas más utilizadas por la industria. Apenas unas
pocas técnicas son evaluadas y por lo tanto sería deseable profundizar el análisis aplicando
otras técnicas en el marco de la evolución de un sistema real.

3.4. Refactorings guiados por code smells


Existe actualmente un amplio abanico de herramientas e indicadores utilizados para realizar
diagnósticos de calidad de un sistema y detectar oportunidades de refactorización.

39
Naturalmente, muchas de estas métricas, sirven también para medir los resultados luego de
aplicar las refactorizaciones al sistema. En este trabajo final se propone utilizar los code smells
con estos propósitos. A continuación se revisa una serie de experiencias basadas en code
smells.

3.4.1. Evaluación de sistemas a partir del análisis objetivo y el


análisis subjetivo
Se ha realizado un estudio empírico utilizando code smells para evaluar subjetivamente la
evolución de estructuras pobremente evolucionadas en el software [Mäntylä2006]. El
componente empírico se basa en un caso de estudio en una empresa de productos de
software. Las métricas basadas en el análisis automático del programa y las humanas de code
smells no se correlacionan plenamente. En base a los resultados, se sugiere que las
organizaciones deben tomar decisiones con respecto a la mejora de la evolución del software
como una combinación de evaluaciones subjetivas y métricas de código.

En este trabajo se comprueba empíricamente el trade-off que existe entre el análisis


realizado con métricas automatizadas y la percepción que puede tener el ojo experto de un
desarrollador a la hora de localizar los code smells en el código. Sin embargo, no se aporta
ningun proceso o metodología de cómo combinar estos dos enfoques ni cómo aplicarlos a la
implementación de los refactorings.

3.4.2. Detección automatizada de code smells


Se ha investigado cómo la calidad de código se puede evaluar de forma automática
mediante la comprobación de la presencia de code smells y cómo este enfoque puede
contribuir a la inspección automática de código [VanEmden2002]. Se presenta un enfoque para
la detección automática y la visualización de code smells y se discute cómo este enfoque se
puede utilizar en el diseño de una herramienta de inspección software. Se investiga también la
viabilidad del enfoque descrito utilizando un caso de estudio en el que se ha desarrollado y
aplicado una herramienta prototipo para detectar code smells en el código Java.

El aporte de este trabajo es identificar en qué áreas de código están localizados la mayor
cantidad de code smells. Para los usuarios del caso de estudio esto permite usar la
herramienta como un monitor para ver cómo varía la calidad del sistema a medida que va
evolucionando. Sin embargo, la herramienta y enfoque no hacen hincapié en cómo resolver los
problemas de modificabilidad del sistema si no que sólo brinda soporte para el análisis y la
detección.

3.4.3. Identificación de Architectural Smells


Mientras que la detección y corrección de code smells mejora la mantenibilidad de un
sistema, muchos de los problemas de mantenibilidad se originan más por el uso pobre de
abstracciones a nivel de arquitectura que de implementación. A partir de analizar distintos
sistemas, se definieron un conjunto de architectural smells arquitectónicos, análogos a los

40
smells de código [Garcia2009]. Estos son Connector Envy, Scattered Functionality, Ambiguous
Interfaces, Extraneous Connector. Una vez detectado un architectural smell, se puede evaluar
el impacto de calidad en el sistema para pensar en refactorizaciones a nivel arquitectónico. Si
bien es sabido que los problemas estructurales son los que más limitan la evolución del
sistema, realizar cambios en la arquitectura del sistema tiene un riesgo muy elevado. Por eso,
es conveniente identificar esos problemas pero dividirlos en problemas más pequeños, de
modo tal de buscar soluciones que mitiguen riesgos y sumadas, permitan una mejora a nivel
global.

3.4.4. Detección de code smells relevantes para la arquitectura


El refactoring de sistemas tiende a evitar la desviación temprana del diseño de la arquitectura
deseada de un programa. Sin embargo, existe poco conocimiento sobre si la manifestación de
code smells en la evolución del software es indicador de desviaciones de la arquitectura. Una
dificultad fundamental en este proceso es que los desarrolladores sólo están equipados con
técnicas estáticas de análisis de código, las cuales no muestran la traza de información
arquitectónica. Se ha atendido este problema ayudando a (1) identificar una familia de code
smells relevantes para la arquitectura, (2) proveer evidencia empírica sobre la correlación entre
patrones de code smells y degeneraciones arquitecturales, (3) proponer un conjunto de
métricas y estrategias de detección que exploran la información de la arquitectura en la
detección de smells y (4) concebir una técnica de soporte para la identificación temprana de
síntomas de degeneraciones de la arquitectura analizando patrones de code smells
[Bertran2011]. Si bien, es importante el aporte realizado, ya que es importante priorizar los
smells que afectan directamente a la arquitectura, estas técnicas no han sido validadas a través
de casos de estudio de sistemas reales.

3.4.5. Orden apropiado de resolución de code smells


La resolución de un tipo de code smell puede impactar en la resolución de otro(s) smells. En
consecuencia, diferentes órdenes de resolución del mismo conjunto de smells pueden requerir
distintos esfuerzos y/o llevar a diferentes mejoras de calidad. Para hacer más eficiente el
trabajo y maximizar los efectos de refactorización, se han analizado las relaciones entre los
smells y se ha llegado a la recomendación de un orden de resolución de code smells [Liu2009].
Dicho orden se puede ver en la Figura 3.1. Sin embargo, en un sistema grande y complejo, el
número de smells que se detectan puede ser muy elevado por lo que no siempre se pueden
resolver todos. Es bueno tener un orden de resolución según el tipo pero también es necesario
acotar la cantidad de smells de acuerdo a otros criterios. Además, en el orden recomendado no
aparecen todos los tipos de smells definidos por otros autores [Lanza2007].

41
Figura 3.1. Orden de resolución de code smells recomendada [Liu2009] .

3.4.6. Una experiencia con code smells en Extreme Programming


(XP)
La metodología ágil de desarrollo de software llamada Extreme Programming (XP) consta de
un conjunto de las mejores prácticas que, cuando se usa, promete la entrega rápida de un
software de mayor calidad que el que se encuentra con las metodologías más tradicionales.

Se ha estudiado un proyecto de desarrollo de software de gran tamaño que utiliza un


enfoque modificado de XP, identificando varias prácticas improductivas que se detectaron
durante sus más de dos años de vida que amenazaron con la terminación del proyecto
[Elssamadisy2002]. Se han identificado problemas en todo el ciclo de vida, incluyendo el
análisis, diseño, desarrollo y pruebas. Por cada práctica se identifican, y se discute la solución
que se implementa para corregirlo y, más importante aún, se examinan los primeros síntomas
de esas malas prácticas: code smells.

La experiencia reflejada en el trabajo puntualiza que XP es un enfoque valioso y eficaz para


el desarrollo de software, siempre y cuando uno reconoce que:

1. No puede tener éxito sin participantes con conciencia de la metodología y

2. Debe ser adaptado según sea necesario para los proyectos que no se ajustan a los
límites de pequeños equipos recomendado por los fundadores de esta metodología ágil.

42
En el trabajo puntualiza la experiencia en un proyecto a gran escala aplicando la metodología
XP, haciendo evolucionar el diseño de software con refactorizaciones sucesivas hasta obtener
la calidad requerida, teniendo en cuenta el análisis de code smells en el código para detectar
posibles desviaciones o malas prácticas en el desarrollo del sistema. Sin embargo, no define en
sí un proceso generalizado para realizar las refactorizaciones, sino específicamente cómo
ajustar la metodología XP al contexto de un determinado proyecto de software.

3.5. Resumen
En resumen, en este capítulo se realizó un repaso general de las distintas tácticas de
refactorización, el uso de herramientas de soporte y distintas formas de medir las mejoras
obtenidas por la refactorización de sistemas de software.

Existe un amplio abanico de enfoques para la refactorización de sistemas. Algunos de ellos


están basados en herramientas de soporte, otros en métricas, en features o en modelos
matemáticos. El objetivo del presente trabajo es tomar algunos elementos de estos enfoques
para definir un proceso de refactorización guiado por la mejoras de calidad y code smells.

Se han revisado trabajos de investigación que vinculan la implementación de refactorings


con las mejoras de calidad del software. En general se aprecia que las refactorizaciones
implican mejoras de calidad. Como se ha visto, las tácticas y enfoques de refactorización son
variadas y también lo son métricas de calidad utilizadas para evaluar las mejoras obtenidas. En
general las investigaciones se acotan a ciertos tipos de refactorizaciones y/o a la observación
de algunas métricas en particular. El objeto de este trabajo final es ofrecer una experiencia a
partir de la definición de un proceso utilizando escenarios de calidad y code smells.

Es relativamente sencillo detectar los code smells en el código de un sistema de software.


Sin embargo, los smells pueden aparecer en gran cantidad y es necesario enriquecer el listado
de smells a partir otras herramientas como el análisis subjetivo aportado por los
desarrolladores, la identificación de smells más relevantes para la evolución del software y la
arquitectura en general y también el orden en que se ataca la resolución de los smells.

43
4. Proceso de Refactorización
La refactorización de sistemas informáticos, tiene el objetivo de mejorar la estructura interna
del software, sin alterar su funcionalidad [Fowler1999]. Bajo esta definición, cualquier cambio,
por pequeño sea, si representa una mejora y no afecta la funcionalidad del sistema, podría
considerarse una “refactorización”. Esto sucede a diario y de manera implícita y natural por
parte de los desarrolladores de un proyecto de software.

Se pretende ir más allá de esas pequeñas mejoras que a diario realizan los programadores
para pensar en la refactorización como un proceso enmarcado en el desarrollo de software que
permite mejorar la calidad del sistema a partir de un incremento de su modificabilidad. Esto
significa que las refactorizaciones se enmarcan en un proceso ordenado, con objetivos
concretos, modificaciones definidas y validadas, testeo de funcionalidades afectadas y análisis
de resultados.

En ambientes de desarrollo de sistemas de software, cada tarea implica una inversión de


horas de trabajo, de las cuales se esperan resultados medibles. Los coordinadores de proyecto
deben decidir a qué aspectos del sistema dedicar la fuerza de trabajo de los diseñadores,
arquitectos, programadores y testers de manera de obtener un software de calidad aceptable
en el menor tiempo posible. De esta manera, a la hora de asignar los recursos disponibles para
realizar los cambios es importante definir cuáles refactorizaciones se deben hacer para tener la
mejor relación costo/beneficio y cuáles no vale la pena realizar ya que repercuten en un alto
esfuerzo a cambio de un bajo beneficio a futuro.

Este capítulo describe un proceso de refactoring de un sistema centrado en la mejora de


calidad del mismo. Dicho proceso es guiado por escenarios de modificabilidad, que es el
atributo de calidad específico que se desea mejorar. A su vez, se utilizan distintos tipos de
métricas para evaluar las modificaciones realizadas, fundamentalmente los code smells, que
servirán tanto para detectar posibles anomalías que afectan al sistema como para medir el
avance. También se realizan sucesivos testeos para verificar que dichas modificaciones no
impactan de manera negativa en la funcionalidad del sistema.

4.1. Definición del Proceso


Si uno o más desarrolladores accedieran al código de un sistema y se valieran de algunas
simples herramientas de detección y recomendación de refactorizaciones, seguramente
podrían realizar cambios a su código fuente. Es muy probable que de esa manera se obtengan
mejoras medibles, aunque no necesariamente dichas modificaciones se realicen sobre los
aspectos del sistema que se desean optimizar en el marco de un proceso de desarrollo.

El propósito de este proceso de refactoring es hacer el sistema más fácil de entender, ya que
un software fácil de entender es un software fácil de modificar [Fowler1999]. Por lo tanto este
proceso tendrá como resultado una mejora de la calidad del software desde el punto de vista
de la modificabilidad.

44
A continuación se describen los principales aspectos del proceso utilizados en este trabajo
final.

4.1.1. Refactoring iterativo por etapas


Existen diversas formas de llevar adelante el proceso de refactorización. La idea es que este
proceso sea descrito bajo ciertos criterios, que sea comprendido por el equipo de trabajo y que
pueda ser implementado por los desarrolladores. En general se suelen identificar etapas por las
cuales se deberá ir avanzando, cumpliendo con diferentes tareas que permitan cumplir con los
objetivos planteados.

Es cada vez más común el uso de metodologías de desarrollo de software de carácter


iterativo, es decir, métodos en los cuales se realizan sucesivas revisiones recorriendo más de
una vez las etapas planteadas. De esta manera, se subdivide el trabajo total a realizar mientras
que se mitigan mejor los riesgos, el proceso se retroalimenta y se obtienen resultados
observables al final de cada iteración. Se decide enmarcar las etapas de refactorización en un
proceso iterativo incremental, de modo de ir obteniendo resultados mejores y más refinados
luego de cada iteración.

En términos generales, para este proceso de refactorización se pueden identificar un


conjunto de etapas iterables de manera incremental, que conforman un proceso guiado por
escenarios de modificabilidad y code smells. En principio, es necesario el análisis del código
del sistema para detectar errores y síntomas de problemas asociados al desarrollo y las malas
prácticas de diseño. Con este diagnóstico, se pasa a la proposición y realización de
modificaciones con el objetivo de solucionar esos problemas detectados; luego, se realiza una
evaluación de los cambios desarrollados y su impacto en la calidad del sistema. Finalmente se
decide si conviene llevar adelante una nueva iteración de etapas para refinar la refactorización.

En la figura 3.1 se detalla el flujo del proceso planteado y los distintos grupos de etapas
propuestas para la refactorización. El proceso consta de 6 etapas:

● Análisis e identificación del problema: Se estudia el sistema o una sección del


mismo en busca de deficiencias de implementación y/o diseño. Se listan los problemas
encontrados.
● Planteo de solución y definición de escenarios: A partir de los problemas
detectados se proponen soluciones a implementar que se definen en escenarios de
modificabilidad.
● Priorización de backlog: Se ponderan los escenarios definidos ubicándolos en un
cuadrante de deuda técnica.
● Implementación de escenario: Los desarrolladores implementan uno a uno los
escenarios especificados. Se ejecutan los casos de test.
● Medición de resultados: Se analizan las mejoras de modificabilidad introducidas en
el sistema.
● Evaluación: Se decide si se realiza una nueva iteración sobre el sistema o una de
sus partes; o si se da por finalizado el proceso de refactorización.

45
A continuación, se detallarán en profundidad cada una de las etapas y sus implicancias.

Figura 3.1. Proceso de refactorización.

46
4.1.1.1. Etapa 1: Análisis e identificación del problema ¿Qué
refactorizar?

Figura 3.2. Entrada, actividades y salida de la etapa 2 del proceso.

La primera etapa de la refactorización tiene el objetivo de revisar el diseño y la


implementación del proyecto para localizar secciones del código fuente que podrían ser
mejoradas. Estas mejoras deben apuntar a modificar artefactos del sistema de modo que
resulte más entendible y modificable, es decir, más adaptable a los cambios futuros
[Fowler1999]. Para esto, es fundamental tener un conocimiento pleno del proyecto: su
arquitectura, funcionalidades actuales y futuras, atributos de calidad, etc. Cuánto más
conocimiento se tenga del sistema, más eficiente será el proceso de refactoring.

La motivación principal del proceso de refactorización es la mejora de un diseño que no


permite añadir fácilmente ciertas funcionalidades. Al momento de agregar una nueva
funcionalidad, el desarrollador ve que si hubiera diseñado el código de otra manera, la adición
de esta funcionalidad se haría fácilmente.

La detección de problemas o síntomas puede realizarse de muchas formas. En algunos


casos, el refactoring resultará más simple ya que los problemas son evidentes o bien el equipo
conoce demasiado bien el proyecto y sus principales defectos. En otros casos, será necesario
examinar el código en profundidad para localizar aquellas secciones de código que pueden ser
mejoradas para que el sistema se adapte más fácil a los cambios en el futuro.

En esta etapa se deben identificar las secciones de código en las cuales el proyecto es poco
modificable, es decir, cuáles son las clases y paquetes que no permiten agregar una nueva
funcionalidad de forma sencilla. Este aspecto está ligado directamente con los cambios que se
deseen realizar, ya que no se trata de mejorar este atributo de calidad en cualquier paquete o
clase del sistema, sino en aquellas áreas directamente afectadas por los requerimientos
funcionales que se proponen desarrollar en un futuro. Por eso, es necesario un reconocimiento
del estado actual del proyecto pero también tener una noción de los próximos pasos en su
desarrollo. A continuación se presentan algunos lineamientos que sirven como guía para lograr
una refactorización eficiente:

• Documentación del proyecto. Para comprender mejor los procesos y flujos de datos
del sistema, es preciso consultar la documentación del proyecto, en especial lo que se

47
refiere a aquellas áreas del código potencialmente afectadas por las nuevas
funcionalidades que se busca desarrollar. Si bien muchos proyectos de desarrollo no
cuentan con documentación actualizada, se pueden generar nuevos documentos que
ayuden a entender cómo interactúan distintos componentes del software. Para esto, son
de gran utilidad los diagramas estandarizados de UML tales como diagrama de clases,
diagrama de interacción y diagrama de actividades, entre otros. La confección de
diagramas tipo UML permite documentar en un lenguaje conocido por los
desarrolladores y a su vez facilita la detección de debilidades en el diseño, por ejemplo
a través de un diagrama de clases. El objetivo no es documentar toda la arquitectura de
manera formal, sino valerse de una descripción más clara del problema concreto que se
desea abordar. Significa que, en cada caso de refactorización, habrá que evaluar si es
necesario generar un nuevo documento, de qué tipo y en qué área específica del
sistema.

• Detección de code smells. Para esta etapa del proceso también se ha optado por
utilizar los code smells como síntomas de malas prácticas que podrían impactar en el
diseño y potencialmente en la modificabilidad del sistema. Es así que una vez
identificadas las “áreas conflictivas” del código se propone observar la presencia de
smells como guía para proponer refactorizaciones de modificabilidad. La detección de
smells puede realizarse a través de herramientas automatizadas. Por ejemplo, para este
trabajo se usa JSpIRIT [JSpIRIT], un plugin de Eclipse que permite detectar los smells y
genera un rankeo de los mismos a partir de distintos criterios como escenarios de
calidad y/o la asignación de pesos a cada tipo de smell. Dada la gran cantidad de smells
que pueden encontrarse en un proyecto de software, el ranking de los mismos es
importante para seleccionar los síntomas que deben abordarse con mayor prioridad.
Como se ha visto, los code smells no son necesariamente errores, sino síntomas de
malas prácticas de implementación que pueden llevar a la detección de falencias en el
diseño del sistema. Por lo tanto, es necesario el estudio de cada caso en su contexto y
en los objetivos generales del proceso de refactorización.

• Identificación de los problemas en el diseño. Encontrar el diseño adecuado de un


sistema es la clave para que sea más entendible y soporte fácilmente cambios en el
futuro [Fowler1999]. Una vez que se tiene definidas las áreas de interés del código y se
profundizó en el análisis utilizando herramientas de detección de code smells que
permiten identificar los síntomas de malas prácticas y problemas de diseño, se debe
realizar la tarea más compleja: identificar los problemas de diseño y las modificaciones
que permitan hacer más entendible el código. Esta tarea no es sencilla y depende del
entendimiento de la arquitectura del sistema, de cuáles son las relaciones entre los
artefactos de software y cuáles son los requerimientos que se desean soportar a futuro.
A la hora de tomar una decisión sobre cuál es el problema que afecta la calidad del
software es importante apoyarse en métricas que permitan evaluar la calidad de un
diseño pero también que los desarrolladores logren un buen entendimiento del sistema
para poder tomar una decisión acertada sobre cuáles son los principales problemas y
cuáles pueden ser los refactorings candidatos para solucionarlos.

48
El análisis de problemas en el sistema debe ser metódico, ordenado y fundamentado. En
general es realizado por los desarrolladores, que son quienes mejor conocen su arquitectura e
implementación. Los miembros del equipo conocen en profundidad el software y a la hora de
incorporar cambios sabrán por dónde empezar y qué restricciones existen para que el sistema
se adapte a las nuevas funcionalidades que se desean incorporar. Sin embargo, la intuición
humana no es escalable a sistemas de grandes dimensiones [Lanza2007] y por lo tanto para
encontrar y mejorar anomalías de diseño es necesario emplear estrategias que incluyan el uso
de herramientas que automaticen la detección de síntomas. Es así que se debe combinar el
análisis objetivo obtenido a través de indicadores estadísticos y herramientas de detección con
el análisis subjetivo que proporcionan las personas abocadas al desarrollo. Al finalizar esta
etapa se deberá contar con una serie de artefactos -clases y paquetes- ligados a una
determinada funcionalidad o sección del sistema que presente síntomas de problemas de
diseño y/o implementación. Así, en la próxima etapa se podrá trabajar en mejoras para esas
irregularidades.

4.1.1.2. Etapa 2: Planteo de solución y definición de escenarios.

Figura 3.3. Entrada, actividades y salida de la etapa 2 del proceso.

Una vez identificados los problemas más significativos en el sistema acorde a los objetivos,
el equipo de trabajo puede comenzar a pensar cómo abordar cada conflicto proponiendo
cambios que devengan en mejoras de calidad. El alcance de cada solución dependerá
directamente de la dimensión del problema detectado y por lo tanto se podrán sugerir cambios
tanto a nivel de diseño como de implementación. Las actividades a realizar en esta etapa son:

• Definir refactorings a realizar. Los expertos aconsejan realizar el refactoring de un


problema en pasos simples, pequeños y seguros [Kerievsky2005]. Para resolver cada
uno de los problemas relevados es prudente encarar su solución en pequeños
refactorings que tengan como objetivo final solucionar el problema o mejorar la calidad
global del sistema. Refactorizar de esta forma hace que el proceso sea más rápido, ya
que encarar cambios más grandes implica tareas más complejas donde se corre mayor
riesgo de introducir errores. Si se procede en pequeños pasos, los errores introducidos
durante la refactorización serán fáciles de encontrar ya que los cambios serán
pequeños.

49
• Especificar Escenarios de Modificabilidad. Para especificar cada uno de estos
pequeños cambios para resolver un problema complejo se utilizan escenarios de
modificabilidad. Con este artefacto, se documenta de manera estandarizada el objetivo
de las tareas que tendrán que realizar los desarrolladores. Para ello, se deberán
organizar las tareas a realizar en distintos escenarios de modificabilidad definiendo para
cada uno: fuente del estímulo, estímulo, artefacto, ambiente, respuesta y medida de la
respuesta.

• Agregar al backlog. Los escenarios especificados se agregan al backlog de tareas a


realizar en la iteración actual del proceso de refactoring.

4.1.1.3. Etapa 3: Priorización de backlog. ¿Por dónde conviene


empezar?

Figura 3.4. Entrada, actividades y salida de la etapa 3 del proceso.

La idea de agrupar en un backlog los escenarios de modificabilidad definidos en la etapa


anterior, tiene que ver con la necesidad de priorizarlos antes de pasar a la etapa de
implementación. Como se ha visto, se busca definir un proceso de refactorización que pueda
enmarcarse en un proceso completo y real de desarrollo de software en donde los recursos
(horas de trabajo) son finitos y los stakeholders del proyecto pretenden maximizar su inversión
y minimizar sus riesgos. Por eso es bueno dedicar cierto tiempo a realizar una ponderación de
tareas que resulte en un aprovechamiento eficiente de los recursos. De esta manera, se
implementarán en orden decreciente los cambios más relevantes para el proyecto y, en caso de
no poder completar el backlog, los cambios pendientes serán aquellos con menor impacto.

Existen múltiples métodos para realizar una buena ponderación de requerimientos, aunque la
definición de este proceso de refactorización no tiene por objetivo encontrar el mejor de ellos
sino más bien proponer ciertos criterios de priorización que conduzcan a un rankeo no aleatorio
y de esta manera mejorar la calidad del proceso de refactorización. Al instanciar el proceso en
un proyecto se podrá optar por otras métodos si así se considera.

Para realizar la ponderación se trabaja sobre el diagrama de cuatro cuadrantes de la


retrospectiva de deuda técnica que se aprecia en la figura 2.5. Se evalúa cada escenario a
partir de su nivel de impacto y de esfuerzo; y luego se lo ubica en el cuadrante correspondiente.

50
• Nivel de Impacto. Esta variable se refiere al nivel de avance que implica la realización
de un escenario. O desde una perspectiva negativa, el nivel de “dolor” o riesgo que
representa ese problema del sistema que se busca solucionar. Naturalmente, tendrán
una prioridad más alta aquellos escenarios de modificabilidad que tengan mayor nivel
de impacto. La valoración de impacto que se le da a un escenario puede variar de
acuerdo al momento en que se encuentra el desarrollo o bien entre los distintos
miembros del equipo de desarrollo ya que cada uno puede tener distintas perspectivas.
Para facilitar el criterio, es importante tener siempre presentes los objetivos principales
por los cuales se está llevando a cabo el proceso de refactorización.
• Nivel de Esfuerzo. La otra variable a considerar es el esfuerzo, es decir, la cantidad de
recursos que se estima necesaria para llevar adelante la tarea. Generalmente es
medida en horas/hombre de acuerdo al tiempo que requiere para ser implementada.
Este factor depende directamente de las capacidades técnicas de los desarrolladores,
de la comprensión del problema y del nivel de conocimiento que tengan del sistema.
• Ubicación en cuadrantes. La definición de los criterios mencionados es una
estimación que surge del equipo de desarrolladores. Es difícil asignarle a cada
escenario un valor exacto de impacto y esfuerzo, pero lo importante es realizar una
aproximación a partir del análisis objetivo y subjetivo realizado. Se propone realizar un
cuadro de doble entrada con dos valores (alta y baja) para cada criterio, para luego
ubicar cada uno de los escenarios en uno de los cuatro cuadrantes resultantes. De esta
manera, para cada escenario se puede apreciar fácilmente la relación entre impacto y
esfuerzo que se le atribuye:
○ Bajo impacto / Alto esfuerzo: Representa el peor cuadrante, los casos con
menor retorno. No es recomendable priorizar escenarios que requieren mucha
dedicación a cambio de pocas mejoras en el sistema o alto riesgo.
○ Alto impacto / Bajo esfuerzo: El mejor caso, los escenarios prioritarios ya que
con poco esfuerzo se logra un impacto relativamente alto en el sistema.
○ Alto impacto / Alto esfuerzo y Bajo impacto / Bajo esfuerzo: Los casos
intermedios. Habrá que evaluar en cada caso el nivel de prioridad que se le
atribuye.

4.1.1.4. Etapa 4: Implementación de escenario. ¡Refactorizar!

Figura 3.5. Entrada, actividades y salida de la etapa 4 del proceso.

51
Una vez definidos los escenarios de modificabilidad y ponderados en el backlog, se puede
pasar a la etapa de implementación de cambios en el sistema. Esta tarea puede ser llevada a
cabo por los responsables del proceso de refactorización o bien por cualquier miembro del
equipo de desarrollo del proyecto. En la figura 3.6 se puede ver el diagrama de flujo de las
actividades de esta etapa. A continuación se describe cada una:

Figura 3.6. Diagrama de flujo de la etapa 3 del proceso.

• Verificar cobertura de test. Por definición, el refactoring realiza cambios en la


estructura del sistema, sin alterar su funcionalidad [Fowler1999]. Por lo tanto, antes de
realizar modificaciones en el código, es importante garantizar su integridad a través del
testeo automatizado de su funcionalidad. En algunos casos el proyecto ya contará con
casos de tests implementados para tal fin, pero en otros casos, habrá que desarrollarlos
antes de la implementación del escenario. Este esquema de trabajo puede requerir más
horas de desarrollo en un principio, pero se espera que con el avance del proceso de
refactorización, se vaya cubriendo el testeo de todas las funcionalidades afectadas, con
lo cual, cada vez habría menos “overhead” de trabajo dedicado a implementar casos de
tests. De esta manera, se logrará un proceso más seguro y confiable, y al finalizar la
refactorización, el proyecto quedará provisto de un sólido conjunto de casos de tests, si
es que nos los tenía de antemano.
• Implementar Escenario. Se deberán tomar uno a uno los escenarios del backlog
priorizado para ir llevando a cabo las tareas que estos especifiquen. Dependiendo de
cada caso, el escenario podrá ser más o menos explícito en las tareas a realizar, es
decir, en algunos casos se detalla textualmente las modificaciones a realizar mientras
que en otros, se describen algunas directrices de diseño sobre uno o más artefactos. Lo

52
importante en todos los casos es garantizar la “medida de la respuesta” ya que de esta
manera el escenario se considerará completado.
• Ejecutar casos de test. Una vez realizados los cambios en el sistema, se deben
ejecutar los casos de test correspondientes para comprobar que no se han introducido
errores. En caso de que algún caso de test no se ejecute con éxito, se deberá volver a
la implementación del escenario para corregir el error introducido, como se ven en la
figura 3.6. En este tipo de situaciones es donde el refactoring en pequeños pasos brinda
sus frutos ya que, al no modificar una gran cantidad de clases y realizarse en un tiempo
mucho menor, el error será más fácil de encontrar.

4.1.1.5. Etapa 5: Medición de resultados. ¿Es válida la


refactorización?

Figura 3.7. Entrada, actividades y salida de la etapa 5 del proceso.

Se espera que con cada implementación realizada se mejore en mayor o menor medida la
modificabilidad del software, ya que hay escenarios que impactan significativamente en las
métricas y otros que su valoración es más bien subjetiva. Por esta razón, la correcta realización
de cada escenario se determinará verificando la medida de su respuesta, la cual guarda
relación directa con la modificabilidad de todo el sistema o de una sección del mismo. Las
actividades a realizar en esta etapa son:

• Análisis basado en métricas y documentación. La medida de la respuesta variará de


acuerdo al problema que aborde el escenario, aunque en general estará ligada a
mejorar ciertas métricas de calidad relacionadas con la modificabilidad y flexibilidad del
sistema para tolerar nuevos cambios. El impacto de las métricas puede ser a nivel
global del sistema o bien a nivel local de las clases y paquetes que son refactorizados
en un escenario específico. La importancia de medir y documentar los resultados está
ligada a la efectividad y eficiencia del proceso de refactorización, de esta manera se
podrá juzgar con mayor precisión cuáles son las mejoras de calidad obtenidas a partir
de cada escenario implementado. En algunos casos, los resultados obtenidos de la
implementación podrán variar de lo definido por el escenario, tanto de manera negativa,
sin alcanzar completamente las medidas esperadas, como de manera positiva,
obteniendo indicadores mejores que los proyectados.
Como métricas se pueden utilizar distintos indicadores calculados a través de
herramientas de soporte, entre ellos se destacan: cantidad, tipos y ponderación de code
smells, cantidad de horas hombre dedicadas a la implementación, cantidad de clases

53
que es necesario revisar o modificar para introducir un cambio y/o métricas relacionadas
con la modificabilidad como por ejemplo: abstractness, average cyclomatic complexity,
average number of parameters, efferent couplings, lines of code, weighted methods,
entre otros.
• Análisis subjetivo y valoración del grupo de desarrollo. En algunos casos es
necesario el análisis de los responsables del proceso para determinar si el escenario
está resuelto o no. Es decir, en esta etapa también será válido el análisis subjetivo de
los desarrolladores que conocen la implementación del software. Por ejemplo, luego de
la refactorización de un artefacto, se podrá evaluar el nivel de esfuerzo requerido para
comprender y modificar dicho artefacto. Este tipo de observaciones solo pueden
realizarse mediante el juicio humano de los desarrolladores del sistema o bien el de un
evaluador de calidad externo.
• Validación del escenario. La evaluación de resultados será de carácter cuantitativa,
cualitativa o bien una combinación de las dos a partir de la complementariedad de
distintas herramientas de análisis. En cualquier caso, el escenario será validado en
tanto se alcance la “medida de la respuesta” especificada en su definición formal.
Finalmente, una vez determinada la resolución del escenario, se deberá tomar el siguiente
escenario del backlog (etapa 4). En caso de no quedar más escenarios, se procederá a la
última etapa de la iteración.

4.1.1.6. Etapa 6: Evaluación. ¿Seguir refactorizando?


Al comenzar esta última etapa, se han implementado todos los escenarios del backlog. Es
necesario determinar si se realiza una nueva iteración del proceso o si se considera que se ha
llegado a un punto de calidad aceptable. Para la toma de esta decisión, los responsables del
proceso, realizarán un rápido análisis teniendo en cuenta varios factores ligados a aspectos
objetivos y subjetivos del desarrollo.

En principio, esta decisión debería ser guiada por las mejoras de calidad en cuanto a la
modificabilidad. Si el sistema se considera lo suficientemente modificable para el desarrollo de
las próximas funcionalidades del proyecto o bien si se observa una notable disminución de la
deuda técnica, entonces el proceso se podrá considerar “terminado”.

En caso que las métricas no revelen una mejora considerable, se podrá optar por una nueva
iteración del proceso. Seguramente, en etapas anteriores se hayan detectado problemas o
síntomas que evidencian la necesidad aplicar refactorizaciones en ciertas secciones del
sistema. En general, los programadores suelen encontrar posibles mejoras para el sistema que
se está desarrollando, de hecho, algunos autores sugieren realizar las refactorizaciones de
manera constante, revisando y mejorando el código a la vez que se lo implementa, analiza y
mantiene [Beck2000], [Liu2012], [MurphyHill2008].

La posibilidad de seguir refactorizando no siempre depende de la capacidad y voluntad de


los desarrolladores, sino de los tiempos y recursos del proyecto, que cuenta con un
presupuesto finito y un cronograma que cumplir. Es por esto, que también será criterio de
análisis la cantidad de horas dedicadas al proceso hasta el momento.

54
5. Caso de Estudio
Todo proceso de refactorización se realiza sobre el código de un sistema de software y en el
marco de su proyecto de desarrollo. A la hora de pensar en un refactoring, es importante
conocer el código, el diseño del sistema y su arquitectura. Es decir, el “éxito” o “fracaso” del
proceso de refactorización depende en gran medida del grado de conocimiento que se tenga
de la estructura del proyecto. En caso de no existir este conocimiento, los responsables del
refactoring deberían comenzar por estudiar el código, su diseño, sus fortalezas y sus
debilidades.

Para poner a prueba el proceso de refactorización definido en el Capítulo 4, se trabajó sobre


un caso de estudio: un proyecto de software que requiere mejoras en su modificabilidad para
agregar nuevos requerimientos funcionales en un futuro cercano. A partir del estado actual del
caso de estudio y sus objetivos de desarrollo, se instancia el proceso de refactorización con
todas sus etapas y actividades.

5.1. SocialGraph, una herramienta para analizar redes


sociales
Para el desarrollo de este trabajo se utilizó como caso de estudio SocialGraph: una
herramienta que permite estudiar las relaciones entre grupos de personas de una organización.
A partir de los datos extraídos de correos electrónicos, SocialGraph permite construir un grafo
de vínculos entre las cuentas de usuario para luego aplicar técnicas de Análisis de Redes
Sociales (SNA, por sus siglas en inglés). Los autores de este trabajo final y responsables del
refactoring, ya habían trabajado con anterioridad sobre la funcionalidad de SocialGraph, por lo
cual cuentan con una noción de su estructura interna.

5.1.1. Sobre el Análisis de Redes Sociales


Las redes sociales son objeto de estudio particular en diversos campos que van desde la
sociología hasta la gestión del conocimiento en las empresas [Wasserman1994]. Esta área de
estudio se centra en la asociación y medida de las relaciones y flujos entre las personas,
grupos, organizaciones, computadoras, sitios web, así como cualquier otra entidad de
procesamiento de información/conocimiento. Los nodos en la red en este caso son personas y
grupos mientras que los enlaces muestran relaciones o flujos entre los nodos. El análisis de
redes sociales proporciona herramientas tanto visuales como matemáticas para el estudio de
las relaciones humanas.

En muchos casos el análisis de redes sociales se fundamenta en el estudio de los agentes


en la estructura de la red, para ello se hace un análisis de las medidas de centralidad de los
actores de la propia red social con el objetivo de ver las relaciones de poder, de protagonismo y
confianza así como la detección de comunidades, grupos, etc. debido a la existencia de
clústeres específicos.

55
5.2. Definición del sistema

5.2.1. Versiones de SocialGraph


SocialGraph fue ideado para asistir el análisis en la toma de decisiones en el marco de
proyectos desarrollo de software en equipo. Es una herramienta desarrollada por un grupo de
investigación del Instituto Superior de Ingeniería de Software Tandil [ISISTAN], por lo cual no ha
sido distribuida de manera pública. Fue desarrollada con fines de aplicación científica y ha
evolucionado con distintas versiones, a fin de agregar nueva funcionalidad. Dicha evolución ha
sido "no planeada" conduciendo a un deterioro de la calidad del software. Como todo sistema,
SocialGraph tiene una arquitectura, aunque es probable que esta no haya sido lo
suficientemente planificada ni documentada.

Si bien no existe seguimiento ni documentación del código del software desde su creación,
se pueden identificar dos grandes “versiones” del mismo. Una primera versión con toda su
funcionalidad principal, sin versionado de código ni documentación sistematizada más allá de
las personas involucradas en su desarrollo. Luego, en el marco de la asignatura “Diseño de
Sistemas de Software”, los autores del presente trabajo accedieron a SocialGraph,
incorporaron funcionalidad, configuraron el versionado del código y elaboraron un informe que
podría considerarse una primera documentación del proyecto de desarrollo. Se considera esta
última etapa como la segunda versión de SocialGraph. Entre las funcionalidades añadidas en la
última versión se destaca: integrar la descarga de emails desde servidor POP o IMAP,
posibilidad de aplicar distintos tipos de filtros para emails descargados, generar grafo a partir de
mails descargados, integración de archivos adjuntos en emails, persistencia y fusión de emails
extraídos de varias cuentas, extensión de la interfaz gráfica de usuario.

5.2.2. Arquitectura actual de SocialGraph

Figura 5.1. Principales componentes de la arquitectura de SocialGraph.

56
Al estudiar la estructura de los paquetes y bibliotecas del proyecto en relación a su
funcionalidad, en principio se puede decir que el sistema no cuenta con un estilo arquitectónico
tipificado, definido. Sin embargo, se pueden identificar a grandes rasgos, como se puede ver en
la figura 5.1, los siguientes componentes y dependencias, con funciones específicas dentro del
sistema:

• Model: Define las clases que hacen al modelo de red social, entre ellas Mail, File
Attachment, Tag y las utilizadas para modelar el grafo AbstractEdge, AbstractVertex,
AttachmentEdge, Attachment, CommunicationEdge, FileVertex, PersonVertex. Puede
notarse que el grafo se compone por dos tipos de vértices, los de personas y los de
archivos y a su vez por dos tipos de arcos, los de comunicación (mensajes entre
personas) y los de adjuntos (archivos entre personas). Esta es una gran característica
del sistema que tendrá un impacto directo en la implementación y escalabilidad del
mismo.

• Graph: Este módulo engloba varias funcionalidades, tanto de procesamiento como de


visualización. El paquete GUI contiene las clases necesarias para proveer una interfaz
gráfica y dibujar el grafo. El paquete filters contiene filtros aplicables a los vértices y
arcos. También se incluye GraphBuilder, utilizado para la construcción del Grafo a partir
de una serie de correos electrónicos.

• NLP Processor: Contiene distintos procesadores de lenguaje natural, organizados bajo


un patrón de diseño tipo strategy. Estos procesadores utilizan aplicaciones y
diccionarios externos para extraer los tópicos principales de cada mensaje, que luego
se convertirán en los Tags asociados a uno o más Mails. Actualmente se está utilizando
la clase OpenNLPProcessor para realizar esta tarea, aunque también se cuenta con un
GateNLPProcessor.

• Profile & Role: La herramienta SocialGraph permite especificar y/o detectar distintos
roles de las personas (vértices) de la organización en base a los tópicos que
frecuentemente involucran a dicha Persona.

• Importer: Provee una interfaz para importar datos de emails desde dumps de bases de
datos. También permite descargar mails directamente de servidores de correo, aunque
este feature no funcionaba correctamente y por eso se ha implementado como parte de
las mejoras al sistema.

El diseño resulta flexible y por lo tanto el sistema SocialGraph es bastante simple de


comprender y reutilizar. Por otra parte, cabe mencionar que la herramienta fue ideada para
funcionar exclusivamente con correos electrónicos y sus archivos adjuntos, por lo tanto no
resulta sencillo extender su funcionalidad para soportar otras fuentes de información.

57
Otra debilidad de la arquitectura es el grado de acoplamiento entre modelo, vista y
controladores. Es decir, no se puede distinguir estos módulos en la implementación. Esta
característica le quita escalabilidad a la plataforma y prácticamente impediría, por ejemplo,
cambiar el motor gráfico por otro o hacer un cambio en las estructuras del modelo. Al dibujar el
grafo, la vista accede directamente a la clase Graph del modelo e implementa allí la lógica de
procesamiento, filtrado, extracción de tags, etc. Sería deseable realizar algunas optimizaciones,
orientando el diseño a una arquitectura de bandas para ganar independencia entre los
componentes del sistema.

En los diagramas de secuencia de las figuras 5.2, 5.3 y 5.4 se puede ver dos de los procesos
más significativos de la aplicación para los objetivos de este trabajo. Estos son la carga de un
grafo desde los archivos generados en la construcción del mismo y la importación de una
fuente de información para la construcción del grafo. En el figura 5.2 se puede ver la
secuencias de invocaciones y las clases involucradas en la carga de un grafo a partir de
correos electrónicos. El sistema carga de distintos archivos, el esquema del grafo y sus
propiedades. Este diagrama es importante ya que hace visible el acoplamiento del modelo del
grafo a la fuente de información de tipo Mail.

Figura 5.2. Diagrama de secuencia de la carga de un grafo en SocialGraph.

En las figuras 5.3 y 5.4 se aprecian los diagramas de secuencia donde se muestra los
métodos invocados y las clases relacionadas a la importación de datos desde un archivo de
backup de correos electrónicos. En la figura 5.3 se puede ver la primera parte de la
importación, que consta del procesamiento del contenido de los correos y su filtrado previo a la
generación del grafo.

En la figura 5.4 se puede ver el acoplamiento del modelo a la fuente de información de tipo
mail en la construcción del grafo. Para cumplir con el objetivo a futuro de soportar varias
fuentes de información, esta será una de las áreas de código sobre la que habrá que focalizar
el refactoring.

58
Figura 5.3. Diagrama de secuencia de importación de datos en SocialGraph (Parte 1).

Figura 5.4. Diagrama de secuencia de importación de datos en SocialGraph (Parte 2).

59
5.2.3. Funcionalidades de SocialGraph
Como se mencionaba en secciones anteriores, SocialGraph fue desarrollado para extraer
datos de cuentas de correo electrónico, mapearlos y visualizarlos en un grafo para luego aplicar
teoría de grafos y técnicas de análisis de redes sociales.

Entre las principales funcionalidades de la herramienta, en la última versión se incluye, para


la construcción del grafo: importación de correos electrónicos a partir de un archivo de respaldo
o desde un servidor de correo, análisis de tópicos de conversación en los mensajes enviados,
definición de distintos roles en la organización y finalmente la visualización del grafo con
distintas opciones de filtrado, coloración y disposición de sus nodos. Una vez realizado el grafo
a partir de los datos de entrada, se pueden aplicar distintos algoritmos como centralidades y
agrupamientos del grafo entre otras estadísticas.

En la figura 5.5, se puede apreciar una captura de pantalla de la herramienta SocialGraph


analizando un grafo de 103 vértices y 245 arcos. Se aprecian distintos tipos de vértices según
los roles de cada persona: architect (rojo), manager (amarillo) y analistas (azul). Los arcos,
tienen valores distintos de acuerdo al grado de relación entre cada persona. La interfaz gráfica
cuenta también con varios parámetros de configuración para personalizar la visualización del
grafo.

Figura 5.5. Analisis de Redes Sociales con SocialGraph

5.2.4. Requerimientos de SocialGraph


El desarrollo y evolución de SocialGraph han sido guiados por la necesidad de contar nuevas
funcionalidades o bien mejorar las existentes. Como se mencionó, dicho proceso no ha sido

60
planeado ni enmarcado en una metodología de desarrollo clara, lo cual ha afectado su calidad,
al menos desde el punto de vista de la modificabilidad. Su aplicación en ambientes de
investigación y desarrollo no pierde vigencia y por lo tanto, como todo sistema de software en
uso, constantemente requiere de mantenimiento y ampliación de sus funcionalidades.

En el caso de este proyecto, los usuarios habituales del sistema, se dedican al análisis de las
relaciones y el intercambio de información entre los miembros de una organización. La
credibilidad de los datos utilizados se basa en los correos electrónicos enviados y recibidos por
esas personas. Pero en general, en un grupo de trabajo, existen otras vías de comunicación e
intercambio de información más allá de los correos. Es así que se plantea la necesidad de
desarrollar nuevos módulos de SocialGraph para dar soporte a más “fuentes de información” a
partir de otras herramientas utilizadas, como por ejemplo, documentación, chat, mensajes de
texto, control de versiones de código, seguimiento de tareas, redes sociales online, etc.

Este nuevo requerimiento pone en evidencia la falta de planificación del sistema y su deuda
técnica, ya que SocialGraph fue pensado y desarrollado desde un principio para soportar sólo
correos electrónicos y en eso se basa su diseño arquitectónico. Es por esto que se decide
encarar un proceso de refactorización de la herramienta que resulte en una mejora cualitativa
en términos de modificabilidad y que permita desarrollar la funcionalidad requerida.

5.3. Aplicación del proceso de refactorización a


SocialGraph
En base a la última versión de SocialGraph, su estado de desarrollo actual y en vistas de
realizar los requerimientos planificados,o se instancia el proceso de refactorización definido en
el capítulo anterior en este sistema. El objetivo de este proceso es mejorar la modificabilidad de
la aplicación para que resulte más flexible implementar los nuevos requerimientos ligados
importación de nuevas fuentes de información. Se espera que como resultado de este proceso,
se mejore la calidad del sistema y el desarrollo de nuevas funcionalidades sea más rápido.

Para empezar con este proceso, se realiza una pre-iteración llamada “Iteración 0”, en donde
se busca hacer un análisis general del sistema entendiendo cómo funciona y cuáles son los
componentes del mismo. De esta manera, se logra tener una visión general de cuáles son los
problemas principales de modificabilidad, realizar una priorización de los mismos y planificar de
forma cómo y cuándo atacar cada problema siguiendo el proceso de refactoring.

5.4. Iteración 0: Preliminar


5.4.1. Análisis general del problema
Como se ha visto, el objetivo principal de refactorizar SocialGraph es obtener una mejora de
calidad en la modificabilidad del sistema. En este sentido, se propone realizar un análisis
general de la última versión del sistema y sus componentes. Naturalmente, se hace énfasis en
los aspectos que tienen que ver con la modificabilidad y la incorporación de nuevas

61
funcionalidades, sabiendo que uno de los requerimientos funcionales proyectados es la
capacidad de generar información a partir de diversas fuentes de datos.

Para esta iteración entran en valor dos variables fundamentales. Primero, cuánta
documentación sobre el sistema se tiene: diagramas UML, especificación de la arquitectura,
etc. Y segundo, el conocimiento que se tenga sobre el código del sistema. Es más fácil para un
desarrollador que trabaja todos los días en el código poder planificar y priorizar qué
refactorizaciones son las más adecuadas. De no ser así se deberá apoyar en mayor medida en
la documentación a la hora de tomar decisiones.

Basados en la experiencia en el desarrollo de SocialGraph y el estudio de los code smells


detectados, se analiza la arquitectura del sistema, su diseño e implementación. También se
generan varios diagramas de secuencia como se puede ver en las figuras 5.2, 5.3 y 5.4 que
nos permiten tener una mayor visión de los workflows de las funcionalidades de interés. Se
pueden identificar varios aspectos que presentan síntomas de malas prácticas, es decir,
secciones del sistema en donde se podrían introducir cambios que mejoren la modificabilidad.
Se llamará a estas “anomalías”. A continuación se describe brevemente cada una de ellas y en
cada iteración cómo fueron abordadas en profundidad a través de escenarios de
modificabilidad en el marco del proceso de refactorización llevado a cabo.

5.4.1.1. Anomalía 1: Estructura del código del proyecto


La primera tiene que ver con la estructura de archivos del proyecto: paquetes, dependencias
y bibliotecas. El proyecto incluye una gran cantidad de bibliotecas, aunque muchas de ellas ya
no son requeridas. A su vez, el proyecto no cuenta con un gestor de dependencias que releve
al desarrollador de esta tarea. Esto impacta en los tiempos de configuración del proyecto y
probablemente en su modificabilidad, ya que, si no hay mucho tiempo para poder realizar una
refactorización es fundamental que el tiempo de setup sea rápido.

5.4.1.2. Anomalia 2: Acoplamiento entre capas


El análisis preliminar evidencia otro problema en la arquitectura como es el alto grado de
acoplamiento entre las distintas “capas” del sistema, es decir, la vista, el modelo y el
controlador se superponen y están ligadas unas con otras, dificultando la posibilidad de realizar
cambios en una capa sin requerir modificaciones en las demás.

5.4.1.3. Anomalía 3: Diseño ligado a correos electrónicos


Luego, se analizan las secciones que tienen relación directa con la importación de datos y
construcción del grafo. En general, se observa un diseño ligado a la fuente de datos de tipo
correo electrónico, limitando la posibilidad de importar otras estructuras de datos que puedan
enriquecer las funcionalidades de SocialGraph. A través de escenarios de modificabilidad se
busca abstraer el diseño para soportar en un futuro, otras fuentes de datos.

5.4.2. Priorización de las anomalías


Siempre se requiere tener conciencia de las prioridades a la hora de hacer una inversión de
tiempo y recursos para introducir cambios al sistema. Se hace una valoración de qué conviene
refactorizar primero para mejorar la calidad del sistema en pos de los objetivos planteados y la

62
funcionalidad que se estima a futuro. Del análisis general del sistema se han identificado cuatro
grandes anomalías de diseño. En esta iteración preliminar se hace una valoración sobre las
mismas pudiendo especificar cuál tiene mayor prioridad. Se utiliza el cuadrante de retrospectiva
de deuda técnica para cumplir con esta tarea.

En la evaluación de deuda técnica se decidió refactorizar primero la Anomalía 1. Como se


puede ver en la figura 5.6, no tiene una alta complejidad pero sí lleva bastante tiempo y
permite, durante la instanciación del proceso, estudiar mejor la arquitectura e implementación
del sistema. Si bien por su ubicación en el cuadrante de deuda técnica tiene un bajo impacto en
los objetivos de este refactoring, permite que el proyecto sea más fácil de manipular a futuro
por los desarrolladores.

Como se puede ver en la figura 5.6, las Anomalías 2 y 3 se colocaron en el cuadrante “Alto
esfuerzo / Alto impacto” que corresponde con la deuda que es compleja de refactorizar pero por
la cual se está pagando un alto interés. Son cambios que involucran una modificación en la
arquitectura del sistema y por lo tanto afectan varios componentes, implicando mayor riesgo
ante el cambio.

Si bien la Anomalía 2 está directamente ligada con la modificabilidad del sistema, al evaluarla
en el cuadrante de deuda técnica, se observa que para realizar una mejora significativa, es
necesario un gran inversión de tiempo de desarrollo. Por otra parte, el nivel de incidencia en el
requerimiento de importación de nuevas fuentes de información, principal motivación del
refactoring, es relativamente bajo. Por lo tanto, se decide no abordar este problema en el actual
proceso de refactorización aunque en algún momento se tendrá que pagar la deuda asociada a
esta anomalía.

En cambio, la Anomalía 3 tiene un impacto directo en los objetivos del proyecto.


Refactorizando esta anomalía se cumplirá en gran medida con la deuda que se está pagando y
se puede aumentar significativamente la modificabilidad del sistema. Es decir, si se abstrae la
aplicación para que su diseño no dependa de una única fuente de información, el sistema
quedará preparado para incorporar fácilmente otras fuentes.

Priorización de cuadrante de deuda técnica:

• Anomalía 1: Estructura del código del proyecto.

• Anomalía 3 Diseño ligado a correos electrónicos.

• Anomalía 2: Acoplamiento entre capas.

63
Figura 5.6. Cuadrante de deuda técnica de las anomalías detectadas.

5.4.3. Cobertura de test de la aplicación


A la hora de refactorizar es una precondición esencial tener casos de test sólidos. Por más
que durante el proceso de refactoring se trate de localizar los cambios y minimizar el efecto
dominó, es probable que se introduzcan errores. El sistema actualmente tiene una cobertura
nula de tests. Se puede identificar un gran número de clases con un método main
implementado a modo de test de funcionalidad. También existen muchas sentencias que
imprimen información por pantalla con el fin de verificar su correcto funcionamiento.

Estas son malas prácticas de desarrollo de software ya que a medida que crece el sistema
se vuelve insostenible testear todo ante cada cambio. Ya que se tendría que ir ejecutando clase
por clase, esperar que se muestre el resultado por pantalla o, en el mejor de los casos, en un
archivo de log, para después verificar si el resultado de la ejecución se corresponde con un
valor esperado. A largo plazo, este esquema es insostenible.

64
Por lo tanto, antes de implementar cada uno de los escenarios es importante chequear la
cobertura de tests. Si no existe un caso de test que le dé cobertura al área de código afectada
por el refactoring, se usará la biblioteca JUnit para automatizar casos de tests unitarios sobre el
código a refactorizar. De esta forma, se validará que el nuevo código cumpla con los
requerimientos anteriores y que no se ha alterado su funcionalidad observable después de una
modificación.

5.4.4. Observaciones de la Iteración 0


La iteración 0 no tiene especificadas todas las etapas ya que es un primer análisis y
diagnóstico del estado de la implementación y diseño del sistema. En caso de que haya una
documentación formal previa de la arquitectura del sistema, esta iteración puede no ser
necesaria o se podrá hacer referencia a tal documento. El objetivo de esta iteración preliminar
es tener un primer punto de vista de cómo está construido el sistema y que áreas del mismo
tienen problemas de diseño, o dónde se evidencia una mayor cantidad de smells. La salida de
esta iteración es una especificación general de las anomalías que afectan la modificabilidad,
para luego a medida que se itere por el proceso se profundice en el análisis en particular de
cada anomalía y se especifiquen los escenarios de modificabilidad a realizar para mejorar la
calidad del sistema.

5.5. Iteración 1: Re-estructuración del Proyecto


5.5.1. Etapa 1: Análisis e identificación del problema
La Anomalía 1 del sistema presenta el primer problema de calidad a resolver que es el de
organizar la estructura del proyecto de forma ágil y sencilla. En el desarrollo de SocialGraph
participan muchas personas con diferentes roles y con distintos tiempos. Normalmente no
pertenecen a un mismo grupo de trabajo. La descarga y configuración del proyecto debe ser lo
más rápida y amigable posible. Los desarrolladores que trabajan en SocialGraph pierden
mucho tiempo configurando el proyecto para poder trabajar o estudiar el código.

5.5.1.1. Documentación del proyecto.


Al no tener documentación del proyecto, el desarrollador no sabe a ciencia cierta qué
versiones se usan de cada biblioteca y si todas las dependencias son requeridas en la última
versión del código. Para compilar y ejecutar SocialGraph es necesario resolver todas las
inconsistencias en las rutas de las dependencias Java y en caso de que el siguiente
desarrollador no tenga la misma estructura interna en sus sistema de archivos o utilice un
sistema operativo diferente, tendrá que realizar este trabajo nuevamente. Durante el análisis se
genera nueva documentación e información que en próximos refactorings permitirá tener una
visión de cómo está estructurado el sistema y qué dependencias se usan para cada área de
código.

5.5.1.2. Detección de code smells.


Para tener una noción de los principales síntomas de malas prácticas de implementación y
diseño en el sistema se hace una primera detección de code smells utilizando la herramienta
JSpIRIT [JSpIRIT]. De esta manera, se detectan 150 code smells en todo el sistema. En la

65
tabla 5.1 se muestra la distribución de code smells según su tipo. Se ve que los tipos Feature
Envy y Dispersed Coupling concentrán mas del 80% de los smells.

Tipo code smell Cantidad Porcentaje

Shotgun Surgery 13 8,67

Refused Parent Bequest 2 1,33

Intensive Coupling 3 2,00

God Class 5 3,33

Feature Envy 72 48,00

Dispersed Coupling 49 32,67

Data Class 2 1,33

Brain Method 4 2,67

Total 150 100,00

Tabla 5.1. Distribución de tipos de code smells en todo el sistema.

Componente Cantidad smells Porcentaje

Importer 43 28,67

GUI 36 24,00

Graph 31 20,67

Model 15 10,00

WordCram 12 8,00

Otros 13 8,66

Total 150 100,00

Tabla 5.2. Distribución de tipos de code smells en todo el sistema.

5.5.1.3. Identificación de los problemas en el diseño.


El principal problema que se trata en esta iteración es el manejo de dependencias del
proyecto, se necesita una forma ágil y rapida para solucionar este problema. La configuración
del proyecto en el ambiente de desarrollo debe ser fácil y eficiente para que el programador
tarde la menos cantidad de tiempo en esta tarea.

66
El proyecto cuenta con código de terceros que no se sabe si es necesario ni si fue extendido
o modificado para algún propósito especial. En particular, los frameworks a analizar y
reestructurar son WordCram [WordCram] y GATE [GATE], utilizados para el procesamiento de
textos en la aplicación.

5.5.2. Etapa 2: Planteo de solución y definición de escenarios


5.5.2.1. Definir refactorings a realizar
Para la reestructuración del proyecto, dados los problemas que se identificaron en la etapa
anterior, se decide dividir la solución del problema en tres refactorings. Dado que se necesita
una forma ágil y eficiente de manejo de dependencias se decide utilizar Maven y en el proceso
verificar cuáles dependencias se están utilizando y cuáles no. Se reestructurará y documentará
todo el código y dependencias verificando el uso de las aplicaciones WordCram y GATE-5.2.1.

Los cambios que se proponen son:

• Mavenizar el proyecto.

• Reestructurar dependencia con aplicación WordCram.

• Reestructurar dependencia con aplicación GATE-5.2.1.

5.5.2.2. Especificar Escenarios de Modificabilidad


Según los refactorings a realizar especificamos los escenarios de modificabilidad propuestos
para la reestructuración del proyecto.

5.5.2.2.1. Escenario 1.1. Mavenizar Proyecto


Este caso de estudio presenta un gran problema a la hora de configurar la aplicación ya que
para comenzar a programar o utilizar el código se puede perder mucho tiempo en configurar el
proyecto. Hay que entender cómo configurar la herramienta, qué bibliotecas utiliza y cómo
obtenerlas si no fueron entregadas junto con el código y, por último, recomponer los java paths
de las mismas. Por eso, se decide utilizar Maven para organizar las dependencias del proyecto
y la configuración. Utilizando Maven, un nuevo desarrollador puede incorporarse al proyecto a
través del repositorio de código, obteniendo las bibliotecas de manera transparente ya que los
repositorios de librerías son públicos. De esta manera, se reduce el tiempo de startup para que
el programador aboque su tiempo a desarrollar y no a configurar el proyecto. En este proceso
también se analiza si las dependencias se están usando realmente junto con su número de
versión.

67
Porción del Valores
Escenario

Fuente del Estímulo El cambio debe ser realizado por el desarrollador

Estímulo Reorganizar las dependencias del proyecto usando Maven para tener
mayor facilidad de configuración del ambiente.

Artefacto Estructura del proyecto

Ambiente Implementación.

Respuesta Realiza modificaciones sin afectar funcionalidades del sistema.

Medida de la Tiempo de realización: 12 horas/hombre.


Respuesta Reducción del tiempo de configuración del ambiente de desarrollo
para una nueva terminal del trabajo.
Tabla 5.3. Especificación de escenario 1.1: Mavenizar Proyecto

El proyecto viene acompañado de una cantidad importante de dependencias JAR para su


funcionamiento, de la cuales no se sabe a ciencia cierta cuáles se están usando realmente y
cuáles han quedado obsoletas.

Dado este problema y el overhead en el tiempo de configuración, se decide usar Maven para
la configuración del proyecto y así tener un mejor manejo de las dependencias del proyecto y,
al mismo tiempo, “limpiar” todas las dependencias que no sean necesarias para el
funcionamiento del proyecto.

5.5.2.2.2. Escenario 1.2. Reestructurar dependencia con aplicación


WordCram
Dentro del proyecto se tienen las clases del paquete WordCram, una API que podría estar
fuera del proyecto como un JAR y agregarla como una dependencia de Maven.

Porción del Valores


Escenario

Fuente del Estímulo El cambio debe ser realizado por el desarrollador

Estímulo Reorganizar las dependencia WordCram del proyecto para tener


mayor facilidad de configuración del ambiente.

Artefacto Estructura del proyecto

Ambiente Implementación.

Respuesta Realiza modificaciones sin afectar funcionalidades del sistema.

Medida de la Tiempo de realización: 6 horas/hombre.


Respuesta Reducción del tiempo de configuración del ambiente de desarrollo

68
para una nueva terminal del trabajo.
Tabla 5.4. Especificación escenario 1.2: Reestructurar dependencia con aplicación WordCram Proyecto.

La biblioteca WordCram realiza el procesamiento de análisis de texto y detección de


colisiones que permite generar nubes de palabras de forma sencilla. En SocialGraph es
utilizada para generar nubes de tags a partir de los contenido intercambiados entre usuarios.

5.5.2.2.3. Escenario 1.3. Reestructurar dependencia con aplicación GATE


GATE es un software de código abierto capaz de resolver diferentes problemas de
procesamiento de texto. Cuenta con una comunidad madura y extensa de desarrolladores,
usuarios, educadores, estudiantes y científicos. Es un proceso definido y repetible para crear
flujos de trabajo de procesamiento de texto de forma robusta y fácil de mantener. En
SocialGraph es utilizado para analizar los contenidos de tipo texto intercambiados entre los
usuarios.

Al igual que en los escenarios anteriores, se analizan los componentes del sistema
relacionados a GATE y sus dependencias, pasando estas a Maven si son requeridas.

Porción del Valores


Escenario

Fuente del Estímulo El cambio debe ser realizado por el desarrollador

Estímulo Reorganizar las dependencia GATE-5.2.1 del proyecto para tener


mayor facilidad de configuración del ambiente.

Artefacto Estructura del proyecto

Ambiente Implementación.

Respuesta Realiza modificaciones sin afectar funcionalidades del sistema.

Medida de la Tiempo de realización: 6 horas/hombre.


Respuesta Reducción del tiempo de configuración del ambiente de desarrollo
para una nueva terminal del trabajo.
Tabla 5.5. Especificación escenario 1.3: Reestructurar dependencia con aplicación GATE.

5.5.2.3. Agregar al backlog


Los escenarios 1.1, 1.2 y 1.3 son agregados al backlog de tareas a realizar por los
desarrolladores. En la próxima etapa se define la priorización del mismo.

5.5.3. Etapa 3: Priorización de backlog


Para priorizar cada escenario del backlog a partir del grado de impacto en el sistema y el
esfuerzo requerido para su implementación, se busca la opinión y el consenso entre los
desarrolladores, stakeholders y demás miembros del proyecto. A continuación, se presenta una
síntesis de ese análisis.

69
5.5.3.1. Nivel de Impacto
El escenario 1.1 tiene mayor prioridad ya que reduce los tiempos para que un desarrollador
configure y se ponga a trabajar en el proyecto con el plus de que optimiza el manejo de
dependencias y el versionado de la aplicación, lo cual implica un alto impacto en la
modificabilidad. Los escenarios 1.2 y 1.3 no tienen un alto impacto para la incorporación de
nuevas fuentes de información, que es la motivación de este trabajo, pero sí permiten estudiar
el sistema en mayor detalle y aplicar la refactorización pertinente hace el sistema más legible,
siguiendo la premisa de que un código fácil de entender es un código fácil de modificar.

5.5.3.2. Nivel de Esfuerzo


El nivel de esfuerzo varía según las capacidades y el conocimiento que tengan del sistema
los desarrolladores. Esta variable se puede medir a partir de los tiempos requeridos para
implementar cada escenario. Según estos tiempos estimados, el orden de escenarios es el
siguiente:

• Escenario 1.1 (12 horas)


• Escenario 1.2 (6 horas)
• Escenario 1.3 (6 horas)

5.5.3.3. Ubicación en cuadrantes


A partir de la valoración de los escenarios, se los ubica en el cuadrante de deuda técnica que
se muestra en la figura 5.7.

Figura 5.7. Cuadrante de deuda técnica para los escenarios de la iteración 1.

70
Priorización de cuadrante de deuda técnica :

● Escenario 1.1: Mavenizar proyecto.


● Escenario 1.2: Reestructurar dependencia con aplicación WordCram.
● Escenario 1.3: Reestructurar dependencia con aplicación GATE.

5.5.4. Etapa 4: Implementar Escenarios


5.5.4.1. Escenario 1.1: Mavenizar proyecto
5.5.4.1.1. Verificar cobertura de test
Para garantizar que las modificaciones se realizan sin afectar las funcionalidades del sistema
para este escenario se crea un caso de test de una generación de un grafo a partir de una
fuente de correos electrónicos hasta su presentación por pantalla, verificando todos los
parámetros que permitan verificar que el escenario realiza las modificaciones sin afectar las
funcionalidades observables del sistema.

5.5.4.1.2. Implementar Escenario


El proceso es ir analizando dónde se utilizan las bibliotecas y en caso de ser necesarias
agregarlas al archivo de configuración de Maven y en caso de no ser utilizadas eliminar la
dependencia. Para esto primero se borran del Java build path del proyecto y se ve cuáles son
requeridas y sólo estas se agregan al archivo de configuración de Maven. En la tabla 5.6 se
listan los jars organizados por biblioteca padre, la acción pertinente realizada y un detalle de la
misma.

Biblioteca Jars Acción Detalle

jfrechart jfreechart-1.0.14- Mavenizar Solo se agrega esta


experimental.jar, jcommon- dependencia a Maven. Las
1.0.17.jar, demas no son requeridas.
<dependency>
gnujaxp.jar, <groupId>org.jfree</groupId>
<artifactId>jfreechart</artifactId>
iText-2.1.5.jar, <version>1.0.14</version>
</dependency>
swtgraphics2d.jar,
servlet.jar,
jfreechart-1.0.14-swt.jar.

Apache poi poi-3.7-20101029.jar, Mavenizar Se agregan a Maven las


poi-scratchpad-3.7- siguientes dependencias:
<dependency>
20101029.jar <groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.7</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-
scratchpad</artifactId>
<version>3.7</version>
</dependency>

71
Jung jung-3d-2.0.1.jar Mavenizar Se agregan a Maven las
j3d-core-1.3.1.jar siguientes dependencias:
<dependency>
<groupId>net.sf.jung</groupId>
<artifactId>jung-3d</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>java3d</groupId>
<artifactId>j3d-core</artifactId>
<version>1.3.1</version>
</dependency>

jung-samples-2.0.1.jar Mavenizar Tira error de dependencias


por lo que se agrega a Maven,
pero las clases dependientes
son de test candidatas a
revisar para borrarlas si no se
necesitan. La clase es
BiComponentTest usando la
clase ClusteringDemo
Agregamos a maven:
<dependency>
<groupId>net.sf.jung</groupId>
<artifactId>jung-
samples</artifactId>
<version>2.0.1</version>
</dependency>

jung-3d-demos-2.0.1.jar, Eliminar Todas estas dependencias no


jung-jai-samples-2.0.1.jar, son necesarias. Son borradas
stax-api-1.0.1.jar, del Java build Path y no se
agregan a las dependencias
wstx-asl-3.2.6.jar,
maven.
vecmath-1.3.1.jar,
concurrent-1.3.4.jar,
jung-visualization-2.0.1.jar,
jung-jai-2.0.1.jar,
jung-io-2.0.1.jar,
jung-graph-impl-2.0.1.jar,
jung-algorithms-2.0.1.jar,
jung-api-2.0.1.jar,
colt-1.2.0.jar,
collections-generic-4.01.jar.

Mail commons-io-2.0.1.jar, Mavenizar Se agregan a Maven las


mail-1.4.4.jar. siguientes dependencias:
<dependency>
<groupId>commons-
io</groupId>
<artifactId>commons-io</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.4</version>

72
</dependency>

commons-codec-1.4.jar Eliminar Todas estas dependencias no


son necesarias. Son borradas
del Java build Path y no se
agregan a las dependencias
maven.

lemmer anna-1.jar, Eliminar Todas estas dependencias no


anna-2.jar son necesarias. Son borradas
del Java build Path y no se
agregan a las dependencias
maven.

anna.jar Mavenizar Esta dependencia afecta la


clase TagFinder, el
lemmatizador de palabras.
Corriendo el test encontramos
un problema con el
lemmatizador, por lo que la
dependencia de maven
<dependency>
<groupId>com.googlecode.mate-
tools</groupId>
<artifactId>anna</artifactId>
<version>3.5</version>
</dependency>
no parece funcionar
correctamente. Se debe usar
los jar que venían con el
proyecto ya que la versión 3.5
es la unica version que hay en
el repositorio de Maven y la
que se utiliza en el proyecto
no tiene identificada la
versión. Se agregan los jar al
repo local de Maven y se
manejan las dependencias
desde ahí.

nlp libstemmer.jar, Eliminar Todas estas dependencias no


jsoup-1.4.1.jar son necesarias. Son borradas
del Java build Path y no se
agregan a las dependencias
maven.

nlp/opennl ant.jar Mavenizar Se agregan a Maven las


p siguientes dependencias:
<dependency>

73
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.9.4</version>
</dependency>

maxent-2.5.2.jar, Mavenizar Las dependencias de opennlp


deben ser agregadas
manualmente al repositorio
local de maven, quedan de la
siguiente forma:
<dependency>
<groupId>org.apache.opennlp</groupI
d>
<artifactId>opennlp-
maxent</artifactId>
<version>2.5.3</version>
</dependency>

Pdf PDFBox-0.7.3.jar Mavenizar Se agregan a Maven las


siguientes dependencias que
afectan la clase
DocumentReader:
<dependency>
<groupId>pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>0.7.3</version>
</dependency>

bcmail-jdk14-132.jar, bcprov- Eliminar Todas estas dependencias no


jdk14-132.jar, checkstyle-all- son necesarias. Son borradas
4.2.jar, del Java build Path y no se
agregan a las dependencias
FontBox-0.1.0-dev.jar
maven.

Bibliotecas opencsv- Mavenizar Se agregan a Maven las


Sueltas 2.3.jar,DateChooser.jar siguientes dependencias:
<dependency>
<groupId>net.sf.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>2.3</version>
</dependency>
Las dependencias de
DateChooser deben ser
agregadas manualmente al
repositorio local de maven,
quedan de la siguiente forma:
<dependency>
<groupId>DateChooser</groupId>
<artifactId>DateChooser</artifactId
>
<version>1.0</version>
</dependency>

74
powerlaws-0.0.1.jar, log4j- Eliminar La dependencia powerlaws-
1.2.16.jar, 0.0.1 afecta la clase
commons-logging-1.1.jar, TestPowerLaw.java en
forms-1.3.0.jar edu.isistan.socialgraph/src/ed
u/isistan/socialgraph/graph/sta
ts. Esta clase no llama a
ninguna otra ni es utilizada. Se
decide borrar la clase y la
dependencia del Java build
Path y no se agrega a las
dependencias maven.

Todas las demás


dependencias no son
necesarias. Son borradas del
Java build Path y no se
agregan a las dependencias
Maven.

wmw jsc.jar, Eliminar La dependencia jsc.jar afecta


commons-math3-3.1.1.jar la clase TestWMW. Esta clase
no llama a ninguna otra ni es
utilizada. Se decide borrar la
clase y la dependencia del
Java build Path y no se
agrega a las dependencias
maven.

Se decide borrar la
dependencia commons-
math3-3.1.1.jar del Java build
Path y no se agrega a las
dependencias maven.

Tabla 5.6 Refactorizaciones escenario 1.1: Mavenizar Proyecto

5.5.4.1.3. Ejecutar casos de test


Se ejecutaron los casos de test correctamente, verificando la no introducción de cambios en
la funcionalidad del sistema.

El test se ejecuta luego de cada una de las reestructuraciones en la configuración del


proyecto indicadas en la tabla 5.6. Esto se realiza para que entre cada una de las
modificaciones no se arrastre un error en la funcionalidad del sistema, ya que en caso de que la
versión de las bibliotecas heredadas en el proyecto no coincidan con las incluidas en Maven

75
puede que no arroje un error de compilación pero si en el funcionamiento interno de la
biblioteca, alterando de esta forma el comportamiento observable de la aplicación.

5.5.4.2. Escenario 1.2: Reestructurar dependencia con aplicación


WordCram Proyecto
5.5.4.2.1. Verificar cobertura de test
Este refactoring tiene cobertura de test, por lo que no se crea un nuevo test específico para
este escenario si no que se utiliza el test creado en el escenario 1.1.

5.5.4.2.2. Implementar Escenario


Para reestructurar la dependencia WordCram en SocialGraph, en la línea en que se viene
trabajando, se discriminan los paquetes en una biblioteca aparte ya que estas clases no han
sido modificadas ni alteradas. Así, se accede a la biblioteca de WordCram como una
dependencia Maven más.

Con WordCram se presenta el problema de que esta biblioteca no tiene repositorio en


Maven, es decir, no da soporte para manejar la biblioteca ni sus versiones desde Maven. Esto
se soluciona descargando la última versión del código del proyecto WordCram desde su
página. Con el mismo se realiza un proyecto Java en Eclipse donde se mantendrá a futuro la
versión de WordCram que se utiliza en SocialGraph. Se agrega el proyecto al repositorio git de
este trabajo final a fin de contar con una copia disponible en caso de que el link del proyecto no
esté disponible en el futuro.

Se instala el Jar del proyecto WordCram en el repositorio Maven local. Esto tiene el problema
de que cada vez que alguien descarga el proyecto se tendrá que hacer lo mismo en su
ambiente y la tarea resulta más compleja.

En el pom se especifica cuáles son las dependencias que deben ser previamente instaladas
en el repositorio local o en caso de un futuro en un repositorio Maven propio. Para realizar esto
sólo hay que tener una copia de una carpeta, la cual será subida al servidor git con las librerías
a instalar en el repositorio de Maven local.

5.5.4.2.3. Ejecutar casos de test


Se ejecutaron los casos de test correctamente, verificando la no introducción de cambios en
la funcionalidad del sistema.

5.5.4.3. Escenario 1.3. Reestructurar dependencia con aplicación


GATE
5.5.4.3.1. Verificar cobertura de test
Este refactoring tiene cobertura de test, por lo que no se crea un nuevo test específico para
este escenario si no que se utiliza el test creado en el escenario 1.1.

76
5.5.4.3.2. Implementar Escenario
Se analiza qué dependencias y clases son utilizadas y requeridas para el funcionamiento del
sistema que tengan relación con la aplicación GATE. En caso de que las dependencias se
requeridas para el normal funcionamiento del sistema deben ser eliminadas del Java Build Path
e incorporadas al archivo de configuración de Maven. En caso contrario, sólo serán eliminadas
del Java Build Path.

Biblioteca Jars Acción Detalle

nlp/GATE jdom.jar, ant.jar, jakarta-ant- Eliminar Todas estas dependencias no


optional.jar, jwnl-1.3.3.jar, son necesarias. Son borradas
maxent-2.5.2.jar, nekohtml- del Java build Path y no se
agregan a las dependencias
1.9.8+2039483.jar, xstream-
maven. Algunas bibliotecas
1.2.jar, fueron agregadas a maven
xercesImpl.jar,ontotext.jar, anteriormente por lo que no es
gate-compiler-jdt.jar, gate- necesario agregarlas.
asm.jar

commons-lang-2.4.jar, trove.jar Mavenizar Se agregan a Maven las


siguientes dependencias:
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-
lang</artifactId>
<version>2.4</version>
</dependency>

<dependency>
<groupId>trove</groupId>
<artifactId>trove</artifactId>
<version>1.0.2</version>
</dependency>
El resto de las dependencias
se incorporan a maven con:
<dependency>
<groupId>uk.ac.gate</groupId>
<artifactId>gate-core</artifactId>
<version>5.2.1</version>
</dependency

opennlp-tools-1.4.3.jar Mavenizar Se agrega la dependencia a


maven, no existe la versión
1.4.3 en el repositorio de
maven empiezan desde la
1.5.1 por lo tanto se tiene que
instalar la versión
manualmente es nuestro
servidor local. Agregamos la
dependencia como:
<dependency>

77
<groupId>opennlp</groupId>
<artifactId>opennlp-
tools</artifactId>
<version>1.4.3</version>
</dependency>

Tabla 5.7. Refactorizaciones escenario 1.3: reestructurar dependencia con aplicación GATE.

5.5.4.3.3. Ejecutar casos de test


Se ejecutaron los casos de test correctamente, verificando la no introducción de cambios en
la funcionalidad del sistema.

5.5.4.3.4. Notas sobre la implementación de los escenarios


Nota técnica sobre GATE: Del análisis de componentes relacionados con GATE y sus
dependencias, se encuentra que se usa la clase GateNLPProcessor para procesar el texto,
particularmente la lematización de los tokens. Actualmente no se está utilizando el
procesamiento con la biblioteca GATE sino que se utiliza OpenNLPProcessor, en la cual los jar
asociados ya se movieron a Maven y sólo quedan en la carpeta lib los diccionarios que utiliza
para realizar este trabajo.

OPENNLP_PROCESSORS_DIR + "SpanishSent.bin.gz";

OPENNLP_PROCESSORS_DIR + "SpanishTok.bin.gz";

OPENNLP_PROCESSORS_DIR + "SpanishPOS.bin.gz";

Se podría quitar todo lo referido a GATE ya que no está siendo utilizado, sin embargo, por
tratarse de una aplicación en constante desarrollo, podría tener que estar preparada para
utilizar GATE en vez de OpenNLPProcessor que actualmente está cumpliendo con la tarea del
procesamiento del texto.

Ante la complejidad que suma tener que instalar todas las dependencias de terceros que no
están en un repositorio de Maven en la nube en un servidor local, se ve que aunque se
resuelve el manejo de versiones de las dependencias se sigue arrastrando el problema de
tener que configurar el entorno cada vez que un nuevo desarrollador tiene que trabajar en el
proyecto.

Por esta razón, se decide agregar todas las dependencias de terceros que no están
disponibles en un repositorio de Maven en la nube a un servidor Maven propio donde se
centraliza las versiones utilizadas en el proyecto sin repositorio en la nube, el mismo es público
por lo que cualquiera que descargue el proyecto podrá, sin problemas, desde el pom construir
el proyecto y descargar las dependencias requeridas.

En el pom.xml se agregan las siguiente líneas:

78
<!-- Maven Repository on Git-BitBucket -->

<repositories>

<repository>

<id>maven-repo</id>

<layout>default</layout>

<url>https://bitbucket.org/facundoklaver/maven-
repo/raw/master/repository</url>

</repository>

</repositories>

5.5.5. Etapa 5: Medición de Resultados


5.5.5.1. Análisis basado en métricas y documentación.
Se pasó todo el proyecto a Maven removiendo las librerías que no se utilizaban. Después de
este refactoring, un desarrollador descarga del repositorio Git sólo el código del proyecto y
archivos de configuración y no tiene que manipular bibliotecas que por su cantidad ocupan un
gran tamaño. Originalmente una descarga del proyecto ocupaba 108MB, después de las
refactorizaciones, el proyecto requiere 81MB. Es decir, se redujo un 25% el espacio ocupado
en disco, lo cual se corresponde con una baja en la cantidad de tiempo que se necesita para
setear el proyecto, mientras que se tiene una mejor organización del código y una óptima
gestión de dependencias.

Antes de realizar estas refactorizaciones, el análisis con la herramienta JSpIRIT arrojaba un


total de 150 code smells en SocialGraph. Luego de esta iteración, esa cantidad se redujo a
132. Esta baja se debe principalmente a las clases de WordCram que se sacaron a una
biblioteca en Maven. Esta biblioteca es independiente a SocialGraph y su modificabilidad no es
una prioridad.

5.5.5.2. Análisis subjetivo y valoración del grupo de desarrollo.


Después de la reestructuración del proyecto no sólo se ha mejorado la organización del
código sino que se ha adquirido un mayor grado de entendimiento del mismo. Al mejorar su
organización, a futuro lo hace mas facil de estudiar, por lo tanto, de entender el funcionamiento
del mismo y en consecuencia su modificabilidad. Este tipo de reestructuración permite también
a los desarrolladores que van a trabajar a futuro con la aplicación y no tenían conocimiento
previo del código entender cuáles son las diferentes áreas del sistema y qué dependencias se
utilizan en el. Con esta reestructuración se disminuye los tiempos de configuración y por lo
tanto los desarrolladores pueden dedicarle más tiempo a trabajar en el sistema.

79
5.5.5.3. Validación de escenarios.
A partir de las refactorizaciones implementadas en estos tres escenarios se han obtenido
varias mejoras en la calidad del proceso de desarrollo. A través de las mismas se logra una
reducción del tiempo de configuración del ambiente de desarrollo para una nueva terminal del
trabajo. De esta manera el proceso de desarrollo resulta más eficiente ya que se disminuye el
overhead producido por en el tiempo de startup.

5.5.6. Etapa 6: Etapa 6: Evaluación.


Como se ha visto en la etapa anterior, los resultados de la implementación de esta iteración
se evalúan de manera positiva. Se ha atacado la Anomalía 1 y mejorado la calidad del sistema
a partir de tres escenarios, con una suma de 24 horas de desarrollo.

El esfuerzo, en término de horas de trabajo, es relativamente bajo, pero el impacto en los


objetivos del proceso también lo es. Como se vió en la Iteración 0, es la Anomalía 3 la principal
causa de los problemas de modificabilidad que motivan este proceso de refactorización.

Es por esto que, con un proyecto reestructurado y con algo más de conocimiento del
sistema, es pertinente realizar una Iteración 2 del proceso para atacar los problemas de
modificabilidad a nivel de arquitectura, diseño y/o implementación.

5.6. Iteración 2: Diseño ligado a correos electrónicos.


5.6.1. Etapa 1: Análisis e identificación del problema
Una vez realizado el análisis preliminar y una reestructuración del proyecto, el equipo está en
condiciones de abordar el problema principal de este proceso de refactorización: realizar una
serie de cambios a nivel de código que impliquen una mejora de la modificabilidad y permiten
incorporar fácilmente nuevas fuentes de datos a SocialGraph.

5.6.1.1. Documentación del proyecto


En esta iteración del proceso, se deben realizar refactorizaciones que eleven el nivel de
abstracción de la implementación y diseño del caso de estudio. En la figura 5.X, se observa un
diagrama de componentes de SocialGraph, entre ellos para esta iteración se destacan model,
graph e importer. Estos tres componentes nuclean la funcionalidad y estructuras que permiten
modelar, construir el grafo e importar datos respectivamente. Actualmente, estos componentes
se encuentran ligados de manera exclusiva a los correos electrónicos de los usuarios, mientras
que la intención es que la herramienta opere también con otras estructuras de datos. Se espera
que la abstracción de cada una de estas secciones del sistema, mejore la calidad del software
en cuanto a su modificabilidad.

80
Figura 5.8. Diagrama de Clases antes de implementar Escenario 3.1.1.

Si bien la documentación del proyecto es escasa, se han realizado diagramas que permiten
comprender mejor el grado de acoplamiento existente entre el modelo de datos y las
funcionalidad de SocialGraph. Por ejemplo, en la figura 5.8, se ve cómo la construcción del
grafo está atada a la clase Mail. En general, la construcción, la importación y las principales
clases del modelo están vinculadas a la clase Mail, lo cual evidencia un fuerte acoplamiento y
la necesidad de realizar cambios en el diseño que permitan pensar el grafo como una
estructura independiente de los correos electrónicos.

81
5.6.1.2. Detección de code smells
Al momento de entrar en la iteración, se detecta un total de 132 code smells en el código del
sistema. En la tabla 5.8 se muestra la distribución según tipos de smell. Se observa que tres
tipos concentran 119 smells, es decir, el 90,15% del total de smells. A su vez, según el
agrupamiento de tipos, 69 (52,27%) son anomalías de identidad, 61 (46,21%) anomalías de
colaboración y 2 (1,52%) anomalías de clasificación. Estos datos indican qué tipos de
anomalías presenta el sistema en su estado actual, pero también es deseable conocer dónde
se localizan estos síntomas.

En la tabla 5.9 se muestra la distribución de smells por cada componente del sistema.
Descartando la interfaz gráfica del sistema, que no es de interés en esta iteración del proceso,
se ve que la gran mayoría de los smells se aloja en tres componentes: importer, graph y model.
Allí se acumulan 83 smells (62,87%). Es decir, esto confirma la necesidad de aplicar
refactorizaciones en estas áreas del sistema, responsables de la importación, la construcción
del grafo y el modelado de estructuras respectivamente.

Tipo code smell Cantidad Porcentaje

Feature Envy 61 46,21

Dispersed Coupling 45 34,09

Shotgun Surgery 13 9,85

God Class 4 3,03

Intensive Coupling 3 2,27

Brain Method 3 2,27

Refused Parent Bequest 2 1,52

Data Class 1 0,76

Total 132 100,00

Tabla 5.8. Distribución de tipos de code smells en todo el sistema.

Componente Cantidad smells Porcentaje

Importer 43 32,58

GUI 36 27,27

Graph 25 18,94

Model 15 11,36

Otros 13 9,85

Total 132 100,00

Tabla 5.9. Distribución de tipos de code smells en todo el sistema.

82
5.6.1.3. Identificación de los problemas en el diseño
Esta etapa de refactorización, se realiza una abstracción del modelo, que hasta el momento
está instanciado exclusivamente en correos electrónicos. Se observa que muchas de las clases
ligadas a la importación de datos y la construcción del grafo están íntimamente ligadas a la
clase Mail, que modela cada correo electrónico y por eso es necesario elevar el nivel de
abstracción, mejorando la calidad del diseño y la modificabilidad del sistema.

Los problemas de diseño detectados al analizar las relaciones entre clases, jerarquías e
interfaces muestran siempre el mismo síntoma: el sistema fue desarrollado para operar
exclusivamente con correos electrónicos. Por lo tanto, las refactorizaciones se deben centrar en
elevar el nivel de abstracción para soportar, en un futuro, nuevas fuentes de información que
puedan complementarse con los correos electrónicos.

5.6.2. Etapa 2: Planteo de solución y definición de escenarios


5.6.2.1. Definir refactorings a realizar
La solución de este problema de acoplamiento se resuelve mediante la abstracción del
modelo y el diseño de la aplicación. El objetivo es desacoplar ciertas clases que intervienen en
el proceso de importación, construcción y visualización del grafo para que ya no dependan de
los correos electrónicos (representados por la clase Mail). En cada uno de los escenarios que
forman parte de esta iteración se aplican distintas tácticas para mejorar la abstracción del
diseño y así la modificabilidad del sistema.

Los cambios que se proponen son:

● Implementación del patrón strategy para la construcción del grafo.


● Abstracción de clase Mail con una interfaz CommunicationLine que sea
implementada por la clase Mail y por otras clases también.
● Utilización de CommunicationLine en todas la clases del modelo, reemplazando a la
clase Mail.
● Utilización de CommunicationLine en áreas del sistema donde se invoque a Mail pero
se considere necesaria una abstracción.
● Refactorización y reorganización de las clases y paquetes de importación de datos.

5.6.2.2. Especificar Escenarios de Modificabilidad.


Se busca refactorizar aquellas clases que guardan relación entre el grafo y los correos
electrónicos. El problema se divide en tres grupos compuestos por uno o más escenarios
específicos: Por un lado aquellos destinados a mejorar cómo se construye el grafo y cómo se
modela (3.1). Luego, un escenario (3.2) para abstraer de los mails la vista del sistema. Y por
último, un conjunto de escenarios (3.3) para realizar la abstracción en el área de importación de
datos. De esta manera, se definen los siguientes escenarios específicos:

5.6.2.2.1. Escenario 3.1: Abstraer el modelo para la construcción del grafo.


El problema de la abstracción del código en general y de la construcción del grafo en
particular está ligado al modelo de datos del sistema. Refactorizar la forma en que se construye

83
el grafo y sus componentes depende directamente de cómo están modelados esos
componentes. En este caso los arcos, vértices y metadatos del grafo están diseñados para
operar sólo con correos electrónicos, por lo tanto las refactorizaciones no sólo deben realizarse
sobre la clase que “arma” el grafo, es necesario también aplicar cambios en las estructuras con
las que esta opera.

Como se vió en el Capítulo 3, es importante dividir el problema en problemas menores para


mitigar riesgos ante posibles errores de implementación. Por eso, este escenario se subdividió
en cinco escenarios más manejables por los desarrolladores. A su vez, se aplican distintas
tácticas de modificabilidad para minimizar el número de artefactos que necesitan ser
modificados en iteraciones posteriores desarrollo.

5.6.2.2.2. Escenario 3.1.1: Aplicar patrón Strategy a la clase GraphBuilder.


Se estudia la clase GraphBuilder del paquete graph que, como su nombre lo indica, es la
clase responsable de la construcción del grafo de personas y relaciones. GraphBuilder está
acoplada a la clase Mail que modela los correos electrónicos y que se presenta como tipo de
fuente de entrada exclusiva de SocialGraph.

El objetivo en este caso es elevar el nivel de abstracción de modo que el armado del grafo
pueda realizarse con distintas fuentes de datos, que se componen de distintos tipos de
estructuras. El patrón strategy sirve para definir una familia de algoritmos, encapsula cada uno
de ellos y los hace intercambiables, permitiendo que un algoritmo varíe independientemente de
los clientes que los usarán [Gamma1995]. Por eso se elige este patrón para el diseño de las
estrategias de construcción del grafo. Cada estrategia sabe cómo construir en el grafo en base
a la lógica y características de sus estructuras de datos. Asi, se podrá implementar de manera
flexible una nueva estrategia específica para cada fuente de datos que se desee incorporar a
SocialGraph. La tabla 5.10. muestra la de especificación de escenario 3.1.1.

Porción del Valores


Escenario

Fuente del Estímulo El cambio debe ser realizado por el desarrollador

Estímulo Poder soportar en un futuro varias fuentes de información

Artefacto Clase edu.isistan.socialgraph.graph.GraphBuilder

Ambiente Implementación.

Respuesta Realiza modificaciones sin afectar funcionalidades del sistema.

Medida de la Tiempo de realización: 6 horas/hombre.


Respuesta Se puede medir también en la mejora de la modificabilidad en el
futuro

Tabla 5.10. Especificación Escenario 3.1.1: Aplicar patrón Strategy a la clase GraphBuilder.

84
5.6.2.2.3. Escenario 3.1.2: Abstraer la clase CommunicationEdge de la clase
Mail en el modelo
La clase CommunicationEdge modela los arcos del grafo y su comportamiento a partir de los
mensajes entre dos o más personas. Al igual que otras clases del modelo,
CommunicationEdge sólo permite utilizar los mails como fuente de información, por lo tanto se
busca realizar una refactorización de modo que los arcos del grafo se puedan modelar con
otros tipos de datos. La tabla 5.11. muestra la de especificación de escenario 3.1.2.

Porción del Valores


Escenario

Fuente del Estímulo El cambio debe ser realizado por el desarrollador

Estímulo Poder soportar en un futuro varias fuentes de información

Artefacto Clases:
model.CommunicationEdge
model.CommunicationLine
model.CommunicationLineTransformer
model.ComunicationLine
model.Mail
emaildump.AnalizadorSemanticoProcesoSoftware
graph.MailGraphBuilder
graph.PositionFinder
graph.TagFinder
graph.gui.SocialGraphGUI
graph.gui.popup.CommunicationEdgePopUpMenu
graph.io.GraphPersistenceHelper
graph.transformer.OnlyActorsGraphTransformer
graph.transformer.OnlyFilesGraphTransformer

Ambiente Implementación.

Respuesta Realiza modificaciones sin afectar funcionalidades del sistema.

Medida de la Tiempo de realización: 5 horas/hombre.


Respuesta Se puede medir también en la mejora de la modificabilidad en el
futuro
Tabla 5.11. Especificación escenario 3.1.2: Abstraer la clase CommunicationEdge de la clase Mail en el modelo.

5.6.2.2.4. Escenario 3.1.3: Abstraer la clase PersonVertex de la clase Mail


en el modelo.
En este escenario se refactoriza la clase PersonVertex, que representa los vértices del grafo
asociados a personas de la red social. Las personas son hasta aquí los usuarios remitentes y
destinatarios de los correos electrónicos. Se debe realizar una abstracción del modelo en la

85
clase PersonVertex y las clases relacionadas. La tabla 5.12. muestra la de especificación de
escenario 3.1.3.

Porción del Valores


Escenario

Fuente del Estímulo El cambio debe ser realizado por el desarrollador

Estímulo Poder soportar en un futuro varias fuentes de información

Artefactos Clases:
model.PersonVertex
graph.MailGraphBuilder
graph.PositionFinder
graph.gui.popup.PersonVertexPopUpMenu
graph.io.GraphPersistenceHelper
profile.ProfileBuilder

Ambiente Implementación.

Respuesta Realiza modificaciones sin afectar funcionalidades del sistema.

Medida de la Tiempo de realización: 4 horas/hombre.


Respuesta Se puede medir también en la mejora de la modificabilidad en el
futuro
Tabla 5.12. Especificación escenario 3.1.3: Abstraer la clase PersonVertex de la clase Mail en el modelo.

5.6.2.2.5. Escenario 3.1.4: Abstraer la clase FileVertex y AttachmentEdge


de la clase Mail en el modelo.
SocialGraph analiza las relaciones entre usuarios de correo electrónico y para ellos no sólo
se vale del texto de los correos electrónicos sino también de los archivos adjuntos enviados.
Las clases FileVertex y AttachmentEdge modelan los vértices de tipo archivo y los arcos entre
estos y los usuarios respectivamente en el grafo. Se debe realizar una abstracción del modelo
para que estas clases sirvan también para otras fuentes de información. La tabla 5.13. muestra
la de especificación de escenario 3.1.4.

Porción del Valores


Escenario

Fuente del Estímulo El cambio debe ser realizado por el desarrollador

Estímulo Poder soportar en un futuro varias fuentes de información

Artefacto Clases:

86
model.AttachmentEdge
model.FileVertex
graph.MailGraphBuilder
graph.gui.popup.AttachmentEdgePopUpMenu
graph.gui.popup.FileVertexPopUpMenu
graph.io.GraphPersistenceHelper

Ambiente Implementación.

Respuesta Realiza modificaciones sin afectar funcionalidades del sistema.

Medida de la Tiempo de realización: 3 horas/hombre.


Respuesta Se puede medir también en la mejora de la modificabilidad en el
futuro
Tabla 5.13. Especificación escenario 3.1.4: Abstraer la clase FileVertex y AttachmentEdge de la clase Mail en el
modelo.

5.6.2.2.6. Escenario 3.1.5: Abstraer la clase TagFinder de la clase Mail en el


modelo.
La clase TagFinder es la responsable de realizar el proceso de análisis NLP del contenido de
las fuentes de información. Arroja como resultado un vector de etiquetas (clase Tag)
representativas de los tópicos de conversación entre dos o más personas. Este procesamiento
es fundamental para el análisis de redes sociales y como es de esperar en SocialGraph, este
conjunto de clases presenta un alto grado de acoplamiento con la clase Mail.

Esta sección de código presenta en su implementación varios síntomas de acoplamiento y


malas prácticas. Estudiando las clase TagFinder y Tag se observa que algunas de sus
responsabilidades están solapadas. Mirando los code smells, se detectan cinco smells en
métodos de la clase TagFinder. Se trata de Feature Envy y Dispersed Coupling, tipos de smell
vinculados a problemas de acoplamiento y cohesión a nivel de método de clase. Estos
síntomas se utilizan como guía para la refactorización. En la implementación del escenario se
eliminan estos smells a través de técnicas como extracción y movimiento de métodos. La tabla
5.14. muestra la de especificación de escenario 3.1.5.

Porción del Valores


Escenario

Fuente del Estímulo El cambio debe ser realizado por el desarrollador

Estímulo Poder soportar en un futuro varias fuentes de información

Artefacto Clases:
graph.TagFinder
model.Tag
profile.ProfileBuilder

Ambiente Implementación.

87
Respuesta Realiza modificaciones sin afectar funcionalidades del sistema.

Medida de la Tiempo de realización: 4 horas/hombre.


Respuesta Se puede medir también en la mejora de la modificabilidad en el
futuro

Tabla 5.14. Especificación de escenario 3.1.5: Abstraer la clase TagFinder de la clase Mail en el modelo.

5.6.2.2.7. Escenario 3.1.6: Refactorizar God Class en


GraphPersistenceHelper
Una parte importante de la construcción del grafo es la persistencia de sus datos. Esta
funcionalidad permite a SocialGraph almacenar en archivos el procesamiento realizado y
retomar su análisis en el futuro.

Toda esta funcionalidad se concentra prácticamente sola clase: GraphPersistenceHelper. Es


una clase extensa y dificil de comprender. El primer code smell del ranking de todo el sistema
es el God Class de esta clase. Además, en la misma, se detectan 7 code smells más
vinculados al acoplamiento con otras clases. Dada la relevancia de esta clase y sus síntomas,
se decide refactorizarla. La tabla 5.15. muestra la de especificación de escenario 3.1.6.

Porción del Valores


Escenario

Fuente del Estímulo El cambio debe ser realizado por el desarrollador

Estímulo Poder soportar en un futuro varias fuentes de información

Artefacto Clase: graph.io.GraphPersistenceHelper

Ambiente Implementación.

Respuesta Realiza modificaciones sin afectar funcionalidades del sistema.

Medida de la Tiempo de realización: 7 horas/hombre.


Respuesta Se puede medir también en la mejora de la modificabilidad en el
futuro. Eliminación del code smell: God Class
Tabla 5.15. Especificación de escenario 3.1.6: Refactorizar God class en GraphPersistenceHelper

5.6.2.2.8. Escenario 3.2: Abstraer la clase Mail en la interfaz gráfica de la


herramienta.
Una vez que se hizo la abstracción de la clase Mail del modelo se decide avanzar sobre este
cambio en la vista, es decir, se adapta la vista a la nueva abstracción realizada en el modelo.
La vista, accede de manera directa a todos los atributos específicos de los correos electrónicos,
se busca que ahora acceda de manera más general a los atributos de CommunicationLine.
Este escenario es un paso intermedio para preparar el sistema para el refactoring sobre la
Anomalía 2, la cual no se aborda en este proceso de refactoring.

88
La solución propuesta en este escenario es abstraer la clase Mail de la vista y que esta
quede en función de CommunicacionLine de forma que no esté ligada a una fuente de
información en particular. Para esto se plantea el escenario correspondiente y se analizan los
code smells asociados a las clases de la vista que hay que modificar para poder indicar
posibles refactorings a realizar sin perder de vista el problema de fondo ni los atributos de
calidad que nos guían en el proceso. La tabla 5.16. muestra la de especificación de escenario
3.2.

Porción del Valores


Escenario

Fuente del Estímulo El cambio debe ser realizado por el desarrollador.

Estímulo Poder soportar en un futuro varias fuentes de información.

Artefacto graph.gui.popup.FileVertexPopUpMenu.java
graph.gui.popup.AttachmentEdgePopUpMenu.java
graph.gui.popup.CommunicationEdgePopUpMenu.java
graph.gui.SocialGraphGUI.java
profile.ProfileBuilder.java
graph.gui.popup.PersonVertexPopUpMenu.java
graph.gui.panel.MailBrowserPanel
graph.gui.panel.MailProcessorPanel
profile.ProfileBuilder

Ambiente Implementación.

Respuesta Realiza modificaciones sin afectar funcionalidades del sistema.

Medida de la Tiempo de realización: 8 horas/hombre.


Respuesta Se puede medir también en la mejora de la modificabilidad en el
futuro

Tabla 5.16. Especificación de escenario 3.2: Abstraer la clase Mail en la interfaz gráfica de la herramienta.

5.6.2.2.9. Escenario 3.3: Refactorizar sección de código de “importación de


datos”
En el proceso de refactorización de SocialGraph, la importación de las fuentes de
información es una sección de interés del sistema. Un conjunto de clases son las responsables
de importar los datos desde distintos medios (se desean agregar más) para comenzar el
procesamiento de información, la construcción del grafo, su persistencia, etc. Actualmente,
SocialGraph permite importar datos de correos electrónicos tanto desde archivos dump
(generados por exportación de datos desde clientes de correo) o bien accediendo directamente
a los servidores de correo.

El objetivo de este escenario mejorar la modificabilidad del módulo de importación de datos,


para que, a futuro sea más fácil incorporar nuevas fuentes de información. La implementación
de esta sección del sistema es compleja y dificil de comprender ya que existen varias clases

89
con funcionalidades similares, con alto acoplamiento, desarrolladas a demanda, en distintas
etapas del ciclo de vida del sistema y por distintos desarrolladores. La tabla 5.17. muestra la de
especificación de escenario 3.3.

Porción del Valores


Escenario

Fuente del Estímulo El cambio debe ser realizado por el desarrollador

Estímulo Poder soportar en un futuro varias fuentes de información

Artefacto Paquetes: importer, emaildump y emaildumpapp

Ambiente Implementación.

Respuesta Realiza modificaciones sin afectar funcionalidades del sistema.

Medida de la Tiempo de realización: 8 horas/hombre.


Respuesta Se puede medir también en la mejora de la modificabilidad en el
futuro
Tabla 5.17. Especificación de escenario 3.3: Refactorizar sección de código de “importación de datos”

5.6.2.3. Agregar al backlog


Los escenarios 3.1.1, 3.1.2, 3.1.3, 3.1.4, 3.1.5, 3.1.6, 3.2 y 3.3 son agregados al backlog de
tareas a realizar por los desarrolladores. En la próxima etapa se define la priorización del
mismo.

5.6.3. Etapa 3: Priorización de backlog


Para priorizar cada escenario del backlog a partir del grado de impacto en el sistema y
esfuerzo requerido para la implementación, se busca la opinión y el consenso entre los
desarrolladores, stakeholders y demás miembros del proyecto. A continuación, una síntesis de
ese análisis.

5.6.3.1. Nivel de Impacto


El análisis de impacto de este conjunto de escenarios es, en general, alto. Las
modificaciones de de diseño e implementación buscan una abstracción de la aplicación, que es
el principal objetivo del proceso de refactorización de SocialGraph.

El conjunto de escenarios 3.1, que ataca los problemas de acoplamiento en la construcción


del grafo se considera mayor impacto dado los cambios introducidos a nivel del modelo y el
diseño del sistema. Este escenario se dividió en cinco escenarios más simples, con lo cual se
entiende que el nivel de impacto (y esfuerzo) va decreciendo a medida que se van
implementando.

Con menor grado de impacto, se encuentra el escenario 3.3, que requiere revisar la
funcionalidad de importación del sistema y por último el escenario 3.2 que propaga las
abstracciones del modelo a la interfaz gráfica del sistema.

90
5.6.3.2. Nivel de Esfuerzo
El nivel de esfuerzo varía según las capacidades y el conocimiento que tengan del sistema
los desarrolladores. Esta variable se puede medir a partir de los tiempos requeridos para
implementar cada escenario. Según estos tiempos estimados, el orden de escenarios es el
siguiente:

1. Escenario 3.1.1 (10 horas)


2. Escenario 3.2 (9 horas)
3. Escenario 3.3 (9 horas)
4. Escenario 3.1.6 (7 horas)
5. Escenario 3.1.2 (7 horas)
6. Escenario 3.1.3 (6 horas)
7. Escenario 3.1.4 (5 horas)
8. Escenario 3.1.5 (4 horas)
La descomposición del escenario 3.1 en 6 escenarios hace que, para su implementación, se
requiere cada vez menos esfuerzo, dado que la solución del problema se va realizando de
manera progresiva y el grado de conocimiento del sistema va evolucionando también. Por
razones prácticas, se realizan todos estos escenarios juntos. Los escenarios que requieren
igual cantidad de esfuerzo se pueden implementar en cualquier orden.

5.6.3.3. Ubicación en cuadrantes

Figura 5.9. Cuadrante de deuda técnica para los escenarios de la iteración 2.

91
A partir de la valoración de los escenarios, se los ubica en el cuadrante de deuda técnica que
se muestra en la figura 5.9.

Priorización de cuadrante de deuda técnica :

• Escenario 3.1.1: Aplicar patrón Strategy a la clase GraphBuilder.


• Escenario 3.1.2: Abstraer la clase CommunicationEdge de la clase Mail en el modelo.
• Escenario 3.1.3: Abstraer la clase PersonVertex de la clase Mail en el modelo.
• Escenario 3.1.4: Abstraer la clase FileVertex y AttachmentEdge de la clase Mail en el
modelo.
• Escenario 3.1.5: Abstraer la clase TagFinder de la clase Mail en el modelo.
• Escenario 3.1.6: Refactorizar God class en GraphPersistenceHelper.
• Escenario 3.2: Abstraer la clase Mail en la interfaz gráfica de la herramienta.
• Escenario 3.3: Refactorizar sección de código de “importación de datos”.

5.6.4. Etapa 4: Implementación de escenarios


5.6.4.1. Escenario 3.1.1: Aplicar patrón Strategy a la clase
GraphBuilder.
5.6.4.1.1. Verificar cobertura de test
Hasta el momento el sistema cuenta sólo con un caso de test que verifica todo el
procesamiento de la información: desde la importación de datos hasta la construcción del grafo.
Si bien la cobertura de test era adecuada, para la implementación de este conjunto de
escenarios, se dividió ese test en dos: Un primer test verifica la correcta importación de datos y
luego otro test verifica la correcta construcción del grafo. Este último será el más aplicable en
este escenario, aunque todos los test son ejecutados luego de una modificación.

5.6.4.1.2. Implementar Escenario


En la figura 5.8 se ve el diagrama de clases de la construcción del grafo antes de realizar las
refactorizaciones. Con el objetivo de abstraer la clase GraphBuilder, se crea la interfaz
GraphBuilderStrategy que contiene el método buildGraph, antes contenido dentro de
GraphBuilder. Esta interfaz es implementada por una nueva clase, MailGraphBuilderStrategy, la
cual contiene todos los métodos (comportamiento) para crear un grafo a partir de correos
electrónicos. Es decir, los atributos y métodos para la construcción exclusiva de un grafo a
partir de correos electrónicos son ubicados en esta estrategia específica. Ahora, la clase
original, GraphBuilder, contiene una colección de estrategias a las cuales invoca una a una
para realizar la construcción del grafo a partir de la combinación de distintos orígenes de datos
y formas de generar el grafo.

En un futuro se podrán desarrollar nuevas estrategias para la creación o complementación


de grafos a partir de otras fuentes de información. En la figura 5.9 se ve el diagrama de clases
de la construcción del grafo después de implementar el escenario.

92
Figura 5.10. Diagrama de Clases después de implementar Escenario 3.1.1.

Al mover los principales métodos de GraphBuilder a MailGraphBuilder, también se mueven


sus code smells. Se realizan refactorizaciones para mejorar la calidad de esta clase. La

93
mayoría de estos code smells se resuelve fácilmente reorganizando el código, se siguen los
pasos para refactorizar de la guia de Lanza y Marinescu [Lanza2007] para atacar cada code
smell. Se describen los más relevantes a continuación:

• Dispersed Coupling en el método createAttachmentEdge. Se reorganizó el código, el


smell persiste aunque no influye negativamente en el diseño.

• Feature Envy en el método createVertex. Se eliminó el smell a través de la acción de


refactorización move method. El contenido del vértice se ingresa ahora en la clase
PersonVertex. De esta manera se encapsula el conocimiento de cómo funciona
internamente la clase, siguiendo los principios de la programación orientada a objetos.

• Dispersed Coupling en el método createEdge. De manera similar, se elimina el smell


moviendo el código responsable del cálculo de los tags del arco a calculateEdge, un
nuevo método en la clase ComunicationEdge.

5.6.4.1.3. Ejecutar casos de test


Se ejecutaron los casos de test correctamente, verificando la no introducción de cambios en
la funcionalidad del sistema.

5.6.4.2. Escenario 3.1.2: Abstraer la clase CommunicationEdge de la


clase Mail en el modelo
5.6.4.2.1. Verificar cobertura de test
La cobertura de test del proyecto es adecuada para la implementación del escenario.

5.6.4.2.2. Implementar Escenario


En este escenario se crea la interfaz ComunicationLine para representar de manera
abstracta cada una de las posibles vías de comunicación (emails, chats, documentos, etc) de
las relaciones analizadas por SocialGraph. Esta nueva interfaz debe tener los métodos
necesarios para ponderar un arco (relación) entre dos vértices (personas) de la red social. La
interfaz CommunicationLine reemplaza a la clase Mail en las estructuras del grafo.

A partir de la implementación de CommunicationLine y de reemplazar por esta nueva interfaz


las apariciones de la clase Mail, surgen algunos conflictos que es necesario resolver sin que
afectar el comportamiento observable de la aplicación. Para cada conflicto se combinaron una
o más tácticas de modificabilidad.

• Conflictos con atributos: En la vista y en otras áreas del sistema, se accede de


manera directa a los atributos propios de correos electrónicos: asunto, remitente,
destinatarios, etc. CommunicationLine no cuenta con estos atributos, ya que fue
diseñado como una interfaz abstracta, que puede ser implementada por Mail o por otras
clases en un futuro. Para resolver esto se aplica tácticas de prevención de efecto

94
dominó y retraso del tiempo de binding. Se agregan atributos dinámicos de modo que
se optimiza el polimorfismo y se minimiza el impacto en la interfaz gráfica y otras áreas
del sistema. Esto puede considerarse un paso intermedio para otro refactoring.

• Conflicto con colecciones: En muchas partes del sistema se utilizan colecciones de


correos electrónicos (en general se implementan en listas). La conversión de una lista
de Mails a una lista de CommunicationLine no es inmediata y además, el reemplazo de
una por otra no se da de manera directa en todo el sistema, sino que se va
implementando de manera paulatina a través de varias refactorizaciones. Significa que
al realizar esta “migración” aparecerán conflictos en muchas sentencias que requieren
una “lista de Mail” y no una “lista de CommunicationLine” y a su vez están dispersas en
todo el sistema, no sólo en las áreas que se decide refactorizar en este escenario, esta
iteración o incluso en esta instancia del proceso. Para resolver este conflicto, se aplica
una táctica de modificabilidad de uso de intermediario, implementando el patrón
adaptador en la clase CommunicationLineTransformer. Este patrón convierte la interfaz
de una clase en otra interfaz que es la que esperan los clientes, permitiendo que
cooperen clases que de otra forma no podrían por tener interfaces incompatibles
[Gamma1995]. Este adaptador bidireccional se compone de dos métodos que permiten
“adaptar” tanto las colecciones de Mail como las de CommunicationLine:

◦ List<CommunicationLine> getCommunicationLineList(List<Mail> mails): A


partir de una lista de correos electrónicos retorna una lista de líneas de
comunicación.

◦ List<Mail> getMailList(List<CommunicationLine> communicationLines): A


partir de una lista de líneas de comunicación retorna una lista de correos
electrónicos.

La implementación del adaptador trae varias ventajas. Por un lado se contiene una
enorme cantidad de conflictos que surgen en todos el sistema al reemplazar la clase
Mail por la interfaz CommunicationLine los posibles. En cada caso habrá que invocar al
adaptadorpara obtener una lista de Mail o una lista de CommunicationLine, ya que
mientras dure el proceso de refactorización y como sugieren las tácticas de
modificabilidad, ambas interfaces conviven en el sistema. Por otro lado, las referencias
a CommunicationLineTransformer representan “tareas pendientes” de abstracción o
refactorización del sistem que deberán abordarse en un futuro pero que ahora serán
fáciles de identificar.

En este punto de la refactorización de la construcción del grafo, se han modificado las


principales estructuras que modelan los componentes del grafo y la forma en que estas se
relacionan con la aplicación. Es decir, el modelo de datos ha elevado su nivel de abstracción y
los cambios introducidos permiten un diseño más flexible y adaptable a nuevos cambios.

95
5.6.4.2.3. Ejecutar casos de test
Se ejecutaron los casos de test correctamente, verificando la no introducción de cambios en
la funcionalidad del sistema.

5.6.4.3. Escenarios 3.1.3, 3.1.4 y 3.1.5


Los escenarios restantes de la sección 3.1 (3.1.3, 3.1.4 y 3.1.5), básicamente propagan los
cambios introducidos en los escenarios anteriores (3.1.1 y 3.1.2). Se aplican esas
modificaciones de diseño a un conjunto de clases del modelo y además, en esa revisión de
código se realizan otras refactorizaciones como eliminación de código muerto, extracción y
movimiento de métodos, etc. De esta manera se eliminan 5 code smells. En todos los casos, se
respetan los lineamientos de implementación definidos en el proceso, garantizando la correcta
funcionalidad del sistema a través de la ejecución de los casos de test correspondientes.

5.6.4.4. Escenario 3.1.6: Refactorizar God Class en


GraphPersistenceHelper (GPH)
5.6.4.4.1. Verificar cobertura de test
La cobertura de test del proyecto es adecuada para la implementación del escenario.

5.6.4.4.2. Implementar Escenario


El refactoring de una God Class es una tarea compleja, ya que la falta de armonía a menudo
es un efecto acumulativo de otros anomalías que se producen a nivel de método. Por lo tanto,
la realización de una refactorización como tal requiere información adicional y más precisa
sobre los métodos de la clase, y a veces incluso sobre su contexto herencia [Lanza2007].

Esta extensa y compleja clase presenta muchos síntomas que, combinados, llevan a
identificarla como God Class. Por ejemplo, varios de métodos deberían ubicarse en otras
clases, como la clase Mail. Dada la falta de abstraccion y polimorfismo en varios puntos se
realizan invocaciones tipo “instance of” que podrían evitarse. También se detecta mucho código
muerto que no es invocado en ningún momento, entre otras malas prácticas de programación.

Para realizar el escenario, se implementa una serie de refactorizaciones estándar que se


muestra a continuación:

• Move Method: getStart, getEnd y filter se movieron a la clase Mail. Se eliminan 2 smells:
God Class y Shotgun Surgery en Mail.getSendDate.
• Eliminación de método Main. Se elimina un smell: Feature Envy en método Main.
• Move Method: increment, formatDate y formatSlice se mueven a la nueva clase
DateUtils del nuevo paquete edu.isistan.socialgraph.utils. No se eliminan smells.
• Eliminación de clase DegreeComparator. No era utilizada en el proyecto.
• Eliminación de métodos: generateSimpleGraph y generateSlicedGraph. No eran
utilizados.
• Eliminación del método testRead. No era utilizado. Imprimía por pantalla los
componentes de grafo. Se eliminan 2 smells.
• Extract method: getEdgeProperties y getVertexProperties.

96
• Se implementa una refactorización de modo que cada Vertex o Edge del grafo sepa
cómo cargar y/o retornar sus propiedades. Se crean métodos abstract en las clases
AbstractVertex y AbstractEdge. De esta manera se eliminarán las sentencias de tipo
“instance of” en la clase GPH. Refactorizaciones implementadas:
◦ Se crean los métodos en AbstractVertex e hijos:
▪ public abstract void readProperties(Map<String, Object> properties);
▪ public abstract void writeProperties(Map<String, Object> properties);

◦ Se movieron constantes desde GPH a las clases:


▪ FV_TAGS y FV_MAILS a FileVertex

▪ PV_SOURCE, PV_TARGET a PersonVertex

◦ Se eliminan los últimos dos smells de GPH, de tipo Dispersed Coupling, ranking 20 y
21.

◦ Se crean los métodos en AbstractEdge e hijos:


▪ public abstract void readProperties(Map<String, Object> properties);

▪ public abstract void writeProperties(Map<String, Object> properties);

◦ Se movieron constantes desde GPH a las clases:


▪ AE_MAILS a AttachmentEdge.

▪ CE_MAILS, CE_TAGS a CommunicationEdge.

Como resultado, se obtiene una clase más limpia, cohesiva y menos compleja. Se reduce
una gran cantidad de smells (7), incluyendo el God Class.

5.6.4.4.3. Ejecutar casos de test


Luego de terminar la implementación del escenario se corrieron los test que le dan cobertura
al área de código afectada. Los test correspondientes, en este caso son dos:
testExtraerMessages y testBuildGraph. El primero, a partir de una fuente de datos de test
importa la misma y verifica que los datos incorporados sean los especificados. De esta forma
se sabe que el comportamiento normal no fue afectado. El segundo, a partir de los datos
importados por el test anterior, realiza la construcción del grafo y comprueba que los
parámetros del grafo sean los especificados, detectando que no haya cambios en el
funcionamiento normal de la aplicación.

Estos dos test se ejecutaron con éxito en un primer momento. Luego de unos días de
desarrollo, utilizando la aplicación se notó que uno de las partes de la interfaz gráfica no estaba
funcionando correctamente. Al depurar el código, detectó un error en una área del código
afectada por este refactoring. Dada esta situación, se comprueba que la cobertura de test no
estaba garantizada y por lo tanto, se procede a crear un nuevo caso de test. Se crea el test
“readGraphBuilder” que lee el grafo creado con en el test “testBuildGraph” y verifica los
parametros del grafo que no se tuvieron en cuenta en el anterior test.

97
De esta experiencia se puede aprender que es más peligroso pensar que se tiene una
cobertura del 100% en el área de código a modificar, que saber que se está modificando código
sin cobertura, ya que resulta en una falsa seguridad y es un riesgo en el proceso de desarrollo
del cual no se tiene conocimiento. También hay que tener en cuenta que en un proceso de
refactoring cuando antes se encuentre un bug mas facil es resolverlo ya que el desarrollador
tiene más fresco el área de código comprometida y cuáles fueron las modificaciones realizadas.

5.6.4.5. Escenario 3.2: Abstraer la clase Mail en la interfaz gráfica de


la herramienta.
5.6.4.5.1. Verificar cobertura de test
La cobertura de test del proyecto es adecuada para la implementación del escenario.

5.6.4.5.2. Implementar Escenario


En los artefactos del escenario se cambian las colecciones de Mail por colecciones de
CommunicationLine. Se elimina el uso del adaptador CommunicationLineTransformer
introducido en escenarios anteriores para localizar las modificaciones y contener el efecto
dominó de los cambios realizados en esos escenarios. Se utiliza el método getAttribute de la
interfaz CommunicationLine para obtener atributos específicos de manera dinámica. A
continuación se puede ver ejemplos de código de las refactorizaciones realizadas en los
artefactos del escenario. Se preceden con un “–” las líneas de código originales y con un “+” las
nuevas por las que fueron reemplazadas.

● Reemplazo de colección de Mail por colección de CommunicationLine:


- List<Mail> mailsList = new ArrayList<Mail>();

+ List<CommunicationLine> mailsList = new ArrayList<CommunicationLine>();

● Eliminación de Adaptador CommunicationLineTransformer:


- mailsList.addAll(CommunicationLineTransformer.getMailList(((CommunicationEdge)
edge).getMails()));

+ mailsList.addAll(((CommunicationEdge) edge).getMails());

● Utilización de atributos dinámicos:


- private Mail mail;

+ private CommunicationLine mail;

[...]

- String content = mail.getContent();

+ String content = (String)mail.getAtribute("content");

A través de la interfaz CommunicationLine se puede tener mayor flexibilidad con los atributos
dinámicos para la vista. Sin embargo, en un futuro se tendrá que realizar una separación de las

98
capas Modelo y Vista para que esta última sea más modificable, tal como se describe en la
Anomalía 2.

Al agregar una nueva fuente de información, si los atributos a mostrar fueran los mismos o
parecidos la modificación realizada en este escenario, el diseño actual podría soportarlo. En
caso contrario, si esta debe ser mostrada de una forma diferente a la ya existente, se deberá
seguir refactorizado la aplicación. En tal caso, habría que modificar la arquitectura para que se
visualicen los atributos según el tipo de fuente de información, para esto se requiere separar la
Vista del Modelo e implementar la lógica en el Controlador.

5.6.4.5.3. Ejecutar casos de test


Una vez terminadas las modificaciones, se realiza un test manual de la interfaz de la
herramienta, constatando su buen funcionamiento y que no haya ningún cambio observable
luego de la implementación del escenario. Además, se ejecutaron los casos de test
correctamente, verificando la no introducción de cambios en la funcionalidad del sistema.

5.6.4.6. Escenario 3.3: Refactorizar sección de código de


“importación de datos”
5.6.4.6.1. Verificar cobertura de test
La cobertura de test del proyecto es adecuada para la implementación del escenario.

5.6.4.6.2. Implementar Escenario


Se estudian los code smells de los artefactos a refactorizar. Se observan clases identificadas
con el smell God Class. Además de este smell, en las clases se detectan varios más,
ubicándose en el ranking de las clases con más smells detectados. Estas clases parecen tener
relación con la funcionalidad de “importación de datos”. Se propone indagar la sección de
código referida a “importación de datos”. Allí se concentra gran cantidad de code smells que
podrían indicar problemas de diseño. En tres clases se concentran 21 smells, un 18,3% del
total de 115 smells actualmente presentes en el sistema:

● importer.Importer (4 smells)
● emaildumpapp.ImportThunderbirdEmail (8 smells, contiene God Class)
● importer.ImporterHelper (9 smells, contiene God Class).

Analizando los paquetes de estas clases, se encuentran más smells:

● importer.MailPersistenceHelper (1 smell)
● importer.docreader.DocumentReader (1 smell, existe dead code)
● importer.FileTag.FileTagProvider (4 smells, existe dead code)
● emaildumpapp.EmailDumpApp (1 smell)
● emaildumpapp.TableEmailPanel (1 smell)
● emaildumpapp.EmailDumpPanel (1 smell)
● emaildumpapp.EmailMergePanel (1 smell)
● emaildump.AnalizadorSemanticoProcesoSoftware (3 smells)

99
● emaildump.EmailContent (1 smell)
● emaildump.EmailDump (5 smells, God Class)
● emaildump.StringUtils (1 smell)

Entonces, analizando el entorno de las clases afectadas, se suman 20 smells más,


resultando en un total de 41 smells, un 35,7% sobre el total de 115 smells del estado actual del
proyecto. Se busca realizar un refactoring que defina las responsabilidades de cada clase y, en
lo posible, permita reutilizar su código para otras fuentes de información.

Refactorizaciones sobre la clase EmailDump (5 smells)


● Se eliminan métodos de código muerto:
○ public static List<edu.isistan.socialgraph.model.Mail> convertir(Message[]
mensajesA)
○ public String guardarGrafo(List<Mail> mailsList)
○ public void imprimirMails(List<Mail> mailsList)
○ public Message[] extraerMailsFromDump(Message[] mensajes, SearchTerm
filtro)
○ public static Message[] extraerMessagesFromDumpThunderbird(String path)
● Método public Mail convertirMensaje(Message m) movido a Mail, en forma de
constructor.
● Método guardarGrafo, se elimina smell Feature Envy a partir de eliminar los
System.out.
● Eliminación del método.

Con esta serie de cambios se anularon todos los smells en la clase EmailDump, sin
embargo, se introdujeron nuevos smells en la clase Mail, destacándose God Class. Entonces,
en esta instancia se elimina el mismo tipo de smell de una clase mientras que se lo incorpora
en otra. La refactorización vale porque el código resulta menos acoplado, más comprensible y
por lo tanto, más fácil de modificar.

Refactorizaciones sobre la clase ImportThunderbirdEmail (8 smells)


La tabla 5.17 muestra la distribución de smells en la clase ImportThunderbirdEmail antes de
realiza refactorizaciones sobre la misma.

N° Smell Clase Ranking

i God Class ImportThunderbirdEmail 2

ii Dispersed Coupling ImportThunderbirdEmail.createMessage 54

iii Feature Envy ImportThunderbirdEmail.importrEMLmailToMail 51

iv Feature Envy ImportThunderbirdEmail.importrEMLmailToMessage 52

v Dispersed Coupling ImportThunderbirdEmail.importrEMLmailToMessage 53

vi Brain Method ImportThunderbirdEmail.importrEMLmailToMessageCleanConcurrente 4

100
vii Dispersed Coupling ImportThunderbirdEmail.importrEMLmailToMessageCleanConcurrente 55

viii Feature Envy ImportThunderbirdEmail.parserEmailFronStringGenericoToMessage 50

Tabla 5.17. Code smells de la clase emaildumpapp.ImportThunderbirdEmail.

● Eliminación de código muerto, métodos:


○ public void importrEMLmailToMail()
○ public Message[] importrEMLmailToMessage()
○ public Message parserEmailFronStringGenericoToMessage(String emailStr)
○ public Mail parserEmailFronStringGenerico(String emailStr)
○ private static String extraerPropiedadContenidoMult(String emailStr,String
tagPropepiedad)
○ private static String extraerPropiedadContenido(String emailStr,String
tagPropiedad)
○ private static String extraerPropiedad(String emailStr, String tagPropepiedad)
○ public static String getText(String p)
○ public String getPathEml()
○ public Message createMessage(String to, String from, String subject,Date
sendDate, String body, List<File> attachments)
○ public Message parserEmailFronStringGenericoToMessageClean(String
emailStr)
● Eliminación de imports innecesarios en ImportThuderbird y en ProcesarMailEml.

Luego de estas refactorizaciones sólo quedan los smells vi y vii, ambos en el método
importEMLmailToMessageCleanConcurrente, uno de los más importantes de esa clase. En el
proyecto restan 116 smells.

Refactorizaciones sobre las clases Importer (4 smells) y ImporterHelper (9 smells)


La tabla 5.18 muestra la distribución de smells en las clases Importer e ImporterHelper antes
de realiza refactorizaciones sobre las mismas.

N° Smell Clase Ranking

i Feature Envy Importer.printAttachments 13

ii Feature Envy Importer.printAttachStats 14

iii Dispersed Coupling Importer.printDateRange 51

iv Feature Envy Importer.printRandom 50

v God Class ImporterHelper 2

vi Feature Envy ImporterHelper.formatMails 28

vii Intensive Coupling ImporterHelper.formatMails 29

101
viii Dispersed Coupling ImporterHelper.getAttachments 31

ix Feature Envy ImporterHelper.getText 30

x Brain Method ImporterHelper.importMails 4

xi Intensive Coupling ImporterHelper.importMails 25

xii Dispersed Coupling ImporterHelper.importMails 27

xiii Dispersed Coupling ImporterHelper.readFolderMap 26


Tabla 5.18. Code smells de las clases importer.Importer e importer.ImporterHelper.

Clase edu.isistan.socialgraph.importer.Importer (4 code smells):

● Solo es invocada en 2 métodos main en las clases Importer y MailPersistenceHelper


● Posee métodos para imprimir por pantalla que son usados en el main de Importer.

Clase edu.isistan.socialgraph.importer.ImporterHelper (9 code smells):

● Sólo es invocada desde el main de Importer.

Dado que ambas clases sólo son instanciadas en métodos main, seguramente para testing
en alguna otra etapa de desarrollo de SocialGraph, se consideran clases obsoletas, código
técnicamente muerto, y por lo tanto son eliminadas del proyecto. También se elimina el método
main de la clase MailPersistenceHelper. Naturalmente, se reducen todos los smells de las
clases Importer e ImporterHelper y además 1 smell en MailPersistenceHelper. En el proyecto
quedan 93 code smells.

Refactorizaciones en la estructura de clases


Luego de realizar una serie de refactorizaciones a nivel de código en este módulo, se
realizan cambios en la estructura de estructura de clases y paquetes: se reubican y renombran
algunos artefactos:

• El paquete emaildumpapp contiene elementos propios de la interfaz gráfica del sistema


y por lo tanto es movido dentro del paquete graph.gui.

• La clase emaildumpapp.ImportThunderbirdEmail es responsable de la importación


desde archivos dump, por lo tanto se renombra y mueve de paquete, quedando
emaildump.ImportEml.

• El paquete emaildump concentra la lógica de importación de emails se mueve dentro


del paquete importer. A su vez, dentro de emaildump se mueven las clases Indexer y
MailPersistenceHelper.

102
5.6.4.6.3. Ejecutar casos de test
Se ejecutaron los casos de test correctamente, verificando la no introducción de cambios en
la funcionalidad del sistema.

5.6.5. Etapa 5: Medición de resultados


Es necesario determinar el grado de avance obtenido con la implementación de los
escenarios de esta iteración del proceso de refactorización. Esta medición de resultados se
hace combinando distintos indicadores para validar las refactorizaciones llevadas a cabo hasta
el momento.

En esta segunda iteración del proceso, se han definido e implementado ocho escenarios de
modificabilidad a través de los cuales se ha atacado la Anomalía 3: “Diseño ligado a correos
electrónicos”. Por lo tanto, habrá que verificar si el diseño de la aplicación es ahora, un poco
menos ligado a correos electrónicos, es decir, un poco más abstracto y más modificable.

5.6.5.1. Análisis basado en métricas y documentación


• Cantidad de horas de desarrollo dedicadas. En total para el análisis, especificación e
implementación de los escenarios de esta iteración se empleó un estimado de 57 horas
de desarrollo.

• Code smells por tipos. En la tabla 5.19 se aprecian las mejoras realizadas en esta
iteración en cuanto a los code smells de todo el sistema. Se observa un descenso
notable de la cantidad total de smells (29,55%) y mejoras significativas en los
principales tipos de smells vinculados a acoplamiento y cohesión. El tipo de smells con
mayor mejora (75%) son las God Class, que se asocia a clases con alta complejidad,
alto acoplamiento y baja cohesión.

Grupo Cantidad por Tipo Antes Después Mejora

Total de Code Smells 132 93 29,55%

Identidad Feature Envy 61 47 22,95%

Colaboración Dispersed Coupling 45 32 28,89%

Colaboración Shotgun Surgery 13 6 53,85%

Colaboración Intensive Coupling 3 1 66,67%

Identidad Brain Method 3 2 33,33%

Identidad God Class 4 1 75,00%

Tabla 5.19. Variación de code smells de cada tipo antes y después de iteración 2.

103
• Code smells por componentes afectados. La tabla 5.20 muestra las mejoras antes y
después de las refactorizaciones en cada uno de los principales componentes del
sistema. Se destaca el mejor resultado relativo en Importer donde, luego del escenario
3.3, se logra bajar de 43 a 17 smells. Luego los componentes Graph (reducción de 14
de 25 smells) y Model (reducción de 4 de 15 smells) siguen en nivel de mejora. Como
se vió en la etapa 1 de esta iteración, estos tres componentes, que concentran la
mayoría de los smells, son los responsables de la construcción del grafo, el modelado y
la importación de datos. Estas áreas de código tienen relación directa con el desarrollo
de los requerimientos funcionales planificados para SocialGraph. Por lo tanto, es
fundamental lograr allí estas mejoras de modificabilidad.
Se observa también que algunos smells no fueron eliminados, sino desplazados a otras
áreas del sistema. En particular los 5 “nuevos” smells que figuran en la fila Otros se
ubican en áreas de menor importancia: tres de ellos en los casos de test y dos el
paquete utils.

También se ve que la interfaz gráfica se mantiene sin cambios. Este resultado es de


esperar dado que se decidió no abordar la Anomalía 2 que representa el acoplamiento
entre las capas de la arquitectura.

Antes Después Mejora

Importer 43 17 60,47%

GUI 36 36 0,00%

Graph 25 11 56,00%

Model 15 11 26,67%

Otros 13 18 -38,46%

Total 132 93 29,55%

Tabla 5.20. Variación de code smells en cada paquete antes y después de iteración 2.

5.6.5.2. Análisis subjetivo y valoración del grupo de desarrollo


En el conjunto de escenarios que componen esta iteración se hicieron algunos cambios en el
diseño y el modelo de la aplicación que mejoran considerablemente su flexibilidad y nivel de
abstracción. Se destaca la aplicación del patrón Strategy en la construcción del grafo en el
escenario 3.1.1. El grafo es la estructura principal de SocialGraph y al estar ligado a los correos
electrónicos (clase Mail) era dificil pensar en nuevas fuentes de datos que alimenten la
información representada en el grafo. Luego de esta refactorización, para agregar una nueva

104
fuente, bastará con crear una nueva estrategia de construcción del grafo, que puede
combinarse con la(s) existente(s).

Esta mejora de diseño se complementa con la abstracción del modelo en los escenarios
3.1.2 a 3.1.5. Alli, se reemplazó en cada uno de los componentes del grafo, la clase Mail por la
interfaz CommunicationLine, que puede ser implementada por diversas fuentes de información.
A su vez, este cambio se propagó a la interfaz gráfica en el escenario 3.2. De modo que se
elevó el nivel de abstracción en todas las secciones de código vinculadas a la construcción del
grafo.

5.6.5.3. Validación de escenarios


Realizadas estas mediciones, se observa una mejora en la modificabilidad y flexibilidad del
sistema, tanto desde el punto de vista del diseño como de la implementación. El resultado de
las refactorizaciones implementadas es positivo y por lo tanto se valida el conjunto de cambios
especificados en los escenarios de esta segunda iteración del proceso.

5.6.6. Etapa 6: Evaluación


En esta iteración se abordó la Anomalía 3, que es probablemente, la más importante para los
objetivos de desarrollo del proyecto de SocialGraph. La evaluación de esta iteración es
altamente positiva. A partir de tener un mejor conocimiento del sistema y de realizar tanto un
análisis objetivo como subjetivo del proceso, se aprecia una mejora en la modificabilidad.

Si se suma el total de horas de desarrollo dedicadas sólo a la implementación de los


escenarios, se ve que se han invertido 81 horas hombre de trabajo, lo que representa
aproximadamente el trabajo de un desarrollador durante dos semanas a tiempo completo.

Se ha aplicado una segunda iteración del proceso definido sobre el caso de estudio. Esto
permitió conocer en profundidad las principales defectos de diseño e implementación del
sistema para aplicar refactorizaciones y obtener mejoras en la modificabilidad del sistema.

Dado el esfuerzo dedicado y el impacto positivo que se obtuvo, se resuelve no realizar una
tercera iteración del proceso y dar por terminada la actual instanciación del mismo.

105
6. Conclusiones
La refactorización es el proceso de modificar un programa para mejorar la calidad estructural
de su código fuente sin alterar su funcionalidad. Esos cambios se pueden dar de manera
aleatoria, no planificada o bien se pueden direccionar los esfuerzos para que la mejora de
calidad sea más valiosa. En este trabajo final se buscó organizar un conjunto de buenas
prácticas para brindar a los desarrolladores un proceso guiado por la modificabilidad del
sistema. Para ello, se combinaron distintas herramientas y metodologías en un proceso paso a
paso que facilita el seguimiento y la evaluación del proceso de refactorización.

A partir de las experiencias de distintos expertos en la materia, se llegó a la definición de un


proceso de refactorización basado en escenarios de modificabilidad y code smells. Para ello, se
documentó sistemáticamente un conjunto de iteraciones, etapas y actividades con la intención
de aplicarlas en el desarrollo de sistemas de software para mejorar su modificabilidad.

6.1. Evaluación de la solución


Para validar la definición del proceso, este fue instanciado en SocialGraph, un proyecto de
desarrollo real utilizado como caso de estudio. Esta experiencia deja algunas conclusiones
interesantes que pueden servir como aporte para otros proyectos de refactorización.

La implementación de refactorizaciones aleatorias sobre un sistema produce mejoras


aisladas, pero el uso de un proceso “racional” permite orientar los esfuerzos para que el
impacto se de sobre los aspectos de interés del proyecto. En el caso de estudio, el objetivo
principal fue elevar la calidad del sistema a partir de su modificabilidad y esto se logró
enmarcando el desarrollo en el proceso definido en este trabajo. Las sucesivas etapas de ese
proceso permitieron estudiar el sistema, detectar problemas, ponderarlos, proponer soluciones
e implementarlas de manera efectiva y planificada.

La etapa de análisis permitió detectar los principales problemas del sistema para luego idear
las refactorizaciones que se debían implementar. Por eso fue fundamental tener un buen
conocimiento del sistema, contar con documentación actualizada, valerse de métricas y otras
herramientas de análisis. En el caso de estudio, los requerimientos funcionales estuvieron
claros, además se contaba con cierto conocimiento de la arquitectura del sistema, aunque este
se debió profundizar y documentar a través de diagramas UML para comprender las
estructuras y el flujo de datos. El uso de code smells, sugerido por el proceso, fue esencial para
detectar síntomas de problemas en componentes clave de la arquitectura. Así se detectó el
grave acoplamiento entre artefactos que desde el punto de vista del diseño, quizás no debieran
estar tan vinculados. Este tipo de métricas son útiles cuando el sistema es relativamente
grande y no se tiene conocimiento de sus detalles de implementación.

El uso de escenarios de calidad para la definición de las refactorizaciones permitió describir,


en un lenguaje conocido por los desarrolladores, qué se debe hacer, qué artefactos se ven
afectados y cómo se valida el trabajo a realizar. Es una forma sencilla de definir el trabajo que
debe realizar un programador. En el caso de estudio se vió que la escala de los escenarios

106
definidos no fue homogénea, ya que en algunos casos se trató de refactorizaciones concretas a
nivel de código pero en otros se requirió profundizar el análisis del diseño incluso durante la
etapa de implementación. En general, esto varía según la complejidad del escenario y la
experiencia de los desarrolladores que lo implementan. En algunos casos se optó por dividir el
escenario en sub-escenarios de manera tal de minimizar el riesgo de sufrir un efecto dominó y
resolver el problema a través de la combinación de varias soluciones.

Una vez definidas las refactorizaciones, en la etapa de implementación se realizaron los


cambios en el sistema. La introducción de cambios no debe poner en riesgo la funcionalidad
del sistema, por eso en esta etapa se pudo determinar la importancia de contar con un testeo
robusto. En el caso de estudio, la presencia de casos de test era prácticamente nula por lo que,
siguiendo el proceso definido, antes de implementar cada escenario se debió garantizar una
cierta cobertura de tests de las áreas afectadas.

Finalmente, la medición de resultados permitió evaluar los avances de calidad obtenidos


luego de la implementación de uno o más escenarios. De esta manera, se validan las etapas
anteriores del proceso y se puede optar por seguir implementando escenarios e iteraciones del
proceso, por revisar algunos cambios realizados o bien por finalizar el proceso.

En conclusión, la instanciación de este proceso de refactorización guiado por escenarios de


calidad y code smells podría realizarse en cualquier otro sistema de software. Sin embargo, los
resultados podrán variar de acuerdo a las características de la arquitectura del sistema. En los
casos de sistemas cuyo proyecto de desarrollo cuente con documentación actualizada,
requerimientos funcionales claros, atributos de calidad definidos, buena cobertura de tests y
desarrolladores con conocimiento de la arquitectura; el proceso será más eficiente, ya que
estos artefactos facilitan el análisis, la detección de problemas y la implementación de
soluciones efectivas. En sistemas con menor grado de formalidad en su metodología de
desarrollo o arquitectura o escasa cantidad de artefactos, el proceso de refactorización será
más lento ya que habrá que conocer la arquitectura, documentar el proyecto para estudiar sus
debilidades, elicitar requerimientos y objetivos del refactoring, implementar casos de tests, etc.
De todas maneras, en esos casos el proceso también es aplicable y como resultado se logrará,
además de una mejora en la calidad del sistema, una mejora en la calidad del proceso de
desarrollo.

6.2. Aportes y beneficios


Con este trabajo se logró enmarcar un conjunto de buenas prácticas de desarrollo de
software en un proceso de refactorización orientado a mejorar la modificabilidad a partir de la
implementación de escenarios de calidad. Es un aporte a aquellos diseñadores y arquitectos
que deseen mejorar sus procesos de desarrollo a partir de la refactorización de áreas de código
de interés. La aplicación del proceso definido permite refactorizar un sistema para incorporar
nuevas funcionalidades de manera segura, planificada e integrada.

Si bien las refactorizaciones son pequeñas mejoras sobre el código de un sistema de


software, un conjunto de refactorizaciones guiadas por un proceso como el definido en este
trabajo conduce a una mejora notable de la calidad, en este caso de la modificabilidad. Los

107
beneficios de enmarcar los esfuerzos de desarrollo en este proceso permiten identificar mejor
las áreas prioritarias para aplicar las refactorizaciones y, como consecuencia de ese análisis,
sugerir soluciones acertadas a implementar.

Los resultados obtenidos a partir del caso de estudio son alentadores para seguir aplicando
este proceso para mejorar la calidad de otros sistemas y también del proceso en general. En su
definición, se especificó una serie de etapas y actividades, brindando un marco flexible para
que los analistas incorporen sus propias herramientas para el análisis y medición de resultados.

6.3. Limitaciones
A partir de la aplicación del proceso definido en el caso de estudio, se detectan algunas
limitaciones que es bueno tener en cuenta en otros casos de aplicación:

• Los escenarios de modificabilidad a veces no son lo suficientemente específicos como


para que un desarrollador sin conocimiento profundo del sistema pueda realizar las
tareas de manera directa. Si el desarrollador que implementa el escenario no es el
mismo que realizó el análisis, los tiempos estimados como medida de respuesta se
pueden extender. Esto se evidencia, por ejemplo, en el escenario 3.1: “Abstraer el
modelo para la construcción del grafo”, el cual fue necesario dividirlo en seis
subescenarios.

• Es difícil garantizar en un 100% la cobertura de tests para poder evitar posibles efectos
dominó al realizar una refactorización. Por ejemplo, en el escenario 3.1.6: “Refactorizar
God Class en GraphPersistenceHelper” se ejecutaron los tests correctamente pero días
más tarde, por casualidad, se detectó un error en el sistema que fue introducido durante
esta refactorización. Esta situación requirió dedicar tiempo a rastrear el origen del error,
corregirlo y además, volver revisar la forma en que se testea el sistema.

• El éxito de la instanciación de este proceso está ligado a las características del sistema
de software al que se aplica: la escala del sistema, la calidad de arquitectura y los
conocimientos del mismo por parte de los desarrolladores responsables del refactoring.
Estos factores son claves para el éxito en las etapas de análisis y especificación de
escenarios y pueden determinar la viabilidad de todo el proceso.

• La experiencia del caso de estudio demuestra que los code smells ayudan a detectar
síntomas de malas prácticas de implementación, sin embargo también se detectan
muchos falsos positivos que obligan a los desarrolladores a revisar cada uno de los
smells detectados. Para el diagnóstico de la arquitectura y el diseño no alcanza con el
análisis objetivo que puede hacerse sobre las métricas. También es imprescindible el
análisis subjetivo obtenido a partir de la experiencia de los desarrolladores en la
evaluación y toma de decisiones. Es necesario contar con uno o más desarrolladores
involucrados con la arquitectura del sistema y los objetivos del proyecto.

108
• Naturalmente, la realización de este proceso no soluciona todos los problemas de un
sistema en desarrollo, más bien ayuda a dedicar los recursos a mejoras estratégicas
para la evolución del sistema. Queda evidenciado que el refactoring debe ser un
proceso continuo en el tiempo, donde se mejore la calidad del software de manera
constante.

6.4. Trabajos futuros


Algunas ideas para desarrollar en posibles trabajos futuros:

• Seguramente el proceso puede ser mejorado en cada una de sus etapas y actividades.
Las técnicas, métodos, indicadores y herramientas utilizados pueden ser refinadas o
bien se pueden incorporar nuevas opciones que aporten a un proceso de mayor calidad.

• En este trabajo final, el proceso definido se ha aplicado apenas sobre un caso de


estudio y, como se ha visto, los resultados pueden variar de acuerdo al ambiente en que
se aplique el proceso. Por eso, sería bueno aplicar el proceso a otros casos de estudio
y profundizar el análisis de resultados obtenidos en este trabajo.

• Dado que el objetivo es mejorar la modificabilidad de un sistema, se podrían comprobar


los resultados a partir de experiencias en casos de estudio donde se midan la mejoras
de este atributo de calidad luego del desarrollo de nuevas funcionalidades, verificando
empíricamente la mejora de modificabilidad obtenida.

• Parte del análisis realizado en el proceso definido es denominado subjetivo, con lo cual
es difícil de medir y podría arrojar resultados dispares dependendiendo de quién o
quiénes lo lleven a cabo. A futuro sería bueno reforzar las herramientas que asisten a
los desarrolladores en la toma de decisiones para el análisis y la validación del proceso.

Cada experiencia de refactorización tiene sus particularidades, por lo que es deseable contar
con un proceso que pueda adaptarse a la mayor variedad de sistemas y metodologías de
desarrollo a fin de proveer un proceso consistente que garantice una mejora de calidad tanto a
nivel de código, como de diseño y de arquitectura.

109
7. Referencias
[Alshayeb2009] Alshayeb, M. (2009). Empirical investigation of refactoring effect on software
quality. Information and software technology, 51(9), 1319-1326.

[Bass2003] Bass, L., Clements, P., & Kazman, R. (2003). Software Architecture in Practice.

[Bavota2015] Bavota, G., De Lucia, A., Di Penta, M., Oliveto, R., & Palomba, F. (2015). An
Experimental Investigation on the Innate Relationship between Quality and Refactoring. Journal
of Systems and Software.

[Beck2000] Beck, K. (2000). Extreme programming explained: embrace change. Addison-


Wesley Professional.

[Bengtsson2000] Bengtsson, P., Lassing, N., Bosch, J., & van Vliet, H. (2000). Analyzing
software architectures for modifiability.

[Bertran2011] Bertran, I. M. (2011, May). Detecting architecturally-relevant code smells in


evolving software systems. In Proceedings of the 33rd International Conference on Software
Engineering (pp. 1090-1093). ACM.

[DuBois2004] Du Bois, B., Demeyer, S., & Verelst, J. (2004, November). Refactoring-
improving coupling and cohesion of existing code. In Reverse Engineering, 2004. Proceedings.
11th Working Conference on (pp. 144-151). IEEE.

[Elssamadisy2002] Elssamadisy, A., & Schalliol, G. (2002, May). Recognizing and responding
to bad smells in extreme programming. In Proceedings of the 24th International conference on
Software Engineering (pp. 617-622). ACM.

[Fowler1999] Fowler, M. (1999). Refactoring: improving the design of existing code.

[Fowler2009] TechnicalDebtQuadrant Martin Fowler, October 2009


http://martinfowler.com/bliki/TechnicalDebtQuadrant.html

[Frier2015] Frier, J. R., & Roggio, R. F. (2015). The Downsides of Software Refactoring.
Journal of Computer Science, 3(1), 01-13.

[Gamma1995] Patrones de Diseño. Erich Gamma, Richard Helm, Ralph Johnson, John
Vlissides, Addison Weasley. 1995.

[Garcia2009] Garcia, J., Popescu, D., Edwards, G., & Medvidovic, N. (2009, March).
Identifying architectural bad smells. In Software Maintenance and Reengineering, 2009.
CSMR'09. 13th European Conference on (pp. 255-258). IEEE.

[GATE] Biblioteca GATE. https://gate.ac.uk/

[ISISTAN] Instituto Superior de Ingeniería de Software Tandil http://www.isistan.unicen.edu.ar

110
[ISO9126] ISO/IEC 9126-1, Software engineering – product quality – Part 1: Quality Model,
first ed.: 2001-06-15.

[JSpIRIT] Java Smart Identification of Refactoring opportunITies


https://sites.google.com/site/santiagoavidal/projects/jspirit

[Kerievsky2005] Kerievsky, J. (2005). Refactoring to patterns. Pearson Deutschland GmbH.

[Kim2012] Kim, M., Zimmermann, T., & Nagappan, N. (2012, November). A field study of
refactoring challenges and benefits. In Proceedings of the ACM SIGSOFT 20th International
Symposium on the Foundations of Software Engineering (p. 50). ACM.

[Lanza2007] Lanza, M., & Marinescu, R. (2007). Object-oriented metrics in practice: using
software metrics to characterize, evaluate, and improve the design of object-oriented systems.
Springer Science & Business Media.

[Lehman2001] Lehman, M. M., & Ramil, J. F. (2001, September). An approach to a theory of


software evolution. In Proceedings of the 4th international workshop on Principles of software
evolution (pp. 70-74). ACM.

[Liu2006] Liu, J., Batory, D., & Lengauer, C. (2006, May). Feature oriented refactoring of
legacy applications. In Proceedings of the 28th international conference on Software
engineering (pp. 112-121). ACM.

[Liu2009] Liu, H., Yang, L., Niu, Z., Ma, Z., & Shao, W. (2009, August). Facilitating software
refactoring with appropriate resolution order of bad smells. In Proceedings of the the 7th joint
meeting of the European software engineering conference and the ACM SIGSOFT symposium
on The foundations of software engineering (pp. 265-268). ACM.

[Liu2012] Liu, H., Gao, Y., & Niu, Z. (2012, July). An initial study on refactoring tactics. In
Computer Software and Applications Conference (COMPSAC), 2012 IEEE 36th Annual (pp.
213-218). IEEE.

[Mäntylä2006] Mäntylä, M. V., & Lassenius, C. (2006). Subjective evaluation of software


evolvability using code smells: An empirical study. Empirical Software Engineering, 11(3), 395-
431.

[Mens2004] Mens, T., & Tourwé, T. (2004). A survey of software refactoring. Software
Engineering, IEEE Transactions on, 30(2), 126-139.

[MurphyHill2008] Murphy-Hill, E., & Black, A. P. (2008). Refactoring tools: Fitness for purpose.
Software, IEEE, 25(5), 38-44.

[Schulz2009] Schulz, C., Löwe, M., & König, H. (2009). Refactoring object-oriented systems.
Manipulation of Graphs, Algebras and Pictures. Essays Dedicated to Hans-Jörg Kreowski on
the Occasion of His 60th Birthday, Universität Bremen, 321-340.

111
[Shrivastava2008] Shrivastava, S. V., & Shrivastava, V. (2008, November). Impact of metrics
based refactoring on the software quality: A case study. In TENCON 2008-2008 IEEE Region 10
Conference (pp. 1-6). IEEE.

[Simon2001] Simon, F., Teinbruckner, F. S., & Lewerentz, C. (2001). Metrics based
refactoring. In Software Maintenance and Reengineering, 2001. Fifth European Conference on
(pp. 30-38). IEEE.

[Tourwé2003] Tourwé, T., & Mens, T. (2003, March). Identifying refactoring opportunities using
logic meta programming. In Software Maintenance and Reengineering, 2003. Proceedings.
Seventh European Conference on (pp. 91-100). IEEE.

[VanEmden2002] Van Emden, E., & Moonen, L. (2002). Java quality assurance by detecting
code smells. In Reverse Engineering, 2002. Proceedings. Ninth Working Conference on (pp.
97-106). IEEE.

[Wasserman1994] Wasserman, S., & Faust, K. (1994). Social network analysis: Methods and
applications (Vol. 8). Cambridge university press.

[WordCram] Biblioteca WordCram: http://wordcram.org/. Repositorio:


https://github.com/danbernier/WordCram/tree/master/src

112

También podría gustarte