• No results found

una "coma", un "punto y coma", un "dos puntos" y una "comilla simple".

4.1.1.5. PreProcessorComposite Este pre-procesador permite combinar varios pre- procesadores como si fueran uno solo, aplicando cada uno de acuerdo al orden en el que fueron pasados en el constructor.

4.1.2. Pre-procesadores Complejos

En la figura 7 se resume, en un diagrama de clases UML, el diseño de los pre- procesadores complejos. Estos pre-procesadores realizan transformaciones sobre los textos teniendo en cuenta cuestiones semánticas de los mismos.

Figura 7: Diagrama de clases de los pre-procesadores complejos

4.1.2.1. IfThenFormConverter Este preprocesador convierte todas las oraciones condicionales a la forma "if Q then P", donde Q es la cláusula condicional y P la cláusula consecuente. Para convertir estas frases, este pre-procesador usa instancias de IConditio- nalConverter, las cuales son las que realizan la conversión. Los posibles tipos de conver- siones se listan a continuación de acuerdo a cada IConditionalConverter.

OnlyIfConverter Este ConditionalConverter simplemente reemplaza el término "only if" por "if". Esto permite a otros ConditionalConverters trabajar de forma correcta o poder identificar el término condicional y consecuente de forma correcta. Tener en cuenta que este converter convierte a minúsculas para poder reconocer el término "only if".

InCaseConverter Al igual que el OnlyIfConverter, este ConditionalConverter re- emplaza el término "in case" por “if”

WhenConverter Al igual que los anteriores, este ConditionalConverter reemplaza el término "when" por "if".

EvenIfConverter Este ConditionalConverter simplemente reemplaza el término "even if" por "if".

IfConverter Este convertidor se encarga de agregar el "then" en las oraciones en las que falte, por ejemplo: "If it rains, i will close the window" por "If it rains then i will close the window"

4.1.2.2. PrepositionalBeginInverter Este pre-procesamiento se encarga de conver- tir todas las frases del tipo

<Preposition>,<Sentence> a

<Sentence><Preposition> Un ejemplo real podría ser el término

After login, the system should shutdown

En donde luego de aplicar este pre-procesador, el término sería reemplazado por the system should shutdown After login

Para la identificación de la frase preposicional y la oración se hizo uso de las herramientas provistas por la biblioteca de Stanford para Procesamiento de lenguaje natural[4]. 4.1.2.3. PronounPreProcessor Este pre-procesador se encarga de reemplazar los pronombres por los sustantivos a los que hace referencia.

Por ejemplo:

The car is blue. It is awesome. lo reemplazaría por

The car is blue. The car is awesome.

Este pre-procesador es útil ya que en muchos casos, los pronombres en sí no aportan mucha información, en cambio los sustantivos a los que referencia sí contienen más información que podría ser útil a la hora del análisis.

4.2.

Vector Generators

En la figura 8 se describen las clases e interfaces relacionadas a la vectorización de requerimientos y responsabilidades. Para darle flexibilidad al diseño, se decidió abstraer la interfaz a usar para generar los vectores. Esto permitiría optar por una técnica diferente a TF-IDF para generar los vectores sin impactar mucho en el resto de la implementación. Al hacerlo, se tuvo en cuenta que para poder generar los vectores(al menos en el caso de TF-IDF), es necesario pasarle el conjunto completo de documentos.

Figura 8: Diagrama de clases de las clases relacionadas a la vectorización de requerimientos y responsabilidades

En la implementación de la clase concreta LazyTF_IDF, se permitió modificar el conjunto de términos distintivos con los cuales se generan los vectores. Esto permite generar los vectores con un conjunto reducido de términos y de esta forma poder ignorar algunos de estos términos. Por ejemplo, se podrían usar únicamente los términos que no son artículos.

4.3.

Modelo

Al comienzo del desarrollo de la herramienta, se planteó el modelo usando POJOs5. Al principió nos fue útil como para comenzar a hacer pruebas relacionadas a la clasificación de requerimientos y detección de responsabilidades.

Al comenzar a trabajar en la detección de relaciones y asignación de responsabilidades a componentes, en donde se comenzó a investigar sobre ontologías, se decidió representar los distintos componentes del Use Case Maps usando ontologías. Esto permitiría hacer uso de los motores de inferencia de las ontologías durante la generación de los Use Case Maps.

Al no estar seguro de cual API de ontologías se iba a usar, se decidió modelar una abstracción del modelo aplicando el patrón Abstract Factory (https://en.wikipedia. org/wiki/Abstract_factory_pattern) como se muestra en la figura 9. De esta forma,

se usaría un IUCMFactory para instanciar los distintos componentes del Use Case Map y se mantendría desacoplado la implementación del modelo.

Figura 9: Diagrama de clases de la abstracción del modelo de Use Case Maps A la hora de elegir cual API para manejar Ontologías, se comenzó usando Apache Jena(https://jena.apache.org/). Inicialmente sirvió para definir los componentes bá- sicos, pero al seguir investigando sobre ontologías se descubrió que en realidad Jena sólo da soporte para RDF(Resource Description Framework). Esto generó que muchas de las

ventajas de las ontologías no estaban disponibles, por ejemplo definición de relaciones transitivas, inversas, etc.

Por este motivo se decidió comenzar a usar OWLAPI(http://owlapi.sourceforge. net/), el cual si provee soporte para OWL entre otras cosas. Por suerte, gracias a la aplicación del Abstract Factory, resulto sencillo migrar del modelo de Jena al modelo de OWLAPI. En la figura 10 se describe la versión final del modelo.

4.4.

DoubtResolver

Como se explicó en la sección 3, en muchos puntos del proceso es necesario reali- zar consultas al experto. Debido a que la herramienta debería soportar ser usada sobre distintas interfaces de usuario, fue necesario abstraer la forma de resolución de las dudas.

Figura 11: Diagrama de clases relacionado al manejo de resolución de preguntas Es por esto que se decidió delegar la responsabilidad de resolver las dudas abstrayendo dicho comportamiento en una interfaz DoubtResolver. En la figura 11 se detalla el diseño de dicha abstracción y en la figura 12 se detalla la forma en la que se implementó diferentes DoubtsResolvers para diferentes interfaces de usuario. Esta interfaz define la forma en la que los DoubtResolvers deberán resolver cada duda que surja durante el procesamiento.

Cada una de estas dudas se encuentra encapsulada en una clase Java que contiene tanto la descripción de la pregunta como las diferentes opciones que puede optar el experto. Cada una de estas opciones sigue el patrón de diseño Command, encapsulando una acción que se ejecutará o no de acuerdo a la resolución del DoubtResolver. Al resolver un Doubt, el DoubtResolver deberá retornar una de estas opciones la cual será ejecutada. En la figura 13 se describe la jerarquía de las opciones que se implementaron.

Figura 13: Diagrama de clases de las opciones

4.5.

RelationChecker

En varios puntos del proceso de análisis semántico, fue necesario comprobar si un cierto término se encontraba relacionado o no con alguna responsabilidad. Debido al desconocimiento de cómo se realizaría dicha comprobación, se decidió proveer de una interfaz común para todas las clases que se encargarían de esto permitiéndonos probar diversas técnicas con el menor impacto posible en el código.

Antes de comprobar si existía una relación o no, probablemente sería necesario realizar un pre-procesamiento a los términos. Esto llevó a implementar la clase PreProcessorRe- lationChecker, la cual hace uso de los pre-procesadores explicados en la sección 4.1 antes de analizar la relación entre el término y la responsabilidad con el uso de otro Relation- Checker.

Figura 14: Diagrama de clases de los RelationChecker

Por otro lado, se identificaron diversas formas de identificar la relación entre el tér- mino y la responsabilidad. Para mantener desacopladas cada una de ellas, se decidió apli- car el patrón Chain of Responsibilities (https://sourcemaking.com/design_patterns/ chain_of_responsibility). En esta, al intentar identificar si existe o no una relación, se probaría con cada una de las estrategias hasta que alguna de ellas pueda concluir si el término está relacionado a la responsabilidad o no. Esto llevó a implementar la clase abstracta ChainedRelationChecker, la cual deberán implementar cada una de las técnicas a usar para identificar si existe o no una relación.

En el caso de que la técnica determine que existe una relación(dentro de la función

check(String, IResponsibility)), deberá retornar “true”. En el caso de que determine que no existe una relación deberá retornar “false”. En ambos casos al retornar un valor distinto de null, la cadena devolverá dicho resultado. Si la técnica no pudiera determinar si existe o no una relación, deberá retornar null, generando que se pregunte al siguiente eslabón en la cadena.

En la figura 14 se detallan todos los RelationCheckers disponibles. A continuación se explican brevemente cada uno de ellos.

4.5.1. ForbiddenWordsChainedRelationChecker

Este ChainedRelationChecker determina si el término no se encuentrá relacionado con la responsabilidad. Para esto, el mismo contiene una lista de palabras prohibidas. En el caso de que alguna de las palabras del término se encontrara en dicha lista, se determinará que el término no se encuentra relacionado a la responsabilidad.

4.5.2. AllWordsIncludedChainedRelationChecker

AllWordsIncludedChainedRelationChecker devolverá “true” sólo en el caso que todas las palabras de la responsabilidad se encuentran contenidas en el término. Por ejemplo, si se desea saber si la responsabilidad “update user profile” se encuentra relacionada al término “The system must update the user profile”, este RelationChecker retornará “true”, afirmando la existencia de dicha relación.

4.5.3. SimpleContainsChainedRelationChecker

En este ChainedRelationChecker, se considera que existe una relación si alguna de las palabras de la responsabilidad se encuentra contenida en el término. Por ejemplo , si se desea saber si la responsabilidad “show user profile” se encuentra relacionada al término “The system should visualize the profile”, este RelationChecker retornará “true”. 4.5.4. JenaChainedRelationChecker

Este RelationChecker determinará que una responsabilidad esta relacionada al término sólo si alguna de las palabras del mismo pertenece a la misma taxonomía, dentro de una ontología en particular, que alguna de las palabras de la responsabilidad. En la sección 3.5 se explicó con más detalle su funcionamiento.

4.6.

Pipeline

Siguiendo la idea del pipeline propuesto en el enfoque, se decidió dividir el proceso en 5 componentes, cada uno consumiendo la salida del anterior y brindando la entrada del siguiente.

Inicialmente, dado un InputStream, elRequirementsLoader(Sección 4.6.1) proveerá de distintas fuentes los requerimientos a ser analizados. Una vez obtenidos, se pasarán al RequirementsClassifier(Sección 4.6.2) en donde estos serán clasificados para obtener sólo los requerimientos funcionales. Luego, estos requerimientos funcionales se usarán como entrada para elResponsibilitiesExtractor(Sección 4.6.3) en donde se analizará el contenido de los textos, haciendo uso del procesamiento del lenguaje natural para extraer las responsabilidades del sistema. Seguido a esto, las responsabilidades serán usadas por el SemanticAnalyzer(Sección 4.6.4) en donde se aplicarán las técnicas descritas en la sección 3.4 para generar el mapa de casos de uso. Finalmente, el Use Case Map generado se exportará a través delUCMExporter(Sección 4.6.5) para exportar el mapa de casos de uso a un formato que sea de utilidad para el usuario.

Figura 15: Diagrama de componentes y conectores general del sistema 4.6.1. RequirementsLoader

En primera instancia nos pareció correcto abstraer la forma en la que se obtienen los requerimientos. El principal motivo de esto fue permitir adaptar la herramienta de forma sencilla para que se integre con otras herramientas. Por ejemplo, se podría integrar la herramienta para que lea los requerimientos desde el plugin de Eclipse Mylyn, en donde este ya posee conectores para varios de los Issue Trackers disponibles en el mercado.

En la figura 16 se detalla en un diagrama de clases UML el diseño general de los RequirementsLoaders.

Figura 16: Diagrama de clases de la materialización del componente RequirementsLoader Para abstraer dicho comportamiento, se implementó una interfaz común que deberán respetar las diferentes clases que permitan obtener requerimientos. Para nuestra herra- mienta se desarrollaron 3 RequirementsLoaders:

CSVLoader: Este RequirementsLoader lee los requerimientos de un InputStream con formato CSV. En éste, la primera columna se corresponde con el nombre del requerimiento, la segunda se corresponde con la descripción del requerimiento y la tercera con la clasificación del mismo.

NFRLoader: Este RequirementsLoader está preparado para leer los archivos NFRR6.

InputLoader: Este RequirementsLoader lee los requerimientos a través de línea de comandos. Un wizzard guía al usuario sobre los datos que deberá ingresar. Este RequirementsLoader fue desarrollado más bien con propósitos de pruebas durante el desarrollo de la herramienta.

4.6.2. RequirementsClassifier

Este componente es el encargado de realizar la clasificación de los requerimientos(como se explicó en la sección 3.2). Para abstraer la forma en la que se clasificarán los reque- rimientos, se definió una interfaz común llamada RequirementClassifier. Las clases que sirvan para llevar al cabo la clasificación deberán respetar dicha interfaz.

En la herramienta se implementó un único RequirementClassifier, el WekaRequire- mentClassifier. Este RequirementClassifier clasificará los requerimientos usando un clasi- ficador existente en la biblioteca provista por Weka. Dicho clasificador deberá ser pasado

por parámetro a la hora de llamar al constructor. En el mismo se instanciarán los pre- procesadores usados antes de la clasificación sobre los requerimientos. Luego se procederá al entrenamiento del clasificador pasado por parámetro.

Figura 17: Diagrama de clases de la materialización del componente Requirements Clas- sifier

Como se muestra en la figura 17, para pre-procesar los requerimientos se decidió usar SpacesFixer, PunctuationMarksRemover y LowerCaseTransformer. El funcionamiento de los mismos se detalla en la sección 4.1.

Para convertir los requerimientos a vectores se decidió usar un LazyTF_IDF Vector- Generator. El mismo se detalla en la sección 4.2.

4.6.3. ResponsibilitiesExtractor

Este componente es el encargado de identificar las responsabilidades contenidas en los requerimientos funcionales(como se detalló en la sección 3.3). En la figura 18 se describe la implementación del extractor de responsabilidades. En este caso, también se decidió mantener desacoplado el proceso de extracción a través de una interfaz común.

Figura 18: Diagrama de clases del extractor de responsabilidades

Para extraer las responsabilidades de los requerimientos, se implementó la clase Stan- fordExtractor. La misma implementa la interfaz ResponsibilityExtractor. Se le dio este nombre debido a que hace uso de las bibliotecas provistas por Stanford para procesamiento del lenguaje natural para identificar las responsabilidades.

Esta clase hace uso de pre-procesadores que dan formato a los textos de los reque- rimientos para mejorar los resultados de la extracción. También posee un Predicate en- cargado de detectar cuando el formato de un requerimiento da lugar a la extracción de responsabilidades potencialmente dudosas. En la figura 19 se describe la jerarquía de clases de los predicates.

Figura 19: Diagrama de clases de la jerarquía Predicate

En los casos que el Predicate encuentre que una responsabilidad se considera dudosa, se procederá a preguntar al experto si dicha responsabilidad es válida o no. Para ello se hace uso del DoubtResolver.

Por último, es importante detallar que, como se explicó en la sección 4.3, se hizo uso del AbstractFactory para crear las responsabilidades detectadas.

4.6.4. SemanticAnalyzer

Como se explica en la sección 3.4, una vez identificadas las responsabilidades el si- guiente paso es el de realizar el análisis semantico. Luego de analizar cuales serían las responsabilidades del SemanticAnalyzer, se observó que el mismo se encontraba sobre- cargado. En este se deberían aplicar diferentes técnicas de reconocimiento de relaciones y diferentes técnicas de asignación de responsabilidades a componentes. Al pensar en qué atributos de calidad se buscaban, decidimos priorizar la modificabilidad y extensibilidad de las técnicas a aplicar sobre los Use Case Maps.

Siguiendo con la idea del Pipeline, decidimos descomponer el SemanticAnalyzer si- guiendo un estilo Pipe and Filters. De esta forma cada filtro sería capaz de procesar un Use Case Map, modificando de acuerdo a la técnica que implemente y cada pipe conecta- ría dichos filtros. Luego de dicha descomposición, el SemanticAnalyzer pasaría a llamarse UseCaseMapGenerator y dentro del mismo habría varios SemanticAnalyzers(que cumpli- rían el rol de Filters dentro del patrón). En la figura 20 se detalla el resultado de dicha descomposición.

Figura 20: Diagrama de clases del analizador semántico

Siguiendo esta descomposición, la materialización del diseño anterior se puede visua- lizar en el diagrama de clases de la figura 21. Para ella se definió una interfaz común que deberán cumplir las clases que representarían a los SemanticAnalyzer de la figura 20.

Figura 21: Diagrama de clases de la materialización del análisis semántico Estos SemanticAnalyzers estarán contenidos en la clase AnalyzersUseCaseMapGene- rator dentro de una lista ordenada. Dicha clase será la encargada de pasar los Use Case Maps analizados de un SemanticAnalyzer a Otro.

Con este diseño, nos fue fácil encapsular cada una de las técnicas explicadas en la sección 3.4 dentro de un SemanticAnalyzer. Esto dio lugar a las clases detalladas en la figura 22.

Figura 22: Diagrama de clases de los SemanticAnalyzers

El funcionamiento de cada una se resume en las sub-secciones 4.6.4.2, 4.6.4.1, 4.6.4.3, 4.6.4.5 y 4.6.4.4.

4.6.4.1. ConditionalPhrasesAnalyzer El ConditionalPhrasesAnalyzer es la mate- rialización de la técnica explicada en la sección 3.4.1. Como se detalla en la figura 23, este es un SemanticAnalyzer que hace uso de un DoubtResolver, para los casos en los que se detecta más de una relación en alguna frase como se explicó en la sección 3.6.2; un RelationChecker, para comprobar si los términos condicionales o consecuentes hacen referencia a alguna responsabilidad en particular; un PreProcessorOperator, y en parti- cular un IfThenFormConverter. Este pre-procesador es de suma importancia ya que el algoritmo podrá identificar la parte condicional y consecuente sólo si la frase completa sigue el formato “if Q then C”.

Figura 23: Diagrama de clases del ConditionalPhrasesAnalyzer

Al analizar el Use Case Map, este SemanticAnalyzer recorrerá una a una las responsa- bilidades, extrayendo la oración de la cual se obtuvieron. Por cada una de estas oraciones, se aplicará el pre-procesamiento dejándola de la forma “if Q then C”. Luego usará al RelationChecker para buscar Responsabilidades en Q y en C y por cada responsabilidad encontrada en C, le asignará una dependencia con todas las relaciones encontradas en Q. En el caso que para una única oración se detectara mas de una relación, se preguntará al DoubtResolver cúales de esas relaciones son válidas.

4.6.4.2. WordTimeAnalyzer En este SemanticAnalyzer se implementó la técnica descrita en la sección 3.4.2. Como muestra la figura 24, el mismo hace uso de un Doub- tResolver, el cual sirve para validar cúales relaciones son válidas en el caso de que se detectara más de una relación; un PreProcessorOperator que realiza pre-procesamiento básico como convertir las oraciones a minúsculas; un RelationChecker, para buscar men- ciones de responsabilidades en la oración luego de haber encontrado alguna palabra en pasado.

Figura 24: Diagrama de clases del WordTimeAnalyzer

Este SemanticAnalyzer recorre uno a uno los requerimientos y por cada frase busca verbos en tiempo pasado. Al encontrar uno, usa al RelationChecker para ver si ese verbo hace mención a alguna responsabilidad. De hacerlo, usa al mismo RelationChecker para buscar menciones de otras responsabilidades contenidas en la oración y de hacerlo, creará una relación entre ésta y la relacionada con el verbo en pasado. En el caso de encon-

Related documents