Download escuela polit´ecnica nacional - Repositorio Digital
Document related concepts
no text concepts found
Transcript
ESCUELA POLITÉCNICA NACIONAL FACULTAD DE INGENIERÍA DE SISTEMAS DESARROLLO DE UNA LIBRERÍA UTILITARIA FUNCIONAL SOBRE UN LENGUAJE ORIENTADO A OBJETOS, APLICADA A UN CASO DE ESTUDIO PROYECTO PREVIO A LA OBTENCIÓN DEL TÍTULO DE INGENIERO EN SISTEMAS INFORMÁTICOS Y DE COMPUTACIÓN SEBASTIÁN ESTRELLA HEREDIA [email protected] DIRECTOR: ING. ENRIQUE ANDRÉS LARCO AMPUDIA, MSc. [email protected] Quito, abril 2016 DECLARACIÓN Yo, Sebastián Estrella Heredia, declaro bajo juramento que el trabajo aquı́ descrito es de mi autorı́a; que no ha sido previamente presentada para ningún grado o calificación profesional; y, que he consultado las referencias bibliográficas que se incluyen en este documento. A través de la presente declaración cedo mis derechos de propiedad intelectual correspondientes a este trabajo, a la Escuela Politécnica Nacional, según lo establecido por la Ley de Propiedad Intelectual, por su Reglamento y por la normatividad institucional vigente. Sebastián Estrella Heredia I CERTIFICACIÓN Certifico que el presente trabajo fue desarrollado por Sebastián Estrella Heredia, bajo mi supervisión. Ing. Enrique Andrés Larco Ampudia, MSc. Director del Proyecto II AGRADECIMIENTOS A mi esposa, por su amor y apoyo incondicional durante todo este largo camino recorrido juntos, no habrı́a podido superar todos los obstáculos sin su apoyo y comprensión. A mi madre, por su ejemplo de fortaleza y voluntad a lo largo de toda su vida, lo cual me a servido de ejemplo para superar todos los desafı́os. A mis hermanas, por su alegrı́a cariño, gracias por siempre llenarme de alegrı́a y saber escucharme en aquellos momentos que lo necesitaba. A mis tı́os y tı́as, por cuidarme como uno más de sus hijos y saber darme sus mejores consejos y guiarme por el camino correcto. A mis primos y primas, por ser mis compañeros de travesuras y ser la nueva generación que nos llena de alegrı́a. A mis suegros, por abrirme las puertas de su hogar y corazón, como uno más de sus hijos. A mis cuñados y cuñada, por su alegrı́a y coraje para siempre seguir adelante y saber luchar las batallas más difı́ciles. III DEDICATORÍA En memoria de mis abuelos, gracias por todo su amor y cariño incondicional. IV CONTENIDO Resumen 1 Presentación 2 1. Marco Teórico 3 1.1. Definición del Paradigma de Programación Funcional . . . . . . . . . 3 1.1.1. Caracterı́sticas Generales . . . . . . . . . . . . . . . . . . . . . 3 1.1.2. Lenguaje de Programación Haskell . . . . . . . . . . . . . . . . 7 1.2. Definición del Paradigma de Programación Orientado a Objetos . . . 8 1.2.1. Caracterı́sticas Generales . . . . . . . . . . . . . . . . . . . . . 8 1.2.2. Lenguaje de Programación Java . . . . . . . . . . . . . . . . . 12 1.3. Comparación entre Paradigmas Funcional y Orientado a Objetos . . . 13 1.3.1. Comparación entre Paradigmas . . . . . . . . . . . . . . . . . 13 1.3.2. Comparación entre Lenguajes . . . . . . . . . . . . . . . . . . 15 1.3.3. Análisis de Resultados . . . . . . . . . . . . . . . . . . . . . . . 17 1.4. Estilo de Programación Funcional sobre un Lenguaje Orientado a Objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 V VI 1.4.1. Patrones de Diseño Funcionales . . . . . . . . . . . . . . . . . 18 1.4.2. Estado del Arte . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 2. Desarrollo de la Librerı́a Utilitaria Funcional 50 2.1. Análisis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 2.1.1. Retrocompatibilidad . . . . . . . . . . . . . . . . . . . . . . . . 55 2.1.2. Limitaciones del Lenguaje de Programación Java . . . . . . . . 56 2.1.3. Análisis de Portabilidad . . . . . . . . . . . . . . . . . . . . . . 62 2.2. Diseño . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 2.2.1. Estructura de Código . . . . . . . . . . . . . . . . . . . . . . . 68 2.2.2. Comportamiento . . . . . . . . . . . . . . . . . . . . . . . . . . 71 2.2.3. Documentación del Código . . . . . . . . . . . . . . . . . . . . 74 2.3. Desarrollo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 2.3.1. Desarrollo Guiado por Pruebas . . . . . . . . . . . . . . . . . . 76 2.3.2. Integración Continua . . . . . . . . . . . . . . . . . . . . . . . . 79 2.3.3. Técnicas Empleadas . . . . . . . . . . . . . . . . . . . . . . . . 81 2.4. Pruebas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 2.4.1. Niveles de Pruebas . . . . . . . . . . . . . . . . . . . . . . . . 86 2.4.2. Tipos de Pruebas . . . . . . . . . . . . . . . . . . . . . . . . . 88 2.4.3. Planificación de Pruebas . . . . . . . . . . . . . . . . . . . . . 90 2.4.4. Cobertura de Pruebas . . . . . . . . . . . . . . . . . . . . . . . 94 VII 2.4.5. Pruebas de Mutación . . . . . . . . . . . . . . . . . . . . . . . 95 2.4.6. Ejemplos de Pruebas . . . . . . . . . . . . . . . . . . . . . . . 97 2.4.7. Reporte de Pruebas . . . . . . . . . . . . . . . . . . . . . . . . 101 3. Aplicación de la Librerı́a Funcional a un Caso de Estudio 3.1. Refactorización Funcional de una Aplicación Orientada a Objetos 103 . . 103 3.1.1. Selección del Caso de Estudio . . . . . . . . . . . . . . . . . . 103 3.1.2. Refactorización del Caso de Estudio . . . . . . . . . . . . . . . 112 3.2. Análisis de Resultados . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 3.2.1. Complejidad Ciclomática (CC) . . . . . . . . . . . . . . . . . . 119 3.2.2. Comparación de Resultados . . . . . . . . . . . . . . . . . . . 120 4. Conclusiones y Recomendaciones 123 4.1. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 4.2. Recomendaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 Siglas 126 Glosario 126 Biblografı́a 129 Anexos 133 .1. Capturas de Pantalla de GitHub . . . . . . . . . . . . . . . . . . . . . . 133 .2. Capturas de Pantalla de JIRA . . . . . . . . . . . . . . . . . . . . . . . 134 LISTADO DE TABLAS 1.1. Caracterı́sticas Principales de Haskell . . . . . . . . . . . . . . . . . . 8 1.2. Caracterı́sticas de Java . . . . . . . . . . . . . . . . . . . . . . . . . . 13 1.3. Comparación entre Paradigmas . . . . . . . . . . . . . . . . . . . . . . 15 1.4. Comparación entre Lenguajes . . . . . . . . . . . . . . . . . . . . . . 17 1.5. Estilos de Programación Funcional en OOP . . . . . . . . . . . . . . . 18 1.6. Caracterı́sticas de Rust . . . . . . . . . . . . . . . . . . . . . . . . . . 32 1.7. Caracterı́sticas de Clojure . . . . . . . . . . . . . . . . . . . . . . . . . 35 1.8. Caracterı́stica de Elixir . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 1.9. Caracterı́sticas de Scala . . . . . . . . . . . . . . . . . . . . . . . . . . 39 1.10.Caracterı́sticas de Erlang . . . . . . . . . . . . . . . . . . . . . . . . . 41 1.11.Caracterı́sticas de OCaml . . . . . . . . . . . . . . . . . . . . . . . . . 43 1.12.Caracterı́sticas de functional.js . . . . . . . . . . . . . . . . . . . . . . 44 1.13.Caracterı́sticas de Javaslang . . . . . . . . . . . . . . . . . . . . . . . 45 1.14.Caracterı́sticas de functional-ruby . . . . . . . . . . . . . . . . . . . . 46 1.15.Caracterı́sticas de functional-php . . . . . . . . . . . . . . . . . . . . . 47 VIII IX 1.16.Caracterı́sticas de Underscore.js . . . . . . . . . . . . . . . . . . . . . 48 2.1. Product Backlog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 2.2. Sprint #1 - Spring Backlog . . . . . . . . . . . . . . . . . . . . . . . . . 53 2.3. Sprint #2 - Spring Backlog . . . . . . . . . . . . . . . . . . . . . . . . . 54 2.4. Sprint #3 - Spring Backlog . . . . . . . . . . . . . . . . . . . . . . . . . 54 2.5. Proceso de Ejecución - Función Suma . . . . . . . . . . . . . . . . . . 61 2.6. Análisis de Portabilidad . . . . . . . . . . . . . . . . . . . . . . . . . . 67 2.7. Niveles de Pruebas Utilizados . . . . . . . . . . . . . . . . . . . . . . . 88 2.8. Tipos de Pruebas Utilizados . . . . . . . . . . . . . . . . . . . . . . . . 90 2.9. Categorı́as de Pruebas - Pruebas de Compatibilidad . . . . . . . . . . 98 2.10.Categorı́as de Pruebas - Cobertura de Pruebas . . . . . . . . . . . . . 99 2.11.Categorı́as de Pruebas - Pruebas de Mutación . . . . . . . . . . . . . 100 2.12.Categorı́as de Pruebas - Pruebas de Unitarias . . . . . . . . . . . . . 101 3.1. Interpretación de Métricas . . . . . . . . . . . . . . . . . . . . . . . . . 105 3.2. Número de Lı́neas de Código por Clase . . . . . . . . . . . . . . . . . 113 3.3. Comparación de la Complejidad Ciclomática . . . . . . . . . . . . . . 121 3.4. Comparación de Resultados . . . . . . . . . . . . . . . . . . . . . . . 122 LISTADO DE FIGURAS 1.1. Curso Universidad de Edinburgh . . . . . . . . . . . . . . . . . . . . . 24 1.2. Curso Universidad de Nottingham . . . . . . . . . . . . . . . . . . . . 25 1.3. Curso Universidad de Oxford . . . . . . . . . . . . . . . . . . . . . . . 25 1.4. Curso Universidad Tecnológica Nacional . . . . . . . . . . . . . . . . . 26 1.5. Curso Universidad de los Andes . . . . . . . . . . . . . . . . . . . . . 26 1.6. Curso Universidad de Oklahoma . . . . . . . . . . . . . . . . . . . . . 27 1.7. Haxl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 1.8. Swift . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 1.9. F# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 1.10.Intel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 1.11.Whatsapp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 1.12.SumAll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 2.1. Release Burndown . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 2.2. Uso de las Versiones de JDK . . . . . . . . . . . . . . . . . . . . . . . 56 2.3. Número de Módulos Importados . . . . . . . . . . . . . . . . . . . . . 68 X XI 2.4. Estructura de Paquetes . . . . . . . . . . . . . . . . . . . . . . . . . . 71 2.5. Diagrama de Actividad - Maybe.maybe . . . . . . . . . . . . . . . . . . 72 2.6. Diagrama de Actividad - Maybe.isNothing . . . . . . . . . . . . . . . . 73 2.7. Diagrama de Actividad - Maybe.isJust . . . . . . . . . . . . . . . . . . 74 2.8. Ejemplo de Javadoc . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 2.9. Captura de Pantalla de Hackage . . . . . . . . . . . . . . . . . . . . . 75 2.10.Ciclo de Vida - TDD . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 2.11.Diagrama de Actividad - Maybe.isNothing . . . . . . . . . . . . . . . . 77 2.12.Captura de Pantalla de Bitbucket . . . . . . . . . . . . . . . . . . . . . 80 2.13.Captura de Pantalla de Shippable . . . . . . . . . . . . . . . . . . . . 81 2.14.Diagrama de Flujo - Planificación de Pruebas . . . . . . . . . . . . . . 94 2.15.Flujo de Trabajo Pruebas de Mutación . . . . . . . . . . . . . . . . . . 96 2.16.Pruebas de Compatibilidad . . . . . . . . . . . . . . . . . . . . . . . . 98 2.17.Resumen de Cobertura de Pruebas . . . . . . . . . . . . . . . . . . . 99 2.18.Resumen de Cobertura de Pruebas . . . . . . . . . . . . . . . . . . . 99 2.19.Mutantes Activos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 2.20.Reporte de Pruebas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 3.1. Repositorio de JUnit en GitHub . . . . . . . . . . . . . . . . . . . . . . 104 3.2. Top 5 Librerı́as de Java más Utilizadas . . . . . . . . . . . . . . . . . . 106 3.3. Posibles Casos de Estudio (Datos Obtenidos) . . . . . . . . . . . . . . 107 XII 3.4. Posibles Casos de Estudio (Datos Normalizados) . . . . . . . . . . . . 109 3.5. Posibles Casos de Estudio (Datos Ajustados) . . . . . . . . . . . . . . 111 3.6. Criterio de Selección . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 1. Repositorio de Google Guava . . . . . . . . . . . . . . . . . . . . . . . 133 2. Repositorio de JUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 3. Repositorio de Log4j . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 4. Repositorio de SLF4J . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 5. Seguimiento de Incidentes de Commons BCEL . . . . . . . . . . . . . 134 6. Seguimiento de Incidentes de Commons BeanUtils . . . . . . . . . . . 134 7. Seguimiento de Incidentes de Commons BSF . . . . . . . . . . . . . . 135 8. Seguimiento de Incidentes de Commons Chain . . . . . . . . . . . . . 135 9. Seguimiento de Incidentes de Commons CLI . . . . . . . . . . . . . . 135 10. Seguimiento de Incidentes de Commons Codec . . . . . . . . . . . . 136 11. Seguimiento de Incidentes de Commons Collections . . . . . . . . . . 136 12. Seguimiento de Incidentes de Commons Compress . . . . . . . . . . 136 13. Seguimiento de Incidentes de Commons Configuration 14. Seguimiento de Incidentes de Commons CSV . . . . . . . . 137 . . . . . . . . . . . . . 137 15. Seguimiento de Incidentes de Commons Daemon . . . . . . . . . . . 137 16. Seguimiento de Incidentes de Commons Dbcp . . . . . . . . . . . . . 138 17. Seguimiento de Incidentes de Commons Dbutils . . . . . . . . . . . . 138 XIII 18. Seguimiento de Incidentes de Commons Digester . . . . . . . . . . . 138 19. Seguimiento de Incidentes de Commons Discovery . . . . . . . . . . 139 20. Seguimiento de Incidentes de Commons EL . . . . . . . . . . . . . . . 139 21. Seguimiento de Incidentes de Commons Email . . . . . . . . . . . . . 139 22. Seguimiento de Incidentes de Commons Exec . . . . . . . . . . . . . 140 23. Seguimiento de Incidentes de Commons FileUpload . . . . . . . . . . 140 24. Seguimiento de Incidentes de Commons Functor . . . . . . . . . . . . 140 25. Seguimiento de Incidentes de Commons Imaging . . . . . . . . . . . . 141 26. Seguimiento de Incidentes de Commons IO . . . . . . . . . . . . . . . 141 27. Seguimiento de Incidentes de Commons JCI . . . . . . . . . . . . . . 141 28. Seguimiento de Incidentes de Commons JCS . . . . . . . . . . . . . . 142 29. Seguimiento de Incidentes de Commons Jelly . . . . . . . . . . . . . . 142 30. Seguimiento de Incidentes de Commons JEXL . . . . . . . . . . . . . 142 31. Seguimiento de Incidentes de Commons JXPath . . . . . . . . . . . . 143 32. Seguimiento de Incidentes de Commons JXPath . . . . . . . . . . . . 143 33. Seguimiento de Incidentes de Commons Lang . . . . . . . . . . . . . 143 34. Seguimiento de Incidentes de Commons Launcher . . . . . . . . . . . 144 35. Seguimiento de Incidentes de Commons Log4j . . . . . . . . . . . . . 144 36. Seguimiento de Incidentes de Commons Logging . . . . . . . . . . . . 144 37. Seguimiento de Incidentes de Commons Math . . . . . . . . . . . . . 145 38. Seguimiento de Incidentes de Commons Modeler . . . . . . . . . . . 145 XIV 39. Seguimiento de Incidentes de Commons Net . . . . . . . . . . . . . . 145 40. Seguimiento de Incidentes de Commons OGNL . . . . . . . . . . . . 146 41. Seguimiento de Incidentes de Commons Pool . . . . . . . . . . . . . . 146 42. Seguimiento de Incidentes de Commons Primitives . . . . . . . . . . . 146 43. Seguimiento de Incidentes de Commons Proxy . . . . . . . . . . . . . 147 44. Seguimiento de Incidentes de Commons SCXML . . . . . . . . . . . . 147 45. Seguimiento de Incidentes de Commons Validator . . . . . . . . . . . 147 46. Seguimiento de Incidentes de Commons VFS . . . . . . . . . . . . . . 148 47. Seguimiento de Incidentes de Commons VFS . . . . . . . . . . . . . . 148 48. Seguimiento de Incidentes de Commons Weaver . . . . . . . . . . . . 148 LISTADO DE FRAGMENTOS DE CÓDIGO 1.1. Ejemplo de Función de Orden Superior . . . . . . . . . . . . . . . . . 4 1.2. Ejemplo de Función Pura . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.3. Ejemplo de Función Impura . . . . . . . . . . . . . . . . . . . . . . . . 5 1.4. Ejemplo de Datos Inmutables . . . . . . . . . . . . . . . . . . . . . . . 5 1.5. Ejemplo de Datos Mutables . . . . . . . . . . . . . . . . . . . . . . . . 6 1.6. Ejemplo de Referencia Transparencial . . . . . . . . . . . . . . . . . . 6 1.7. Ejemplo Contrario de Referencia Transparencial . . . . . . . . . . . . 7 1.8. Ejemplo de Encapsulamiento . . . . . . . . . . . . . . . . . . . . . . . 9 1.9. Ejemplo de Herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 1.10.Ejemplo de Polimorfismo . . . . . . . . . . . . . . . . . . . . . . . . . 11 1.11.Ejemplo de Composición . . . . . . . . . . . . . . . . . . . . . . . . . 12 1.12.Ejemplo de Encapsulamiento Estático . . . . . . . . . . . . . . . . . . 19 1.13.Extracción de Código (Encapsulamiento Estático) . . . . . . . . . . . 20 1.14.Refactorización utilizando Encapsulamiento Estático . . . . . . . . . . 21 1.15.Ejemplo de Objeto como Contenedor . . . . . . . . . . . . . . . . . . 22 1.16.Ejemplo de Código como Datos . . . . . . . . . . . . . . . . . . . . . . 23 1.17.Ejemplo de Lı́nea de Comandos . . . . . . . . . . . . . . . . . . . . . 23 1.18.Ejemplo de Rust . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 1.19.Ejemplo de Clojure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 1.20.Ejemplo de Elixir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 1.21.Ejemplo de Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 1.22.Ejemplo de Erlang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 1.23.Ejemplo de OCaml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 1.24.Ejemplo de functional.js . . . . . . . . . . . . . . . . . . . . . . . . . . 44 XV XVI 1.25.Ejemplo de Javaslang . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 1.26.Ejemplo de functional-ruby . . . . . . . . . . . . . . . . . . . . . . . . 47 1.27.Ejemplo de functional-php . . . . . . . . . . . . . . . . . . . . . . . . . 48 1.28.Ejemplo de Underscore.js . . . . . . . . . . . . . . . . . . . . . . . . . 49 2.1. Efecto Secundario Explı́cito . . . . . . . . . . . . . . . . . . . . . . . . 57 2.2. Efecto Secundario Implı́cito . . . . . . . . . . . . . . . . . . . . . . . . 57 2.3. Evaluación Perezosa - Haskell . . . . . . . . . . . . . . . . . . . . . . 58 2.4. Evaluación Perezosa - Java . . . . . . . . . . . . . . . . . . . . . . . . 58 2.5. Ejemplo de Función de Orden Superior . . . . . . . . . . . . . . . . . 59 2.6. Función Suma en Haskell . . . . . . . . . . . . . . . . . . . . . . . . . 59 2.7. Función Suma en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 2.8. Ejemplo de Uso Función Suma en Haskell . . . . . . . . . . . . . . . . 60 2.9. Ejemplo de Uso Función Suma en Java . . . . . . . . . . . . . . . . . 60 2.10.Ejemplo de Aplicación Parcial de Funciones . . . . . . . . . . . . . . . 62 2.11.Módulo Maybe en Haskell . . . . . . . . . . . . . . . . . . . . . . . . . 69 2.12.Interfaz Maybe en Java . . . . . . . . . . . . . . . . . . . . . . . . . . 69 2.13.Módulo Maybe en Java . . . . . . . . . . . . . . . . . . . . . . . . . . 70 2.14.Ejemplo de Uso - Maybe.maybe . . . . . . . . . . . . . . . . . . . . . 71 2.15.Ejemplo de Uso - Maybe.isNothing . . . . . . . . . . . . . . . . . . . . 72 2.16.Ejemplo de Uso - Maybe.isJust . . . . . . . . . . . . . . . . . . . . . . 73 2.17.Prueba isNothingReturnsTrue . . . . . . . . . . . . . . . . . . . . . . . 78 2.18.Primera Iteración isNothing . . . . . . . . . . . . . . . . . . . . . . . . 78 2.19.Prueba isNothingReturnsFalse . . . . . . . . . . . . . . . . . . . . . . 79 2.20.Segunda Iteración isNothing . . . . . . . . . . . . . . . . . . . . . . . 79 2.21.Estructura de Datos Función . . . . . . . . . . . . . . . . . . . . . . . 82 2.22.Ejemplo de Función de Orden Superior . . . . . . . . . . . . . . . . . 82 2.23.Función Parcialmente Aplicada . . . . . . . . . . . . . . . . . . . . . . 83 2.24.Ejemplo de Uso Función de Orden Superior . . . . . . . . . . . . . . . 83 2.25.Ejemplo de Currying . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 2.26.Ejemplo de Uso de Currying . . . . . . . . . . . . . . . . . . . . . . . . 84 2.27.Ejemplo de Aplicación Parcial de Funciones . . . . . . . . . . . . . . . 85 2.28.Proceso de Construcción de Gradle . . . . . . . . . . . . . . . . . . . 91 XVII 2.29.Ejemplo de Prueba Unitaria . . . . . . . . . . . . . . . . . . . . . . . . 101 3.1. Método fail Antes de la Refactorización . . . . . . . . . . . . . . . . . 113 3.2. Método fail Después de la Refactorización . . . . . . . . . . . . . . . 114 3.3. Método equalsRegardingNull Antes de la Refactorización . . . . . . . 115 3.4. Método equalsRegardingNull Después de la Refactorización . . . . . 115 3.5. Método doubleIsDifferent Antes de la Refactorización . . . . . . . . . 116 3.6. Método floatIsDifferent Antes de la Refactorización . . . . . . . . . . . 116 3.7. Método doubleIsDifferent Después de la Refactorización . . . . . . . 117 3.8. Método floatIsDifferent Después de la Refactorización . . . . . . . . . 117 3.9. Método isDifferent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 3.10.Método formatClassAndValue Antes de la Refactorización . . . . . . . 118 3.11.Método formatClassAndValue Déspues de la Refactorización . . . . . 118 3.12.Método doubleIsDifferent . . . . . . . . . . . . . . . . . . . . . . . . . 120 RESUMEN En el capı́tulo 1, se presenta una breve introducción sobre los paradigmas de programación funcionales y orientados a objetos, al igual que una introducción sobre los lenguajes de programación Haskell y Java. Una vez definidos dichos paradigmas y lenguajes de programación, se procedió a realizar una comparación entre ellos. En el capı́tulo 2, se definió el alcance de la librerı́a desarrollada, basándose en algunos criterios, como son las limitaciones del lenguaje y la compatibilidad con versiones anteriores de la JVM (desde la versión 1.6 en adelante). Una vez definido el alcance de la librerı́a se procedió a la implementación utilizando la técnica de desarrollo guiado por pruebas (TDD). En el capı́tulo 3, se seleccionó a JUnit como caso de estudio utilizando criterios de selección previamente definidos: porcentaje de uso, número de problemas reportados y número de solicitudes de cambio. Una vez seleccionado el caso de estudio, se refactorizó el código utilizando la librerı́a desarrollada en el capı́tulo 2, y se midió los resultados obtenidos en término de la complejidad ciclomática, encontrando una mejora mayor al 50 %. Finalmente el capı́tulo 4, contiene las conclusiones y recomendaciones encontradas durante la realización del proyecto. 1 PRESENTACIÓN La naturaleza del software es evolutiva, ya que se debe ajustar a las necesidades definidas por los usuarios. Dichos ajustes deben ser realizado con frecuencia, lo cual depende directamente del diseño del software. Un software altamente acoplado será difı́cil de mantener, ya que cualquier cambio causarı́a una serie de efectos en cascada, volviendo las tareas de mantenimiento y corrección de errores complicadas. El paradigma funcional realiza una clara separación entre datos y funcionalidad lo cual permite desarrollar aplicaciones bajamente acopladas, estos conceptos fueron aplicados dentro de una librerı́a funcional para desarrollar aplicaciones fáciles de mantener, lo cual justificarı́a el desarrollo del proyecto. Con el fin de cuantificar las mejoras obtenidas al utilizar la librerı́a desarrollada, se procedió a refactorizar un caso de estudio, el cual fue seleccionado bajo un conjunto de métricas de selección. Una vez seleccionado el caso de estudio, se procedió a evaluar la complejidad ciclomática de la librerı́a antes y después de la refactorización. Los resultados obtenidos indicaron la reducción de la complejidad ciclomática en un 50 %. 2 CAPÍTULO 1. MARCO TEÓRICO 1.1 DEFINICIÓN DEL PARADIGMA DE PROGRAMACIÓN FUNCIONAL La programación funcional inicia con LISP, sin embargo el nombre de paradigma no fue introducido sino hasta 1.977 por John Backus en su paper ganador del Premio Turing llamado Can Programming Be Liberated From the von Neumann Style? A Functional Style and Its Algebra of Programs [5]. Backus menciona múltiples puntos de vista acerca de construir aplicaciones como combinaciones de ecuaciones algebraicas. En la programación funcional los programas son ejecutados mediante la evaluación de expresiones, en contraste con la programación imperativa en donde los programas están compuestos de instrucciones que cambian el estado global cuando se ejecutan. Tı́picamente, la programación funcional evita utilizar estados mutables [7]. 1.1.1 CARACTERÍSTICAS GENERALES A continuación un listado de algunas de las caracterı́sticas principales de la programación funcional: 3 4 Funciones de Orden Superior Son funciones que toman como argumentos a otras funciones. Las funciones de orden superior son útiles para refactorizar código y reducir el número de repeticiones dentro del código [33]. 1 > map ( + 1 ) [ 1 , 2 , 3 ] 2 [2 , 3 , 4] Fragmento de Código 1.1: Ejemplo de Función de Orden Superior En el fragmento de código 1.1 anterior la función map toma como argumento a la función (+1), la cual es aplicada a cada uno de los elementos de la lista [1, 2, 3] obteniendo como resultado [2, 3, 4]. Puridad Algunos lenguajes de programación permiten expresiones que lanzan acciones adicionalmente a retornar valores, este tipo de acciones se lo conoce como efectos secundarios. Los lenguajes que prohı́ben los efectos secundarios son conocidos como lenguajes puros [18]. A continuación dos fragmentos de código 1.2 y 1.3, los cuales retornan el mismo resultado, sin embargo uno de ellos tiene un efecto secundario: 1 sum : : I n t −> I n t −> I n t 2 sum x y = x + y Fragmento de Código 1.2: Ejemplo de Función Pura 5 1 public i n t sum ( i n t x , i n t y ) { 2 System . o u t . p r i n t l n ( ” Running . . . ” ) ; 3 return x + y ; 4 } Fragmento de Código 1.3: Ejemplo de Función Impura Como se puede observar en el fragmento de código 1.3, la función sum no solo retorna el resultado de la suma; en la lı́nea de código 2 se imprime un mensaje de texto en la pantalla, lo cual es considerado un efecto secundario debido a que cambia el estado inicial de la pantalla, a diferencia del fragmento de código 1.2 en donde sólo se retorna el resultado de la suma. Datos Inmutables Un dato inmutable por definición es aquel que no puede ser modificado, en la programación funcional al trabajar con valores inmutables cada modificación crea una copia del valor original dejando intacto el valor original [14]. A continuación dos fragmentos de código 1.4 y 1.5, en los cuales se puede apreciar la diferencia entre eliminar un elemento de una lista inmutable en Haskell y una mutable en Java: 1 > l e t xs = [ 1 , 2 , 3 ] 2 [1 , 2 , 3] 3 > d e l e t e 1 xs 4 [2 , 3] 5 > xs 6 [1 , 2 , 3] Fragmento de Código 1.4: Ejemplo de Datos Inmutables 6 1 > L i s t <I n t e g e r > xs = [ 1 , 2 , 3 ] ; 2 [1 , 2 , 3] 3 > xs . remove ( 1 ) ; 4 [2 , 3] 5 > xs ; 6 [2 , 3] Fragmento de Código 1.5: Ejemplo de Datos Mutables Como se puede observar en el fragmento de código 1.5, la lista original definida en la lı́nea de código 1, después de eliminar el elemento 1 ha sido modificada, esto se puede evidenciar en la lı́nea de código 7. Referencia Transparencial Las operaciones puras siempre retornan el mismo resultado cada vez que son invocadas, a está propiedad se la conoce como referencia transparencial [20]. A continuación dos fragmentos código 1.6 y 1.7 en donde se puede observar la diferencia entre una función con referencia transparencial y una sin referencia transparencial: 1 > 1 + 2 2 3 3 > 1 + 2 4 3 Fragmento de Código 1.6: Ejemplo de Referencia Transparencial 7 1 > System . c u r r e n t T i m e M i l l i s ( ) ; 2 1375025861 3 > System . c u r r e n t T i m e M i l l i s ( ) ; 4 1375025862 Fragmento de Código 1.7: Ejemplo Contrario de Referencia Transparencial Como se puede observar en el fragmento de código 1.7, la función currentTimeMillis siempre retorna un resultado diferente, esto se considera una violación al principio de referencia transparencial. 1.1.2 LENGUAJE DE PROGRAMACIÓN HASKELL Haskell es un avanzado lenguaje de programación puramente funcional. Un producto vanguardista Open-Source con más de 20 años de investigación, permite un desarrollo de software rápido robusto, conciso, y correcto. Posee un alto soporte de integración con otros lenguajes, construido con concurrencia y paralelismo, debuggers, perfiles, grandes librerı́as y una comunidad muy activa. Haskell permite fácilmente producir software flexible, mantenible y de alta calidad [21]. Caracterı́sticas Principales La tabla 1.1 a continuación lista las principales caracterı́sticas del lenguaje de programación Haskell: Caracterı́stica Descripción Tipado Estático Cuando se compila un programa, el compilador conoce que parte del código es un número, cual es una cadena de texto y ası́ sucesivamente. Esto significa que muchos posibles errores son capturados al momento de compilación. 8 Caracterı́stica Descripción Evaluación Perezosa Significa que al menos que no se indique explı́citamente, Haskell no ejecutará funciones y calculará valores hasta que sea forzado a mostrar un resultado. Tabla 1.1: Caracterı́sticas Principales de Haskell 1.2 DEFINICIÓN DEL PARADIGMA DE PROGRAMACIÓN ORIENTADO A OBJETOS El paradigma orientado a objetos no es sólo un estilo de programación, también es un método de diseño para la construcción de sistemas. Los lenguajes orientados a objetos permiten al programador especificar unidades autónomas en forma de objetos, los cuales son construidos de datos y los métodos u operaciones que pueden ser ejecutados sobre el objeto. En la programación orientada a objetos un objeto contiene datos y provee operaciones (o métodos) para acceder a los datos. Los datos en un objeto no son visibles directamente, sólo a través de operaciones. En otras palabras las operaciones son la interfaz de un objeto para los usuarios del objeto. 1.2.1 CARACTERÍSTICAS GENERALES A continuación una breve recopilación, sobre las principales caracterı́sticas de la programación orientado a objetos encontrados en la mayorı́a de lenguajes de programación pertenecientes a este paradigma: 9 Encapsulamiento Procedimiento utilizado para ocultar detalles de implementación de una clase hacia el exterior, también es utilizado para restringir el acceso de otras clases a ciertos atributos y métodos definidos [22]. A continuación un fragmento de código 1.8 en donde la clase User solo expone métodos de lectura: 1 public class User { 2 private String firstName ; 3 p r i v a t e S t r i n g lastName ; 4 5 public User ( S t r i n g f i r s t N a m e , S t r i n g lastName ) { 6 this . firstName = firstName ; 7 t h i s . lastName = lastName ; 8 } 9 10 public S t r i n g getFirstName ( ) { 11 12 return firstName ; } 13 14 public S t r i n g getLastName ( ) { 15 16 r e t u r n lastName ; } 17 } Fragmento de Código 1.8: Ejemplo de Encapsulamiento Como se puede observar en el fragmento de código 1.8, la clase User solo expone métodos a otras clases para poder acceder a sus atributos firstName y lastName, impidiendo que estos atributos sean modificados desde fuera de la clase. 10 Herencia La herencia de clases puede es utilizada para reutilizar atributos y métodos definidos en una clase padre, por lo general cuando se tiene una relación del tipo is-a es una buena opción usar herencia de clases [22]. 1 public class B i c y c l e { . . . } 2 3 public class MountainBike extends B i c y c l e { . . . } Fragmento de Código 1.9: Ejemplo de Herencia Como se puede observar en el fragmento de código 1.9, la clase MountainBike es una especialización de la clase Bicycle, por lo tanto se utiliza herencia para representar está relación. Polimorfismo Dos clases se consideran polimórficas, si por lo menos unos de sus métodos define la misma interfaz, es decir tanto el nombre del método como los tipos de datos recibidos como argumentos y el tipo de dato de retorno son los mismos [22]. El polimorfismo es utilizado para definir métodos los cuales se comporten diferente dependiendo el contexto de ejecución. A continuación un fragmento de código 1.10: 11 1 public i n t e r f a c e Shape { 2 void draw ( ) ; 3 } 4 5 public class C i r c l e implements Shape { 6 public void draw ( ) { . . . } 7 } 8 9 public class Square implements Shape { 10 public void draw ( ) { . . . } 11 } Fragmento de Código 1.10: Ejemplo de Polimorfismo Como se puede observar en el fragmento de código 1.10, las clases Circle y Square pueden ser dibujadas mediante la implementación del método draw, sin embargo cada una de las clases será dibujado de manera diferente dependiendo la figura a la cual representan. Composición La composición de clases, consiste en construir una clase compleja a partir de pequeñas clases, con el fin de poder separar responsabilidades en diferentes clases [22]. A continuación un fragmento de código 1.11 de ejemplo: 12 1 public class Engine { . . . } 2 3 public class Car { 4 p r i v a t e Engine engine ; 5 6 public Car ( Engine engine ) { 7 8 t h i s . engine = engine ; } 9 } Fragmento de Código 1.11: Ejemplo de Composición Como se puede observar en el fragmento de código 1.11, la clase Car se encuentra compuesta de un atributo engine del tipo Engine, es decir la clase Car conoce sobre la existencia de un motor, sin embargo no conoce sobre los detalles de su implementación. 1.2.2 LENGUAJE DE PROGRAMACIÓN JAVA Es un lenguaje orientado a objetos multipropósito utilizado para el desarrollo de diversas aplicaciones, desde aplicaciones de escritorio hasta aplicaciones móviles [32]. Caracterı́sticas Generales La tabla 1.2 a continuación lista las caracterı́sticas más destacadas encontradas dentro del lenguaje de programación Java: 13 Caracterı́stica Descripción Independencia de Plataforma Java provee un middleware de software, el cual permite que el código escrito en este lenguaje sea portable a través de diferentes infraestructuras de hardware. Portabilidad El bytecode generado por el compilador de Java puede ser importado a cualquier plataforma. Multihilos En Java se pueden escribir programas que manejen múltiples tareas al mismo tiempo definiendo varios hilos. La principal ventaja de utilizar múltiples hilos es que la memoria es compartida. Distribuido Las tecnologı́as de Java Remote Method Invocation (RMI) y Enterprise Java Beans (EJB) son utilizados para la creación de aplicaciones distribuidas, permitiendo acceder archivos llamando a métodos de otra máquina a través de la red. Tabla 1.2: Caracterı́sticas de Java 1.3 COMPARACIÓN ENTRE PARADIGMAS FUNCIONAL Y ORIENTADO A OBJETOS Una vez descritas tanto las caracterı́sticas del paradigma funcional en la sección 1.1, como las caracterı́sticas del paradigma orientado a objectos en la sección 1.2, se procederá a realizar un análisis comparativo de dichos paradigmas al igual que sus lenguajes Haskell y Java respectivamente. 1.3.1 COMPARACIÓN ENTRE PARADIGMAS La tabla 1.3 a continuación realiza un análisis comparativo entre los diferentes paradigmas de programación expuestos anteriormente en las secciones 1.1 y 1.2: 14 Caracterı́stica Funciones de FP OOP Sı́ cumple No cumple Orden Superior Análisis Comparativo No todos los lenguajes orientados a objetos soportan esta caracterı́stica, sólo aquellos que incluyen funciones de primer orden. Puridad Sı́ cumple No cumple En los lenguajes orientados a objetos no existe ninguna restricción referente a tener efectos secundarios. Datos Inmuta- Sı́ cumple No cumple bles La programación orientada a objetos se basa en el cambio de estado de un objeto por lo cual al menos que los datos sean definidos explı́citamente como inmutables está caracterı́stica no está disponible. Referencia Sı́ cumple No cumple Transparencial Una misma función pura retorna siempre un mismo valor, sin embargo en lenguajes orientados a objetos debido a los efectos secundarios una función puede verse influenciada por efectos externos que cambien su resultado. Encapsulamiento Sı́ cumple Sı́ cumple En lenguajes funcionales se puede realizar tanto encapsulamiento de funciones como de estructura de datos, mientras que en los lenguajes orientados a objetos se pueden realizar a nivel de clase, métodos y atributos. Herencia No cumple Sı́ cumple En los lenguajes funcionales no existe herencia de objetos ya que se trabaja con estructuras de datos. 15 Caracterı́stica Polimorfismo FP OOP Sı́ cumple Sı́ cumple Análisis Comparativo En lenguajes orientados a objetos existe polimorfismo a nivel de clases, mientras que en lenguajes funcionales existen polimorfismos de estructuras de datos. Composición Sı́ cumple Sı́ cumple En los lenguajes funcionales no solo se puede componer estructuras de datos, también es posible realizar composición de funciones. Tabla 1.3: Comparación entre Paradigmas 1.3.2 COMPARACIÓN ENTRE LENGUAJES Una vez realizada la comparación entre los paradigmas de programación Functional Programming (FP) (1.1) y Object Oriented Programming (OOP) (1.2), se procederá a realizar la comparación entre los lenguajes de programación Haskell (1.1.2) y Java (1.2.2): 16 Caracterı́stica Puramente Haskell Java Sı́ cumple No cumple Funcional Análisis Comparativo Haskell es un lenguaje puramente funcional, es decir el desarrollador mediante programación declarativa indica al computador cómo realizar una acción en lugar de escribir instrucciones secuenciales. En Java los programas deben describir detalladamente los pasos que el computador debe realizar para cumplir el objetivo. Tipado Estático Sı́ cumple Sı́ cumple Ambos lenguajes son fuertemente tipados, el compilador analiza el código fuente comprobando que los tipos definidos sean correctos, además no existe colisión de tipos, es decir, la conversión de un tipo de dato a otro debe ser explı́cita. Evaluación Pe- Sı́ cumple No cumple rezosa En Java cualquier expresión es evaluada explı́citamente, es decir si se tiene una lista de 20 elementos y se desea imprimir los 10 primeros elementos, Java necesita evaluar la lista completa primero, antes de imprimir los deseados. En Haskell esto no sucede solo se evaluarán los 10 primeros elementos a ser impresos. Independencia de Plataforma Sı́ cumple Sı́ cumple Tanto Haskell como Java son independientes de plataforma. 17 Caracterı́stica Multihilos Haskell Java Sı́ cumple Sı́ cumple Análisis Comparativo Los dos lenguajes soportan procesamiento multihilos. Distribuidos Sı́ cumple Sı́ cumple Ambos lenguajes son de propósito general y funcionan en ambientes distribuidos. Tabla 1.4: Comparación entre Lenguajes 1.3.3 ANÁLISIS DE RESULTADOS Una vez realizada la comparación entre paradigmas y lenguajes de programación en las secciones 1.3.1 y 1.3.2, se procederá con el análisis de los resultados obtenidos: La inmutabilidad de datos provista por defecto en lenguajes funcionales como Haskell, permite construir aplicaciones concurrentes de manera natural sin considerar la concurrencia de datos como parte del diseño ya que no existen cambios de estado. La evaluación perezosa de expresiones permite gestionar los recursos de una manera efectiva, ya que cualquier evaluación se realiza bajo demanda mejorando la velocidad de ejecución y disminuyendo el consumo de recursos. Tanto la puridad como la referencia transparencial explı́cita definida en lenguajes como Haskell, permite tener una clara separación entre funciones puras y aquellas con efectos secundarios, esto mejora tanto la lectura como la escritura de pruebas. 18 1.4 ESTILO DE PROGRAMACIÓN FUNCIONAL SOBRE UN LENGUAJE ORIENTADO A OBJETOS En la actualidad existen varios lenguajes de programación los cuales incluyen caracterı́sticas de la programación funcional, la tabla 1.5 a continuación lista algunos de estos lenguajes y sus caracterı́sticas: Lenguaje Caracterı́sticas Ruby Las listas en Ruby proveen funciones de orden superior para la transformación de listas como map y reduce. JavaScript En JavaScript las funciones son objetos de primer orden, es decir una función puede ser asignada a una variable, o utilizada como argumentos de otra función. Java (versión 8 en adelante) Incluye funciones de orden superior y métodos para transformar colecciones de una manera funcional, como por ejemplo map y reduce. C# / Visual Basic (LINQ [29]) Es un lenguaje integrado de consulta, incluye caracterı́sticas para la consulta de listas y se extiende a bases de datos. Tabla 1.5: Estilos de Programación Funcional en OOP 1.4.1 PATRONES DE DISEÑO FUNCIONALES A pesar que el paradigma funcional es diferente del orientado a objetos, es posible programar en lenguajes orientados a objetos utilizando un estilo de programación funcional. Los patrones de diseño a continuación han sido tomados del libro Becoming Functional [4]. 19 Encapsulamiento Estático Con el fin de explicar de una manera práctica el patrón de diseño, se utilizará el siguiente fragmento de código 1.12: 1 public class Contact { 2 private String firstName ; 3 p r i v a t e S t r i n g lastName ; 4 private S t r i n g email ; 5 6 public void sendEmail ( ) { 7 sendEmail ( ” To : ” + e m a i l + ” \ n S u b j e c t : My S u b j e c t \ nBody : 8 ... ”); } 9 10 p r i v a t e void sendEmail ( S t r i n g c o n t e n t ) { . . . } 11 } Fragmento de Código 1.12: Ejemplo de Encapsulamiento Estático Como se puede observar en el fragmento de código 1.12, la clase Contact posee las siguientes responsabilidades: Contiene los datos referentes al usuario. Es encargado de enviar correos electrónicos al usuario. A simple vista parece no existir ninguna violación al principio de responsabilidad simple ya que ambas responsabilidades están relacionadas al usuario, sin embargo la manera en la cual se envı́e un correo electrónico debe ser transparente a la clase Contact. 20 La clase Contact necesita enviar un correo electrónico sin conocer sobre detalles de su implementación; es por ello que toda lógica relacionada a enviar un correo electrónico será extraı́da a otra clase. El fragmento de código 1.13, a continuación emplea la técnica de encapsulamiento estático para extraer toda la lógica referente a enviar un correo electrónico a la clase Email: 1 public class Email { 2 p r i v a t e S t r i n g address ; 3 private String subject ; 4 p r i v a t e S t r i n g body ; 5 6 public s t a t i c void send ( Email e m a i l ) { 7 ... 8 } 9 } Fragmento de Código 1.13: Extracción de Código (Encapsulamiento Estático) Una vez encapsulada la lógica relacionada a enviar correos electrónicos en la clase Email, se puede proceder a refactorizar la clase Contact de la siguiente manera: 21 1 public class Contact { 2 private String firstName ; 3 p r i v a t e S t r i n g lastName ; 4 private S t r i n g email ; 5 6 public void sendEmail ( ) { 7 Email . send (new Email ( email , ”My S u b j e c t ” , ”My Body ” ) ) ; 8 } 9 } Fragmento de Código 1.14: Refactorización utilizando Encapsulamiento Estático Como se puede observar en el fragmento de código 1.14 la clase Contact desconoce sobre los detalles de la implementación relacionados a enviar un correo electrónico, ya que toda está responsabilidad ha sido delegada a la clase Email. Objeto como Contenedor Este patrón de diseño es utilizado para encapsular el comportamiento de una función dentro de atributos de una clase, permitiendo ası́ modificar el comportamiento de la función [4]. Tomando como referencia la clase Email definida anteriormente 1.13, se puede modificar el comportamiento del método send agregando un nuevo atributo a la clase Email como se muestra en el fragmento de código 1.15 a continuación: 22 1 public class Email { 2 p r i v a t e boolean dearReader ; 3 4 public s t a t i c void send ( Email e m a i l ) { 5 i f ( e m a i l . isDearReader ( ) ) { 6 sendDearEmail ( e m a i l ) ; 7 } else { 8 sendEmail ( e m a i l ) ; 9 10 } } 11 12 p r i v a t e s t a t i c void sendDearEmail ( Email e m a i l ) { . . . } 13 14 p r i v a t e s t a t i c void sendEmail ( Email e m a i l ) { . . . } 15 } Fragmento de Código 1.15: Ejemplo de Objeto como Contenedor Como se puede observar en el fragmento de código 1.15 todos los atributos relacionados a enviar un correo electrónico han sido encapsulados dentro de la clase Email. Código como Datos Uno de los puntos claves dentro de la programación funcional es tratar a las funciones como datos, es decir una función puede ser asignada a una variable o ser pasada como argumento a otra función, está caracterı́stica permite reutilizar código a un nivel granular [4]. Tomando como punto de partida el siguiente fragmento de código 1.16: 23 1 public class CommandLineOption { 2 private String description ; 3 p r i v a t e Runnable f u n c t i o n ; 4 } Fragmento de Código 1.16: Ejemplo de Código como Datos Como se puede observar en el fragmento de código 1.16, la clase CommandLineOption tiene como atributos description el cual describe la opción de la lı́nea de comando, y atributo function el cual representa la acción realizada por la opción de la lı́nea de comandos. Utilizando la clase CommandLineOption definida anteriormente se puede construir una lı́nea de comandos de la siguiente manera: 1 public class CommandLine { 2 public Map<S t r i n g , CommandLineOption> o p t i o n s ( ) { 3 Map<S t r i n g , CommandLineOption> o p t i o n s = new HashMap <>() ; 4 options . put ( ” c ” , createUser ( ) ) ; 5 options . put ( ” d ” , deleteUser ( ) ) ; 6 return options ; 7 } 8 9 p r i v a t e CommandLineOption c r e a t e U s e r ( ) { . . . } 10 p r i v a t e CommandLineOption d e l e t e U s e r ( ) { . . . } 11 } Fragmento de Código 1.17: Ejemplo de Lı́nea de Comandos Como se puede observar en fragmento de código 1.17, se crea una instancia de la clase CommandLineOption para representar cada una de las acciones disponibles 24 en la lı́nea de comandos. 1.4.2 ESTADO DEL ARTE La presente sección es una breve recopilación sobre el estado actual de la programación funcional en diversos campos: Universidad Empresa Al igual que un listado de lenguajes y librerı́as funcionales, utilizados en la actualidad. Universidad Durante las últimos años, las universidades han ido incorporando en su pénsum de estudios cursos referentes a la programación funcional. A continuación un listado de algunos de los cursos de programación funcional, dictados alrededor del mundo: Paı́s Reino Unido Universidad Universidad de Edinburgh Instructor Philip Wadler Curso Informatics 1 - Functional Programming Año 2015 Figura 1.1: Curso Universidad de Edinburgh Fuente: http://www.inf.ed.ac.uk/teaching/courses/inf1/fp 25 Paı́s Estado Unidos Universidad Universidad de Nottingham Instructor Neil Ghani Curso Functional Programming Año 2015 Figura 1.2: Curso Universidad de Nottingham Fuente: http://www.cs.nott.ac.uk/ gmh/afp.html Paı́s Reino Unido Universidad Universidad de Oxford Instructor Jonathan Hill Curso Functional Programming for the Integrated Graduate Development Programme in Software Engineering at Oxford University Año 2015 Figura 1.3: Curso Universidad de Oxford Fuente: http://www.comlab.ox.ac.uk/igdp/text/course06.html 26 Paı́s Argentina Universidad Universidad Tecnológica Nacional Instructor Estudiantes Curso Paradigmas de Programación Año 2015 Figura 1.4: Curso Universidad Tecnológica Nacional Fuente: http://www.pdep.com.ar Paı́s Colombia Universidad Universidad de los Andes Instructor Jorge Ricardo Cuellar Curso Programación Funcional y sus Aplicaciones Año 2015 Figura 1.5: Curso Universidad de los Andes Fuente: http://matematicas.uniandes.edu.co/eventos/2015/haskell 27 Paı́s Estados Unidos Universidad Universidad de Oklahoma Instructor Rex Page Curso Introduction to Computer Programming Año 1997 Figura 1.6: Curso Universidad de Oklahoma Fuente: http://www.cs.ou.edu/ rlpage/fpclassSpring97 Empresa Empresas alrededor del mundo como Facebook y Apple, han empezado a utilizar lenguajes funcionales tanto para el desarrollo de nuevos productos, como para la creación de herramientas de desarrollo. Empresa Facebook Proyecto Haxl Descripción Es una librerı́a que simplifica el acceso remoto a datos. Entre las principales caracterı́sticas se encuentran: batch de múltiples peticiones a una misma fuente de datos, solicitar datos de múltiples fuentes de datos de manera concurrente y cache de solicitudes anteriores. Año 2014 Figura 1.7: Haxl Fuente: https://code.facebook.com/projects/854888367872565/haxl 28 Empresa Apple Proyecto Swift Descripción Es un lenguaje de programación desarrollado por Apple, a pesar de no ser puramente funcional como Haskell, posee algunos conceptos de la programación funcional como son: funciones de orden, funciones como ciudadanos de primer orden y funciones anónimas. Año 2015 Figura 1.8: Swift Fuente: https://developer.apple.com/swift Empresa Microsoft Proyecto F# Descripción Es un lenguaje de programación desarrollado por Microsoft como parte de .NET framework, entre las principales caracterı́sticas se encuentran: inmutabilidad de datos, aplicación parcial de funciones, expresiones de objetos, entre otros. Año 2005 Figura 1.9: F# Fuente: http://fsharp.org 29 Empresa Intel Proyecto Compilador de Haskell Descripción Intel ha desarrollado un compilador en Haskell, como parte de su investigación en paralelismo multinúcleo a escala. Año 2013 Figura 1.10: Intel Fuente: http://www.leafpetersen.com/leaf/publications/hs2013/hrc-paper.pdf Empresa Whatsapp Proyecto Servicio de Mensajerı́a Descripción Whatsapp tiene un promedio de 8 billones de mensajes entrantes y 12 billones de mensajes de salida por dı́a, esto es posible gracias al uso de Erlang y FreeBSD. Año 2013 Figura 1.11: Whatsapp Fuente: https://www.erlang-solutions.com/about/news/erlang-powered-whatsappexceeds-200-million-monthly-users 30 Empresa SumAll Proyecto Procesamiento de Datos Descripción Agrega varios flujos de datos de redes sociales. Se encuentran el proceso de reescribir su backend en Haskell. Lo que nos atrajo sobre el lenguaje es la disciplina y su aproximación para resolver problemas difı́ciles y complejos de manejar. Año 2014 Figura 1.12: SumAll Fuente: https://sumall.com Lenguajes de Programación La presente sección contiene un listado de lenguajes de programación funcionales, utilizados tanto en la academia como en la construcción de aplicaciones. Rust Rust es un lenguaje de programación de sistemas rápido el cual garantiza seguridad en la memoria y ofrece concurrencia sin complicaciones (sin condiciones de carrera). No utiliza un recolector de basura y tiene un costo mı́nimo en tiempo de ejecución [37]. Caracterı́sticas Descripción Hilos sin Condiciones de Carrera La caracterı́stica de seguridad de memoria de Rust se aplica a la historia de concurrencia. Los programas en Rust deben ser seguros de memoria, y no tener condiciones de carrera, el sistema de tipos de Rust se encuentra listo para está tarea. 31 Caracterı́sticas Descripción Genéricos Basados en Traits Un trait es una caracterı́stica del lenguaje la cual notifica al compilador de Rust que el tipo debe ser provisto, por el contexto en donde se ejecute la función. Búsqueda de Patrones Al igual que otros lenguajes funcionales, Rust posee la capacidad de comprobar si una determinada secuencia de datos coincide con algún patrón definido. Inferencia de Tipos Rust posee la capacidad de deducir automáticamente el tipo de un dato a partir de una expresión. 32 Caracterı́sticas Descripción Bindings Eficientes en C Los Foreign Function Interface (FFI) flexibles de Rust proveen bindings de C eficientes los cuales permiten exponer e invocar código de Rust sin ningún costo extra. Esto permite reescribir una aplicación módulo a módulo, una transición paulatina hacia una mejor experiencia de desarrollo. Tabla 1.6: Caracterı́sticas de Rust A continuación un ejemplo de código 1.18 escrito en Rust tomado de la página oficial [37]: 1 f o r token i n program . chars ( ) { 2 match token { 3 ’ + ’ => accumulator += 1 , 4 ’− ’ => accumulator −= 1 , 5 ’ ∗ ’ => accumulator ∗= 2 , 6 ’ / ’ => accumulator / = 2 , 7 8 => { / ∗ i g n o r e e v e r y t h i n g e l s e ∗ / } } 9 } Fragmento de Código 1.18: Ejemplo de Rust Clojure Clojure es un dialecto de Lisp, comparte con Lisp la filosofı́a de código como datos y un sistema poderoso de macros. Clojure es un lenguaje de programación funcional predominante, y cuenta con basto conjunto de estructuras de datos persistentes inmutables. Cuando datos mutables son necesitados, Clojure ofrece un sistema de 33 software transaccional y un agente de sistema reactivo el cual asegura diseños multihilos limpios y correctos [10]. Caracterı́stica Descripción Desarrollo Dinámico Clojure es dinámico, lo cual significa que solo ejecuta código compilado, también permite la interacción con el lenguaje a partir de su REPL. Dicha interacción, permite evaluar código y variables de una manera rápida. Programación Funcional Clojure es un lenguaje de programación funcional. Este provee herramientas para impedir la mutación de estado, provee funciones como objetos de primera clase, y enfatiza la recursión iterativa en lugar de bucles con efectos secundarios. Lisp Clojure es un miembro de la familia de Lisp, sin embargo Clojure extiende el enfoque de código como datos mucho más allá de las listas entre paréntesis expresiones-s, hasta vectores y mapas. Estos vectores y mapas pueden ser utilizados en el sistema de macros. 34 Caracterı́stica Descripción Polimorfismo en tiempo de Ejecución Los sistemas que utilizan polimorfismo en tiempo de ejecución son fáciles de cambiar y de extender. Clojure soporta polimorfismo de muchas maneras diferentes: Muchas de las estructuras de datos principales en tiempo de ejecución de Clojure, son definidas mediante interfaces de Java. Clojure soporta la generación de implementaciones de interfaces en Java utilizando proxies proxy. El lenguaje Clojure soporta polimorfismo tanto a lo largo de clases como de herencia con múltiples métodos. El lenguaje Clojure también soporta un polimorfismo más rápido mediante el uso de protocolos (sin embargo se encuentra limitado solo a polimorfismo a nivel de clases para tomar ventaja de las capacidades existentes en la JVM). Programación Concurrente Clojure, es un lenguaje practico, permite cambiar el estado sin embargo provee mecanismos para asegurar la consistencia, mientras elimina la responsabilidad a los desarrolladores de lidiar manualmente con bloqueos y resolver conflictos. 35 Caracterı́stica Descripción Funciona sobre la JVM Clojure está diseñado para ser un lenguaje anfitrión, compartiendo el sistema de tipos de la JVM. Compila todas las funciones a bytecode de la JVM. Tabla 1.7: Caracterı́sticas de Clojure A continuación un ejemplo de código 1.19 escrito en Clojure tomado de GitHub [27]: 1 ( use ’ korma . db ) 2 3 ( s e l e c t users 4 ( where ( o r (= : username ” c h r i s ” ) 5 (= : e m a i l ” c h r i s @ c h r i s . com ” ) ) ) ) Fragmento de Código 1.19: Ejemplo de Clojure Elixir Elixir es un lenguaje de programación funcional dinámico, diseñado para la construcción de aplicaciones escalables y mantenibles [11]. Caracterı́stica Descripción Escalabilidad Todo código se ejecuta sobre un pequeño hilo de proceso, el cual es aislado e intercambia información mediante mensajes. Tolerancia a Fallos Elixir provee supervisores, los cuales describen como reiniciar un parte del sistema cuando algún error se presenta. Programación Funcional Promueve un estilo de programación que ayuda al desarrollador a escribir código pequeño, rápido y mantenible. 36 Caracterı́stica Descripción Ecosistema Creciente Elixir está construido con un gran conjunto de herramientas para su fácil desarrollo. Mix es una herramienta la cual facilita la creación de proyectos, manejo de tareas, ejecutar pruebas y más. Desarrollo Interactivo Herramientas como IEx (shell interactivo de Elixir) permiten interactuar con el lenguaje de manera practica, mediante herramientas como auto-completar, debugging, recarga de código. Compatible con Erlang Elixir se ejecuta sobre la máquina virtual de Erlang, lo cual permite ejecutar funciones Erlang directamente. Tabla 1.8: Caracterı́stica de Elixir A continuación un ejemplo de código 1.20 escrito en Elixir tomado de la página oficial [11]: 37 1 d e f sum ( a , b ) when i s i n t e g e r ( a ) and i s i n t e g e r ( b ) do 2 a + b 3 end 4 5 d e f sum ( a , b ) when i s l i s t ( a ) and i s l i s t ( b ) do 6 a ++ b 7 end 8 9 d e f sum ( a , b ) when i s b i n a r y ( a ) and i s b i n a r y ( b ) do 10 a <> b 11 end 12 13 sum 1 , 2 14 #=> 3 15 16 sum [ 1 ] , [ 2 ] 17 #=> [ 1 , 2 ] 18 19 sum ” a ” , ” b ” 20 #=> ” ab ” Fragmento de Código 1.20: Ejemplo de Elixir Scala Scala es un lenguaje de programación multiparadigma, diseñado para expresar patrones comunes de una manera concisa, elegante y con seguridad de tipos. De una manera fácil integra caracterı́sticas de orientación a objectos y lenguajes funcionales [38]. 38 Caracterı́sticas Descripción Orientación a Objetos Scala es un lenguaje puro orientado a objetos en el sentido que cada valor es un objeto. Los tipos y el comportamiento de los objetos son descritos por la clases y traits Trait. Las clases extienden a través de subclases y una composición flexible basada en mixins Mixin. Funcional Scala es un lenguaje funcional en el sentido que cada función es un valor. Scala provee una sintaxis ligera para la definición de funciones anónimas, soporta funciones de orden superior, permite que las funciones sean anidadas, y soporta Currying. Tipado Estático Scala se encuentra equipado con un sistema de tipos expresivo el cual se encarga de garantizar que las abstracciones sean utilizadas de una manera coherente. En particular el sistema de tipos soporta: Clases genéricas Anotaciones de variantes Lı́mites de tipos superior e inferior Clases internas y tipos abstractos como miembros de objetos Tipos compuestos Vistas Métodos polimórficos 39 Caracterı́sticas Descripción Extensible En practica, el desarrollo de aplicaciones de dominio especı́fico por lo general requiere extensiones. Scala provee una única combinación de mecanismos del lenguaje la cual permite añadir nuevas caracterı́sticas al lenguaje mediante librerı́as. Tabla 1.9: Caracterı́sticas de Scala A continuación un ejemplo de código 1.21 escrito en el lenguaje Scala [38]: 1 d e f max ( x : I n t , y : I n t ) : I n t = { 2 return x > y ? x : y 3 } 4 5 max ( 1 , 2 ) / / => 2 Fragmento de Código 1.21: Ejemplo de Scala Erlang Erlang es un lenguaje de programación utilizado para la construcción masiva de software escalable el cual requiera de alta disponibilidad. Algunos de ellos se encuentran en uso dentro de las telecomunicaciones, banca, comercio electrónico, y mensajerı́a instantánea [12]. 40 Caracterı́sticas Descripción Concurrencia Los procesos son bastante ligeros, con un tamaño de alrededor de 500 bytes por proceso. Esto significa que millones de procesos pueden ser creados, incluso con computadoras antiguas. Debido a que los procesos de Erlang son completamente independientes de sistema operativo, el programa se comportará de igual manera en Linux, FreeBSD, Windows y otros sistemas sobre los cuales corre Erlang. Reemplazo de Código en Caliente En un sistema de tiempo real usualmente no se quiere detener el sistema para realizar una actualización de código. En algunos sistemas de tiempo real nunca serı́a posible apagar el sistema para realizar una actualización, este tipo de sistemas deben ser diseñados con código dinámico actualizable. Un ejemplo de este tipo de sistemas es el sistema de control X2000 desplegado por la NASA. Multiplataforma Erlang corre sobre Linux, FreeBSD, Windows, Solaris, Mac OS X, e incluso sobre plataformas embebidas como VxWorks. 41 Caracterı́sticas Descripción Soporte Un equipo dedicado de empleados de Ericsson trabajan en Erlang. Soporte comercial y servicios están disponibles para Erlang. También existe una comunidad bastante activa alrededor del mundo, centrada alrededor de la lista de correo de Erlang y su canal de Internet Relay Chat (IRC). Juega bien con el Mundo Exterior Provee integración con Java, .NET, C, Python y Ruby. Existe una interfaz de acuerdo al sistema operativo deseado. HiPE El compilador de Erlang puede compilar a código nativo de Windows, Linux o Mac OS X y forma parte de la distribución estándar de Erlang. Tipado Estático, cuando sea necesario Se puede anotar el código con información sobre tipos y utilizar Dialyzer, un poderoso verificador de tipos, el cual asegura la validez del código y mejora el rendimiento. Dialyzer forma parte de Erlang, y soporta tipado gradual el cual ofrece máxima flexibilidad. Tabla 1.10: Caracterı́sticas de Erlang A continuación un código de ejemplo 1.22 escrito en Erlang tomado de la página oficial [12]: 42 1 connect ( Host , User , Password ) −> 2 { f t p s e r v e r , Host } ! { connect , s e l f ( ) , User , Password } , 3 receive 4 { f t p s e r v e r , Reply } −> Reply ; 5 Other −> Other 6 a f t e r 10000 −> t i m e o u t 7 end Fragmento de Código 1.22: Ejemplo de Erlang OCaml El gestor de paquetes de OCaml (OPAM) soporta la instalación de múltiples compiladores, restricciones de paquetes flexibles, y un flujo de trabajo familiar con Git. Los paquetes son automáticamente probados, cualquier notificación es enviada a los mantenedores [31]. Caracterı́sticas Descripción Sistema de Tipos Estático OCaml provee un colección de reglas asignadas a un tipo, las cuales permiten construir variables, expresiones, funciones o módulos. Inferencia de Tipos Es la capacidad del compilador de deducir un tipo dependiendo del contexto sobre el cual se evalúe. Polimorfismo Paramétrico OCaml no soporta la sobrecarga de funciones debido a que es demasiado complicado tener inferencia de tipos y sobrecarga de funciones al mismo tiempo. Sin embargo OCaml soporta polimorfismo paramétrico y subtipos. En OCaml, el polimorfismo paramétrico está introducido solo por el uso del constructor let, el cual es llamado restricción de valores. 43 Caracterı́sticas Descripción Funtores Los funtores son funciones de módulos a módulos, pueden ser utilizadas para resolver una variedad de problemas de estructura de código: Inyección de dependencias. Autoextensión de módulos. Instanciación de módulos con estado. Tabla 1.11: Caracterı́sticas de OCaml A continuación un ejemplo de código 1.23 escrito en el lenguaje OCaml [31]: 1 l e t r e c f a c t x = i f x <= 1 then 1 else x ∗ f a c t ( x − 1 ) ; ; 2 f a c t 5 ; ; − : i n t = 120 Fragmento de Código 1.23: Ejemplo de OCaml Librerı́as de Programación La presente sección es una breve recopilación, sobre el estado actual de la programación funcional aplicada a lenguajes orientados a objetos. functional.js - 2015 Es una librerı́a funcional de JavaScript. Facilita la programación por medio de Currying y point-free, está metodologı́a ha sido utilizada desde los inicios de la construcción de la librerı́a [16]. 44 Caracterı́stica Descripción Currying fjs.curry permite facilita la creación de funciones de orden superior mediante la invocación parcial de una función existente, sin proveer todos los argumentos de la función original. Aridad La aridad de una función en JavaScript es el número de argumentos esperados por una función. Con fjs.curry es posible extender la aridad de una función más allá de su longitud esperada. Expresiones Lambda fjs permite especificar expresiones lambda de manera opcional, en lugar de las funciones de JavaScript para cualquiera de sus funciones que permitan currying. fjs.each fjs.each permite iterar sobre elementos de una colección, aplicando una función iterador a cada elemento. Esto es muy similar a la función nativa forEach disponible en navegadores modernos, sin embargo facilita la implemetación de fjs.curry. Tabla 1.12: Caracterı́sticas de functional.js El ejemplo 1.24 a continuación ha sido tomado de GitHub [16]: 1 v a r add = f j s . c u r r y ( f u n c t i o n ( arg1 , arg2 ) { 2 r e t u r n arg1 + arg2 ; 3 }) ; 4 5 v a r add3 = add ( 3 ) ; 6 add ( 1 , 2 , 3 ) ; / / => 6 7 add3 ( 1 , 2 , 3 , 4 , 5 ) ; / / => 18 Fragmento de Código 1.24: Ejemplo de functional.js 45 Javaslang - 2014 Javaslang es una librerı́a funcional para Java 8 y versiones superiores. Javaslang añade un API y algunas mejores practicas tomadas de lenguajes como Scala, para tomar ventaja sobre las Lambdas en al programación diaria [24]. Caracterı́stica Descripción Núcleo Javaslang viene con una representación de los tipos básicos faltantes o son rudimentarios en Java. Funciones La programación funcional es sobre valores y transformaciones de valores utilizando funciones. Colecciones Con Javaslang se ha puesto mucho esfuerzo en el diseño de la librerı́a de colecciones para Java, la cual cumpla con los requerimientos de la programación funcional, como la inmutabilidad. Tabla 1.13: Caracterı́sticas de Javaslang El ejemplo de código 1.25 a continuación, ha sido tomado de la página oficial de Javaslang [24]: 1 f i n a l Tuple2<S t r i n g , I n t e g e r > j a v a 8 = Tuple . o f ( ” Java ” , 8 ) ; 2 3 f i n a l Tuple2<S t r i n g , I n t e g e r > guessWhat = j a v a 8 . map( 4 ( s , i ) −> Tuple . o f ( s + ” s l a n g ” , i / 4 ) 5 ) ; / / => ( ” Javaslang ” , 2 ) Fragmento de Código 1.25: Ejemplo de Javaslang functional-ruby - 2013 La librerı́a funcional-ruby se encuentra inspirada en algunos lenguajes de progra- 46 mación y herramientas como Erlang, Clojure y Functional Java. Caracterı́stica Descripción Especificación de protocolo Especificación de protocolos inspirada por los protocolos de Clojure, el comportamiento de Erlang, y los protocolos de Objective-C. Sobrecarga de Funciones La sobrecarga de funciones con un estilo de Erlang permite la búsqueda de patrones. Threads Seguros Simple, seguridad de threads, estructuras de datos inmutables, como son registros, uniones, y tuplas, inspiradas por Clojure, Erlang, y otros lenguajes funcionales. Ejecución Perezosa La ejecución perezosa con una clase Delay basada en Clojure delay. Estructuras de Valor Estructuras de valor, simples, seguridad de thread, una variación inmutable de las estructuras abiertas de Ruby. Tabla 1.14: Caracterı́sticas de functional-ruby El ejemplo de código 1.26 a continuación ha sido tomado de GitHub [3]: 47 1 Name = F u n c t i o n a l : : Record . new ( : f i r s t , : middle , : l a s t , : s u f f i x ) do 2 mandatory : f i r s t , : l a s t 3 default : f i r s t , 4 default : last , ’J. ’ ’ Doe ’ 5 end 6 7 anon = Name . new 8 #=> #<r e c o r d Name : f i r s t =>” J . ” , . . . > 9 matz = Name . new( f i r s t : ’ Yukihiro ’ , last : ’ Matsumoto ’ ) 10 #=> #<r e c o r d Name : f i r s t =>” Y u k i h i r o ” , . . . > Fragmento de Código 1.26: Ejemplo de functional-ruby functional-php - 2011 Es un conjunto de primitivos funcionales para PHP, principalmente inspirado por Scala traversable collection, los arreglos en Dojo y Underscore.js [15]. Caracterı́stica Descripción Arrays Trabaja con arrays y cualquier implementación de Traversable Interfaz consistente Provee una interfaz consistente para funciones las cuales toman colecciones y callback, el primer parámetro es siempre una colección, y el segundo el callback. Los Callbacks siempre reciben los parámetros $value, $index, $collection. Soporta Closures Es posible utilizar tanto callbacks como closures. Espacio de Nombres Todas las funciones residen en un mismo espacio de nombres ”Functional”para evitar cualquier tipo de conflictos con cualquier otra extensión o librerı́a. Tabla 1.15: Caracterı́sticas de functional-php 48 El ejemplo de código 1.27 a continuación ha sido tomado de GitHub [15]: 1 use f u n c t i o n F u n c t i o n a l \map ; 2 3 map( range ( 0 , 100) , f u n c t i o n ( \ $v ) { r e t u r n \$v + 1 ; } ) ; Fragmento de Código 1.27: Ejemplo de functional-php Underscore.js - 2008 Underscore es una librerı́a de JavaScript la cual provee un completo conjunto de utilitarios funcionales sin la extensión de cualquier objeto incorporado en el lenguaje [39]. Caracterı́stica Descripción Colecciones Underscore provee algunos métodos los cuales operan sobre colecciones. Arreglos Underscore provee algunas funciones las cuales trabajan exclusivamente sobre arerglos. Objetos Underscore provee varios métodos para clonar objetos, para extenderlos y manipular objetos. Funciones Underscore provee funciones las cuales trabajan sobre funciones, este permite construir funciones de orden superior. Tabla 1.16: Caracterı́sticas de Underscore.js El ejemplo de código 1.28 a continuación ha sido tomado de la página oficial: 49 1 2 . map ( [ 1 , 2 , 3 ] , f u n c t i o n (num) { r e t u r n num ∗ 3 ; } ) ; / / => [ 3 , 6 , 9 ] 3 4 5 . map( { one : 1 , two : 2 , t h r e e : 3} , f u n c t i o n ( num , key ) { r e t u r n num ∗ 3 ; 6 }) ; 7 / / => [ 3 , 6 , 9 ] 8 9 10 . map ( [ [ 1 , 2 ] , [ 3 , 4 ] ] , . first ); / / => [ 1 , 3 ] Fragmento de Código 1.28: Ejemplo de Underscore.js CAPÍTULO 2. DESARROLLO DE LA LIBRERÍA UTILITARIA FUNCIONAL Descripción del Problema La naturaleza del software es evolutiva, se debe ajustar a las necesidades definidas por los usuarios. Dichos ajustes deben ser realizado con frecuencia, el esfuerzo involucrado se encuentra directamente relacionado al diseño del software. Un sistema altamente acoplado es difı́cil de mantener, cualquier cambio realizado causarı́a una serie de modificaciones en cadena, incrementando la posibilidad de introducir nuevos errores o defectos en el sistema. Dentro de sistemas distribuidos, el manejo de la concurrencia resulta un desafı́o. El cambio constante de estado dentro de la programación orientada a objetos, puede causar algunas problemas dentro de la concurrencia como: deadlocks y condiciones de carrera [34]. La mayorı́a de lenguajes orientados a objetos como Java, poseen mecanismos para el manejo de la concurrencia, sin embargo es el desarrollador quien es responsable de emplear dichos mecanismos durante el desarrollo. El presente proyecto se enfocará en dos problemas puntuales, la escalabilidad causada por sistemas altamente acoplados, y la concurrencia causada por el uso de objetos mutables. Descripción de la Solución El presente capı́tulo, describe el análisis, diseño, desarrollo y pruebas de la librerı́a 50 51 funcional construida sobre el lenguaje orientado a objetos Java. Dentro de las secciones a continuación se describe tanto las limitaciones del lenguaje Java sobre Haskell, ası́ como algunas de las técnicas empleadas durante el desarrollo para superar algunas de estás limitaciones. Algunos de los objetivos claves del presente capı́tulo son: Identificar los módulos de Haskell a ser importados. Identificar las limitaciones encontradas al tratar de importar ciertos módulos a Java. Describir las convenciones tanto de documentación, como de estructura de código de la librerı́a. Describir las técnicas empleadas para superar algunas de las limitaciones encontradas en Java. Describir el proceso de pruebas utilizado durante el desarrollo de la librerı́a. Metodologı́a de Desarrollo Para el desarrollo de la librerı́a funcional se ha seleccionado Scrum como metodologı́a. La naturaleza iterativa e incremental de Scrum sigue la misma filosofı́a del desarrollo guiado por pruebas, lo cual hace que se integren bien durante las diferentes fases del desarrollo. La tabla 2.1 a continuación, lista las historias de usuario dentro del Product Backlog del proyecto: 52 Fase Historia de Usuario Prioridad Estimación Estado Análisis Como un usuario de la librerı́a, 1 8 Hecho 2 8 Hecho 3 3 Hecho 4 5 Hecho yo quiero que la librerı́a sea compatible con diferentes versiones de Java, para que no existan conflictos en mis proyectos durante la actualización de versiones del lenguaje. Diseño Como un usuario de la librerı́a, yo quiero tener acceso a la documentación, para tener una referencia del modo de uso de los métodos y clases definidos dentro de la librerı́a. Diseño Como un contribuidor de la librerı́a, yo quiero tener acceso al código fuente, para poder realizar contribuciones al proyecto. Desarrollo Como un contribuidor de la librerı́a, yo quiero que el proyecto utilice un servicio de integración continua, para poder detectar defectos y errores introducidos por cambios de una manera temprana. 53 Fase Historia de Usuario Prioridad Estimación Estado Pruebas Como un usuario de la librerı́a, 5 8 Hecho yo quiero utilizar un producto de software con un alto nivel de cobertura de pruebas, para que el uso de la librerı́a no introduzca errores o defectos dentro de mis sistemas. Tabla 2.1: Product Backlog Las historias de usuario presentadas en el Product Backlog fueron desarrolladas a lo largo de 3 Sprints, los cuales se describe a continuación: Historia de Usuario Estimación Como un usuario de la librerı́a, 8 Tareas Evaluar las versiones de Java yo quiero que la librerı́a sea más utilizadas en la industrı́a. compatible con diferentes ver- Identificar las limitaciones del siones de Java, para que no lenguaje Java comparado con existan conflictos en mis proyec- Haskell. tos durante la actualización de Analizar la portabilidad de los versiones del lenguaje. módulos de Haskell a Java. Como un usuario de la librerı́a, 5 Documentar el código fuente. yo quiero tener acceso a la do- Generar la documentación. cumentación, para tener una re- Subir la documentación a un re- ferencia del modo de uso de los positorio público. métodos y clases definidos dentro de la librerı́a. Tabla 2.2: Sprint #1 - Spring Backlog 54 Historia de Usuario Estimación Como un contribuidor de la li- 3 Tareas Instalar y configurar un sistema brerı́a, yo quiero tener acceso al de versionamiento. código fuente, para poder reali- Subir el código fuente a un repo- zar contribuciones al proyecto. sitorio en la nube. Como un contribuidor de la li- 5 Ejecutar las pruebas como parte brerı́a, yo quiero que el proyec- del proceso de integración con- to utilice un servicio de integra- tinua. ción continua, para poder detec- Ejecutar las pruebas utilizando tar defectos y errores introduci- diferentes versiones de la JVM. dos por cambios de una manera Generar la documentación co- temprana. mo parte de la integración continua. Tabla 2.3: Sprint #2 - Spring Backlog Historia de Usuario Estimación Como un usuario de la librerı́a, 8 Tareas Instalar y configurar JUnit en el yo quiero utilizar un producto de proyecto. software con un alto nivel de co- Configurar la librerı́a PiTest para bertura de pruebas, para que el la ejecucción de pruebas de mu- uso de la librerı́a no introduzca tación. errores o defectos dentro de mis Automatizar la ejecucción de sistemas. pruebas. Generar el reporte de pruebas como parte de la integración continua. Tabla 2.4: Sprint #3 - Spring Backlog Finalmente la figura a continuación 2.1, muestra el progreso realizado a lo largo de 55 todo el desarrollo del proyecto: Figura 2.1: Release Burndown 2.1 ANÁLISIS Antes de proceder con el diseño de la librerı́a funcional, se debe evaluar cada uno de los módulos escritos en Haskell, con el fin de analizar su portabilidad a Java. Adicionalmente se tomarán en cuenta las siguientes consideraciones: Compatibilidad con versiones anteriores de Java Development Kit (JDK). Limitaciones del lenguaje de programación Java. 2.1.1 RETROCOMPATIBILIDAD Uno de los principales objetivos del presente proyecto, es extender el uso de la librerı́a desarrollada, para lo cual se evaluará el porcentaje de uso de las diferentes versiones de Java. La figura 2.2 a continuación muestra el uso de las diferentes versiones de Java en la actualidad: 56 29 % 0.1 % 0.9 70 % JDK 8 JDK 7 JDK 6 JDK 5 Figura 2.2: Uso de las Versiones de JDK Fuente: https://plumbr.eu/blog/java/most-popular-java-environments Como se puede observar en la figura 2.2, a pesar de existir hoy en dı́a nuevas versiones de Java las cuales introducen algunas caracterı́sticas de la programación funcional, todavı́a existe un alto porcentaje de uso de la versión 6 de Java. La librerı́a desarrollada será compatible con las siguientes versiones de Java: Java 6 Java 7 Java 8 Cualquier análisis realizado en las secciones a continuación, tomará en cuenta que la librerı́a debe ser compatible con versiones anteriores de Java a partir de la versión 6. 2.1.2 LIMITACIONES DEL LENGUAJE DE PROGRAMACIÓN JAVA Al comparar dos lenguajes de programación, pueden existir ventajas y desventajas de un lenguaje sobre otro, sin embargo debido a que se procederá a importar 57 módulos escritos en Haskell a Java, la presente sección se enfocará en describir las limitaciones de Java sobre Haskell las cuales serán consideradas en la fase de diseño 2.2 más adelante. Efectos Secundarios En Java no existe restricción para limitar efectos secundarios en un método a diferencia de Haskell, en donde una función debe especificar explı́citamente la presencia de efectos secundarios. 1 printMessage : : S t r i n g −> IO ( ) 2 printMessage m = putStrLn m Fragmento de Código 2.1: Efecto Secundario Explı́cito 1 public void printMessage ( S t r i n g message ) { 2 System . o u t . p r i n t l n ( message ) ; 3 } Fragmento de Código 2.2: Efecto Secundario Implı́cito Como se puede observar en el fragmento de código 2.2, no existe ningún indicio en la firma del método printMessage de que tenga algún efecto secundario, a diferencia del fragmento de código 2.1 en donde el tipo del retorno de la función IO () indica que la función printMessage tiene algún efecto sobre la entrada y salida de información. Evaluación Perezosa Haskell es perezoso por defecto, es decir, no evalúa una expresión hasta que sea necesario, a continuación un ejemplo: 58 1 > take 3 [ 1 . . ] 2 [1 , 2 , 3] Fragmento de Código 2.3: Evaluación Perezosa - Haskell Como se puede observar en el fragmento de código 2.3, la función take toma como argumento una lista infinita [1..] , sin embargo para mostrar el resultado en la pantalla, Haskell solo evaluará los 3 primeros elementos de la lista. Analizando el mismo ejemplo en Java, dado que el proceso de evaluación del lenguaje es estricto, evaluar una lista infinita resultarı́a en un error de memoria, como se puede observar en el fragmento de código a continuación 2.4: 1 t a k e ( 3 , i n f i n i t e L i s t ) ; / / => OutOfMemoryError Fragmento de Código 2.4: Evaluación Perezosa - Java Por lo tanto Java no soporta la evaluación perezosa de expresiones. Funciones de Primera Clase Para que una función sea considerada de primera clase, debe cumplir las siguientes caracterı́sticas: Poder ser utilizada como argumento de otra función. Poder ser retornada como resultado de una función. Poder ser asignada a una variable. Utilizando los criterios mencionados anteriormente, se puede determinar que una función en Java, no puede ser considerado una función de primer orden debido a que no cumple ninguna de las caracterı́sticas mencionadas. 59 Funciones de Orden Superior Una función que toma como parámetro otra función, se denomina función de orden superior, está caracterı́stica sin embargo no existe en Java debido a que no existen funciones de primera clase como se indicó en la sección 2.1.2 anterior. El fragmento de código 2.5 a continuación muestra un ejemplo de uso de una función de orden superior en Haskell: 1 > map (+ 1 ) [ 1 , 2 , 3 ] 2 [2 , 3 , 4] Fragmento de Código 2.5: Ejemplo de Función de Orden Superior Como se puede observar en el fragmento de código 2.5, la función map toma como argumento a la función (+1), la cual es aplicada a cada uno de los elementos de la lista [1, 2, 3]. Currying Currying es el proceso de transformar una función que toma n número de argumentos, en una secuencia de n funciones que toman un solo argumento, la última función de la secuencia retorna el resultado final de la función. Con el fin de ilustrar de una manera práctica la diferencia entre una función con currying y una sin currying, se procederá a definir una misma función escrita tanto en Haskell como en Java. 1 sum : : I n t −> I n t −> I n t 2 sum x y = x + y Fragmento de Código 2.6: Función Suma en Haskell 60 1 public i n t sum ( i n t x , i n t y ) { 2 return x + y ; 3 } Fragmento de Código 2.7: Función Suma en Java Como se puede observar en los fragmentos de código 2.6 y 2.7, la función sum escrita tanto en Haskell como en Java, toma el mismo número de argumentos y retorna el mismo tipo de dato como resultado. A continuación un ejemplo de uso de cada una de las funciones definidas anteriormente: 1 > sum 1 2 2 3 Fragmento de Código 2.8: Ejemplo de Uso Función Suma en Haskell 1 > sum ( 1 , 2 ) ; 2 3 Fragmento de Código 2.9: Ejemplo de Uso Función Suma en Java Como se puede observar en los fragmentos de código 2.8 y 2.9, el resultado de invocar la función sum es el mismo tanto en Haskell como en Java, sin embargo el proceso de ejecución es diferente. La tabla 2.5 a continuación, detalla el proceso de ejecución para los fragmentos de código 2.8 y 2.9: 61 Haskell Java 1. La función sum toma como ar- 1. La función sum toma como ar- gumento el número 1 y re- gumento los números 1 y 2, y torna como resultado otra fun- retorna el resultado de la suma, ción, empleando el proceso de el número 3. currying. 2. La función retornada toma el argumento faltante, el número 2 y retorna el resultado de la suma el número 3. Tabla 2.5: Proceso de Ejecución - Función Suma Como se puede observar en la tabla 2.5 anterior, Haskell aplica la transformación de currying por defecto a todas sus funciones, permitiendo ası́ invocar a una función con un menor número de argumentos, mientras que en Java esto no es posible ya que darı́a como resultado un error de compilación. Aplicación Parcial de Funciones La aplicación parcial de funciones, es la habilidad de una función para tomar un menor número de argumentos, y retornar otra función la cual tome los argumentos restantes. Utilizando la función sum definida en la sección 2.1.2 anterior, el fragmento de código 2.10 a continuación, muestra como se puede obtener una función parcialmente aplicada a partir de la función definida: 62 1 > l e t addOne = sum 1 2 > addOne 2 3 3 Fragmento de Código 2.10: Ejemplo de Aplicación Parcial de Funciones Como se puede observar en el fragmento de código 2.10, la función sum toma como argumento el número 1, obteniendo como resultado una función parcialmente aplicada la cual es almacenada en la variable addOne, posteriormente dicha función es invocada con el argumento restante 2, obteniendo como resultado 3. El ejemplo definido en el fragmento de código 2.10, no serı́a posible importarlo a Java debido a las siguientes limitaciones: Las funciones en Java no son consideras objetos de primera clase, esto impedirı́a que una función sea asignada a una variable. Las funciones en Java no son transformadas mediante la técnica de currying, esto impedirı́a que la función sum escrita en Java, sea invocada con un menor número de argumentos que el definido por la función. Debido a los puntos mencionados anteriormente, se puede concluir que Java no soporta la aplicación parcial de funciones. 2.1.3 ANÁLISIS DE PORTABILIDAD Una vez descritas las limitaciones del lenguaje de programación Java comparado con Haskell en la sección 2.1.2, se procederá a realizar un análisis de portabilidad para poder determinar que módulos escritos en Haskell, son factibles de ser importados a Java. La versión de la librerı́a base de Haskell utilizada para el análisis de portabilidad será la versión 4.7.0.2, la cual forma parte del compilador de Haskell ghc en su versión 7.8.4. 63 Antes de proceder con el análisis de portabilidad, se debe destacar que sólo se tomarán en cuenta los módulos definidos dentro de Data, tal y como se especı́fico dentro del alcance del proyecto. La tabla 2.6 a continuación, detalla el análisis de portabilidad realizado para cada uno de los módulos candidatos: Módulo Importado Data.Bits No Justificación Este tipo de operaciones a nivel de bits es algo común en aplicaciones especializadas sin embargo en la mayorı́a de aplicaciones se trabaja con estructuras de datos de más alto nivel. Data.Bool Sı́ Las operaciones entre booleanos son bastante comunes dentro del desarrollo de aplicaciones. Data.Char No A pesar de que trabajar con caracteres sea un tarea muy común la mayorı́a de la funcionalidad provista por este módulo en Haskell ya se encuentra implementada en la librerı́a base de Java. Data.Coerce No Este módulo provee la capacidad de transformar un valor entre diferentes tipos sin añadir un costo adicional de procesamiento, este concepto es útil sin embargo no es posible aplicarlo en Java debido a que sus tipos son rı́gidos. Data.Complex No Este módulo es utilizado para manejar tipos de datos algebraicos lo cual no es muy común dentro del desarrollo de sistemas no especializados. 64 Módulo Data.Dynamic Importado No Justificación Este módulo provee soporte para tipos dinámicos en Haskell funcionalidad que no será importada en Java ya que involucra conceptos que no son soportados por el lenguaje. Data.Either Sı́ La estructura de datos definida por este módulo es útil para convertir funciones con efectos secundarios en funciones puras, por ejemplo funciones que lanzan excepciones. Data.Eq No Java ya define como parte del lenguaje una manera estándar para comparar objetos por lo que se utilizará la misma técnica. Data.Fixed No La clase Math definida como parte de la librerı́a base de Java cubre la mayorı́a de la funcionalidad definida en este módulo en Haskell. Data.Foldable No Este módulo define una manera genérica de representar estructuras plegables como listas sin embargo el sistema de tipos en Java no permite implementar está funcionalidad de una manera sencilla. Data.Function Sı́ En versiones anteriores de Java no existe el concepto de una función como objeto de primer orden por lo que importar este módulo proporcionara está caracterı́stica. Data.Functor Sı́ Solo se definirá las instancias de la clase Functor para aquellas estructuras de datos que existan en Java. 65 Módulo Data.IORef Importado No Justificación Al no ser Java un lenguaje puramente funcional, cualquier función es libre de tener efectos secundarios por lo que no es necesario hacerlo de manera explı́cita. Data.Int No Ya existe un tipo de dato similar en Java para el manejo de enteros por lo que no es necesario crear uno nuevo. Data.Ix Sı́ En Java no existe el concepto de rango de datos por lo que será útil introducirlo para poder generar listas solamente especificando el inicio y fin de la misma. Data.List Sı́ A pesar de que Java ya posee su propia definición de una lista su equivalente en Haskell provee muchas funciones para trabajar con este tipo de estructuras de una manera más clara y sencilla. Data.Maybe Sı́ En Java no existe el concepto de parámetros opcionales el introducir esta idea permitirá evitar el uso de nulos. Data.Monoid Sı́ Sin embargo solo se definirá la instancia de Monoid para las estructuras de datos existente en el lenguaje Java. Data.OldTypeable No Actualmente este módulo se encuentra marcado como deprecado en Haskell. Data.Ord No En Java ya existe una manera por defecto de comparar objetos por lo que no es necesario definir una nueva. 66 Módulo Data.Proxy Importado No Justificación El definir una manera estándar de crear un proxy en Java no es necesario ya que esto se puede conseguir de una manera muy fácil empleando composición de objetos. Data.Ratio No Este módulo provee funciones para trabajar con números racionales lo cual no es una funcionalidad muy utilizada en aplicaciones no especializadas. Data.STRef No Este módulo en Haskell es utilizado para crear referencias a objetos mutables sin embargo en Java todo tipo de dato es mutable por defecto por lo que esto no es necesario. Data.String Si Las cadenas de texto son parte de la librerı́a base de Java sin embargo su equivalente en Haskell provee funcionalidad extra para trabajar con este tipo de datos. Data.Traversable No Muchos de los conceptos definidos dentro de este módulo no pueden ser importados a Java debido a algunas limitaciones del lenguaje. Data.Tuple Sı́ Una tupla es similar a una lista sin embargo está contiene solo dos elementos los cuales no necesariamente deben ser del mismo tipo, esto resulta útil para poder representar conceptos como coordenadas en un plano cartesiano. 67 Módulo Importado Data.Typeable No Justificación Este módulo utiliza algunos de los conceptos definidos por otros módulos como Data .Dynamic y Data.Data los cuales no serán importados. Data.Unique No La clase UUID en Java cubre toda la funcionalidad definida por este módulo en Haskell. Data.Version Sı́ Este módulo en Haskell define una manera estándar de representar una versión la cual resulta útil al momento de comprobar si una versión x se encuentra dentro de un rango de versiones definido. Data.Word No Este módulo provee funcionalidad para trabajar con enteros sin signo lo cual no es muy común dentro del desarrollo de sistemas no especializados. Tabla 2.6: Análisis de Portabilidad La figura 2.3 muestra un breve resumen, del número de módulos totales vs el número de módulos importados: 68 Importados Módulos 11 30 Totales 0 10 20 30 40 # de Módulos Figura 2.3: Número de Módulos Importados En resumen un 37 % del total de módulos analizados serán importados a Java, debido a cada una de las consideraciones expuestas en la tabla 2.6. 2.2 DISEÑO Realizado tanto el análisis de limitaciones del lenguaje de programación Java, como el de portabilidad de módulos en la sección anterior 2.1, se procederá al diseño de la librerı́a, la presente sección tiene como objetivos: Definir convenciones sobre la estructura de código. Definir convenciones sobre la documentación. 2.2.1 ESTRUCTURA DE CÓDIGO La presente sección, busca reflejar la relación existente entre los módulos escritos en Haskell frente a su equivalente en Java a nivel de estructura de código. A continuación un módulo de ejemplo escrito en Haskell: 69 1 module Data . Maybe where 2 3 data Maybe a = Nothing | Just a 4 5 isNothing : : Maybe a −> Bool 6 isNothing = . . . 7 8 i s J u s t : : Maybe a −> Bool 9 isJust = . . . Fragmento de Código 2.11: Módulo Maybe en Haskell El módulo descrito en el fragmento de código 2.11, será importado a Java utilizando dos clases: La primera clase será utilizada para reflejar la estructura de datos definida dentro del módulo escrito en Haskell. La segunda clase será utilizada para añadir todos los métodos utilitarios definidos en el módulo escrito en Haskell. Los dos fragmentos de código a continuación 2.12 y 2.13, muestra un ejemplo de como se verı́a el módulo Data.Maybe importado de Haskell a Java. 1 package j p r e l u d e . data . maybe ; 2 3 public i n t e r f a c e Maybe<A> { 4 } Fragmento de Código 2.12: Interfaz Maybe en Java 70 1 package j p r e l u d e . data . maybe ; 2 3 public class Maybes { 4 public s t a t i c <A> Maybe<A> n o t h i n g ( ) { 5 } 6 7 public s t a t i c <A> Maybe<A> j u s t (A v a l u e ) { 8 } 9 10 public s t a t i c <A> boolean i s N o t h i n g ( Maybe<A> x ) { 11 } 12 13 public s t a t i c <A> boolean i s J u s t ( Maybe<A> x ) { 14 } 15 } Fragmento de Código 2.13: Módulo Maybe en Java Como se puede observar en el fragmento de código 2.12 el nombre de la interfaz coincide con el de la estructura de datos Maybe definida en el fragmento de código 2.11. Analizando la clase Maybes definida en el fragmento de código 2.13, se puede observar que los nombres de las funciones coinciden con las funciones definidas en Haskell por el módulo Data.Maybe, esto se debe a que la estructura de la clase definida en Java busca reflejar la misma estructura de su equivalente en Haskell. La figura 2.4 a continuación, muestra la estructura de paquetes para todos los módulos importados a Java y su equivalente en Haskell: 71 jprelude data bool ......................................................... Data.Bool either......................................................Data.Either eq ............................................................. Data.Eq function.................................................Data.Function functor...................................................Data.Functor ix .............................................................. Data.Ix list .......................................................... Data.List maybe ...................................................... Data.Maybe monoid .................................................... Data.Monoid ord ........................................................... Data.Ord string......................................................Data.String tuple ....................................................... Data.Tuple Figura 2.4: Estructura de Paquetes 2.2.2 COMPORTAMIENTO La presente sección tiene como objetivo ilustrar el comportamiento de algunas de las funciones en Haskell, y su equivalente en Java. Método Maybe.maybe El siguiente fragmento de código 2.14, muestra el comportamiento de la función maybe en Haskell: 1 > maybe 0 ( + 1 ) Nothing 2 0 3 > maybe 0 ( + 1 ) ( Just 2 ) 4 3 Fragmento de Código 2.14: Ejemplo de Uso - Maybe.maybe La figura 2.5 a continuación, muestra el comportamiento de la función maybe importada a Java: 72 Es instancia de Just No es instancia de Just Aplicar f al valor de Just Retornar valor por defecto Retornar el resultado Figura 2.5: Diagrama de Actividad - Maybe.maybe Método Maybe.isNothing El siguiente fragmento de código 2.15, muestra el comportamiento de la función isNothing en Haskell: 1 > isNothing Nothing 2 True 3 > isNothing ( Just 1 ) 4 False Fragmento de Código 2.15: Ejemplo de Uso - Maybe.isNothing La figura 2.6 a continuación, muestra el comportamiento de la función isNothing importada a Java: 73 Es instancia de Nothing Retornar verdadero No es instancia de Nothing Retornar falso Figura 2.6: Diagrama de Actividad - Maybe.isNothing Método Maybe.isJust El siguiente fragmento de código 2.16, muestra el comportamiento de la función isJust en Haskell: 1 > i s J u s t Nothing 2 False 3 > i s J u s t ( Just 1 ) 4 True Fragmento de Código 2.16: Ejemplo de Uso - Maybe.isJust La figura 2.7 a continuación, muestra el comportamiento de la función isJust importada a Java: 74 Es instancia de Just No es instancia de Just Retornar verdadero Retornar falso Figura 2.7: Diagrama de Actividad - Maybe.isJust 2.2.3 DOCUMENTACIÓN DEL CÓDIGO La documentación del código fuente, tiene los siguientes objetivos: Identificar el comportamiento El desarrollador no debe acceder al código fuente de la función para comprender cual es su uso y comportamiento, esto puede ser especificado en la documentación. Identificar su origen en Haskell Debido a que todas las clases en Java serán importados de Haskell, es importante definir el origen de cada una de las clases y funciones escritas en Java. Agregar ejemplos Como parte de la definición del comportamiento de la función, se agregarán ejemplos de uso de la función, para que el usuario pueda comprender de una manera practica el comportamiento de cada una de las funciones definidas. Para la documentación del código fuente se utilizará la herramienta estándar de Java conocida como Javadoc, dicha herramienta genera documentación en formato HyperText Markup Language (HTML), a continuación una captura de pantalla [13] de una página HTML generada utilizado Javadoc: 75 Figura 2.8: Ejemplo de Javadoc Fuente: http://jprelude.bitbucket.org/docs La documentación de las clases y funciones en Java, será tomada de su fuente original escrita en Haskell, dicha información se encuentra disponible en Hackage. Hackage es el repositorio central de paquetes de Haskell, el cual tambien es utilizado para navegar a través de la documentación disponible en cada paquete, a continuación una captura [19] de pantalla: Figura 2.9: Captura de Pantalla de Hackage Fuente: https://hackage.haskell.org 76 2.3 DESARROLLO Concluido el diseño y el análisis de la librerı́a se procederá con el desarrollo para lo cual se tomaran en cuenta buenas prácticas como son los principios de SOLID y el desarrollo guiados por pruebas. 2.3.1 DESARROLLO GUIADO POR PRUEBAS El desarrollo guiado por pruebas, más conocido como Test-Driven Development (TDD), es una considerada una buena práctica de desarrollo de software, en la cual el desarrollador escribe la prueba primero antes de la implementación, este proceso presente algunas ventajas, mismas que se listan a continuación: Mantenimiento Cambios introducidos al sistema, los cuales afecten su comportamiento, serán detectados por las pruebas existentes. En TDD el porcentaje de cobertura de pruebas es alto, ya que todo componente desarrollado parte de escribir una prueba. Refactorización La refatorización, permite mejorar el diseño del sistema, gracias al alto porcentaje de cobertura de pruebas que ofrece TDD, esto simplifica el mover pedazos de código de un componente a otro, sin modificar el comportamiento general del sistema. TDD es un proceso, iterativo e incremental, es decir se parte de escribir una prueba, luego la implementación y a continuación se refactoriza el diseño una vez que el comportamiento ya ha sido definido por las pruebas escritas. Este proceso se repite una y otra vez hasta alcanzar el objetivo deseado, el cual en el caso del desarrollador, serı́a implementar una nueva caracterı́stica o arreglar algún error en el sistema. El ciclo de vida descrito anteriormente se describe en la figura 2.10 [28] a continuación: 77 Figura 2.10: Ciclo de Vida - TDD Fuente: https://peru.wordcamp.org/2014/session/test-driven-development-en-wordpress-2/ Las subsecciones a continuación describen el proceso de TDD, aplicado de manera practica para la implementación de una de las funciones importadas a Java, el comportamiento de dicha función se describe en el diagrama 2.11 a continuación: Es instancia de Nothing Retornar verdadero No es instancia de Nothing Retornar falso Figura 2.11: Diagrama de Actividad - Maybe.isNothing 78 Primera Iteración La prueba 2.17 a continuación, describe el comportamiento de la función isNothing al recibir como argumento una instancia de la clase Nothing: 1 @Test 2 public void t e s t I s N o t h i n g R e t u r n s T r u e ( ) { 3 a s s e r t T h a t ( i s N o t h i n g (new Nothing ( ) ) , i s ( t r u e ) ) ; 4 } Fragmento de Código 2.17: Prueba isNothingReturnsTrue Una vez escrita la prueba, antes de proceder a escribir la implementación, se debe verificar que efectivamente la prueba falle debido a las razones adecuadas, es decir debido a que el método todavı́a no se encuentra implementado. TDD al ser un proceso iterativo e incremental, dicta que solo se debe escribir el código mı́nimo y necesario durante cada iteración, es decir para el escenario descrito en la prueba 2.17, la implementación 2.18 serı́a la siguiente: 1 public s t a t i c boolean i s N o t h i n g ( Maybe<?> maybe ) { 2 return true ; 3 } Fragmento de Código 2.18: Primera Iteración isNothing Segunda Iteración Una vez completada la primera iteración; la presente iteración, definirá el comportamiento de la función isNothing al recibir como argumento una instancia de la clase Just, dicho comportamiento es reflejado en la prueba 2.19 a continuación. 79 1 @Test 2 public void t e s t I s N o t h i n g R e t u r n s F a l s e ( ) { 3 assertThat ( isNothing ( j u s t (1) ) , i s ( false ) ) ; 4 } Fragmento de Código 2.19: Prueba isNothingReturnsFalse Como se indicó al inicio de la sección 2.3.1, TDD es un proceso iterativo e incremental, al ser incremental cualquier comportamiento definido previamente durante la primera iteración debe ser tomado en cuenta para la implementación de la función isNothing. La implementación final de la función isNothing, serı́a la siguiente: 1 public s t a t i c boolean i s N o t h i n g ( Maybe<?> maybe ) { 2 r e t u r n maybe instanceof Nothing ; 3 } Fragmento de Código 2.20: Segunda Iteración isNothing Finalmente, como se puede observar en el fragmento de código 2.20, la versión final de la función isNothing cumple con el comportamiento definido por las pruebas 2.17 y 2.19. 2.3.2 INTEGRACIÓN CONTINUA La integración continua, es una práctica de desarrollo de software en donde los miembros del equipo integran su trabajo de manera frecuente. Cada integración es verificada mediante la construcción automática del proyecto, dicha construcción involucra la ejecución de las pruebas. 80 Herramientas de Versionamiento El proceso de integración continua, requiere de un sistema de versionamiento para el control de cambios, este permitirá revertir cualquier cambio que introduzca errores en el proyecto. Para el presente proyecto se utilizará Git junto con Bitbucket para el versionamiento del proyecto, a continuación una captura de pantalla [6] de Bitbucket: Figura 2.12: Captura de Pantalla de Bitbucket Fuente: https://bitbucket.org Como se puede observar en la captura de pantalla 2.12, Bitbucket guarda un historial de todos los cambios realizados. Herramientas de Integración Continua Como herramienta de integración continua se utilizara Shippable, un servicio en la nube el cual se integra con Bitbucket para la construcción automática de proyectos y generación de reporte de pruebas. A continuación una captura de pantalla [35] de Shippable: 81 Figura 2.13: Captura de Pantalla de Shippable Fuente: https://app.shippable.com Como se puede observar en la captura de pantalla 2.13 anterior, Shippable ejecuta el proceso de construcción utilizando diferentes versiones de Java y almacena los resultados de las pruebas. 2.3.3 TÉCNICAS EMPLEADAS El objetivo de la presente sección es destacar algunas de las técnicas de desarrollo empleadas para superar algunas de las limitaciones encontradas dentro del lenguaje Java descritas anteriormente. Funciones de Primera Clase Como se indicó en la sección 2.1.2, una de las principales limitaciones de Java frente a Haskell, es el no poder utilizar una función como un objeto de primer orden, es decir como una cadena de texto o un número. Para poder solventar la limitación descrita anteriormente, es necesario implementar una estructura de datos la cual represente una función, dicha implementación debe ser abstracta ya que si bien la estructura básica de una función es la misma - toma datos de entrada y genera un resultado -, su comportamiento es diferente. La interfaz descrita en el fragmento de código 2.21 a continuación, será utilizada a 82 lo largo del proyecto como la representación de una función. 1 public i n t e r f a c e F<A , B> { 2 B c a l l (A x ) ; 3 } Fragmento de Código 2.21: Estructura de Datos Función A simple vista la estructura de datos definida en el fragmento de código 2.21 parece sencilla, sin embargo más adelante, se podrá observar como se utiliza para la construcción de funciones más complejas como son las funciones de orden superior. Funciones de Orden Superior Una vez definida una estructura de datos para una función, es posible utilizarla para la construcción de funciones de orden superior, a continuación un ejemplo: 1 public <A , B> L i s t <B> map( F<A , B> f , L i s t <A> xs ) { 2 L i s t <B> r e s u l t = new A r r a y L i s t <B>() ; 3 f o r (A x : xs ) { 4 r e s u l t . add ( f . c a l l ( x ) ) ; 5 } 6 return r e s u l t ; 7 } Fragmento de Código 2.22: Ejemplo de Función de Orden Superior Como se puede observar en el fragmento de código 2.22 anterior, la función map toma como argumento una función f, la cual es aplicada a cada uno de los elementos de la lista, por ejemplo dada la siguiente función sum: 83 1 public F<I n t e g e r , I n t e g e r > sum ( f i n a l i n t x ) { 2 r e t u r n new F<I n t e g e r , I n t e g e r >() { 3 public I n t e g e r c a l l ( I n t e g e r y ) { 4 return x + y ; 5 } 6 7 }; } Fragmento de Código 2.23: Función Parcialmente Aplicada La función sum definida en el fragmento de código 2.23 anterior, puede ser utilizada como argumento de la función map de la siguiente manera: 1 > map(sum( 1 ) , [ 1 , 2 , 3 ] ) ; 2 [2 , 3 , 4] Fragmento de Código 2.24: Ejemplo de Uso Función de Orden Superior Currying La estructura de datos creada en la sección 2.3.3 para representar una función, permite la implementación de funciones las cuales retornen como resultado otra función en Java. Dicha caracterı́stica permite la implementación del proceso de currying en Java, sin embargo a diferencia de Haskell, en donde este proceso es automático, en Java será creado de manera explı́cita y empleando sobrecarga de funciones. El fragmento de código 2.25 a continuación, es un ejemplo de la implementación del proceso de currying en Java: 84 1 public i n t sum ( i n t x , i n t y ) { 2 3 return x + y ; } 4 5 public F<I n t e g e r , I n t e g e r > sum ( f i n a l i n t x ) { 6 r e t u r n new F<I n t e g e r , I n t e g e r >() { 7 public I n t e g e r c a l l ( I n t e g e r y ) { 8 r e t u r n sum ( x , y ) ; 9 } 10 11 } } Fragmento de Código 2.25: Ejemplo de Currying Como se puede observar en el fragmento de código 2.25, el uso de sobrecarga de funciones permite crear diferentes versiones de una función, dichas funciones toman un número diferente de argumentos, tal y como se puede observar en el fragmento de código 2.26 a continuación 1 > sum ( 1 , 2 ) ; 2 3 3 > sum ( 1 ) . c a l l ( 2 ) ; 4 3 Fragmento de Código 2.26: Ejemplo de Uso de Currying Aplicación Parcial de Funciones Empleando la técnica de currying 2.3.3 definida en la sección anteriormente, se puede hacer uso de ella, para la creación de funciones parcialmente aplicadas, como se indica en el fragmento de código 2.27 a continuación: 85 1 > map(sum( 1 ) , [ 1 , 2 , 3 ] ) ; 2 [2 , 3 , 4] Fragmento de Código 2.27: Ejemplo de Aplicación Parcial de Funciones 2.4 PRUEBAS Para el desarrollo de las pruebas, se utilizarán tanto los niveles como categorı́as de pruebas descritos por International Software Testing Qualifications Board (ISTQB) el cual es un estándar internacional de pruebas, basta mente utilizado en la industria de desarrollo de software. Los niveles y categorı́as de pruebas de ISTQB junto con TDD serán utilizados para elaborar el plan de pruebas el cual cubrirá todos los distintos aspectos de la librerı́a ha ser desarrollada, desde las caracterı́sticas funcionales, hasta las no funcionales como por ejemplo la compatibilidad con JDK 6. A continuación un listado de algunas de las principales razones por las cuales se prueba el software tomadas de la página de ISTQB [9]: Probar el software es realmente requerido para identificar defectos y errores realizados durante la etapa de desarrollo. Es esencial ya que asegura que la confiabilidad y satisfacción del cliente en la aplicación. Es muy importante para asegurar la calidad del producto. Calidad del producto entregada al cliente ayuda a ganar su confianza. Probar es necesario para proveer las facilidades al cliente como le entrega de un producto de software de alta calidad el cual requiera poco costo de mantenimiento y por lo tanto resulte más conveniente, consistente y confiable. Probar es necesario para un desempeño eficaz del producto de software. 86 Probar es importante para asegurar que una aplicación no resulte en fracaso debido a que resulte muy costosa en el futuro o en etapas posteriores del desarrollo. Es requerido para mantenerse en el negocio. 2.4.1 NIVELES DE PRUEBAS Los niveles de pruebas son básicos para identificar áreas faltantes y prevenir sobreescritura y repetición entre las fases del ciclo de desarrollo. En software el ciclo de vida por modelos define fases como la obtención de requerimientos y análisis, diseño, codificación o implementación, pruebas y despliegue [8]. El listado de niveles de pruebas a continuación, ha sido tomado del programa de estudio de nivel básico de ISTQB ??: Pruebas de Componente Tienen por objeto localizar defectos y comprobar el funcionamiento de módulos de software, programas, objetos, clases, etc. que pueden probarse por separado. Pruebas de Integración Las pruebas de integración se ocupan de probar las interfaces entre los componentes, las interacciones con distintas partes de un mismo sistema, como el sistema operativo, el sistema de archivos y el hardware, y las interfaces entre varios sistemas. Pruebas de Sistema Las pruebas de sistema deben estudiar los requisitos funcionales y no funcionales del sistema y las caracterı́sticas de calidad de los datos. Pruebas de Aceptación El objetivo de las pruebas de aceptación es crear confianza en el sistema, partes del sistema o caracterı́sticas especı́ficas no funcionales del sistema. Una vez definidos los niveles de pruebas, a continuación se listan los niveles a ser utilizados por el presente proyecto: 87 Nivel Pruebas de Componente Utilizado? Sı́ Descripción Como se indicó en la sección 2.3.1, el desarrollo completo de la librerı́a se realizará utilizado TDD, para lo cual se partirá escribiendo pruebas de métodos hasta llegar al nivel de clases. Pruebas de Integración Sı́ Como se indicó en la sección 2.1.1, la librerı́a desarrollada debe ser compatible con versiones nuevas y antiguas de la JDK, por lo que es necesario ejecutar las pruebas utilizando diferentes infraestructuras, una por cada versión diferente de Java. Pruebas de Sistema Sı́ Los requisitos funcionales del sistema son que los módulos migrados a Java, cumplan con el mismo comportamiento que su par escrito en Haskell. Cada módulo de Haskell describe funciones y estructuras de datos las al ser importadas a Java cuales serán probados a nivel de componentes. El único requisito no funcional del sistema, es que sea compatible con versiones antiguas y nuevas de las JDK, lo cual se encuentra cubierto por las pruebas de integración como se indicó anteriormente. 88 Nivel Utilizado? Pruebas de Aceptación No Descripción Debido a que las pruebas de aceptación se enfocan en la entrega y despliegue del sistema, no serán utilizadas en el proyecto, ya que la construcción de una librerı́a no involucra ninguna de las dos actividades mencionadas anteriormente. Tabla 2.7: Niveles de Pruebas Utilizados 2.4.2 TIPOS DE PRUEBAS Los tipos de pruebas, son categorı́as de pruebas utilizados para identificar el objeto de prueba sobre el cual se enfocan. El listado de tipos de pruebas a continuación, ha sido tomado del programa de estudio de nivel básico de ISTQB [23]: Pruebas Funcionales Las funciones que un sistema, subsistema o componente debe llevar a cabo pueden describirse en productos de trabajo tales como una especificación de requisitos, casos de uso o una especificación funcional, o incluso pueden no estar documentadas. Pruebas No Funcionales Las pruebas no funcionales incluyen, pero sin limitarse a ello, pruebas de rendimiento, pruebas de carga, pruebas de estrés, pruebas de usabilidad, pruebas de mantenibilidad, pruebas de fiabilidad y pruebas de portabilidad. Pruebas Estructurales Las pruebas estructurales (caja blanca) pueden realizarse en todos los niveles de prueba. Las técnicas estructurales son las más idóneas, después de las técnicas basadas en la especificación, para ayudar a medir exhaustividad de las pruebas mediante una evaluación de la cobertura de un tipo de estructura. 89 Pruebas de Regresión Las pruebas de regresión son la prueba reiterada de un programa ya probado, después de haber sido modificado, con vistas a localizar defectos surgidos o no descubiertos como resultado del cambio de los cambios. A continuación un listado de los tipos de pruebas, a ser utilizados durante el desarrollo del presente proyecto: Tipo de Prueba Pruebas Funcionales Utilizado? Sı́ Descripción Como se indicó en la sección 2.1.3, el comportamiento de las funciones importadas a Java, ya se encuentra definido por su contra parte en Haskell, por lo que toda prueba funcional debe reflejar el comportamiento original de toda función o estructura de datos escrita originalmente en Haskell. Pruebas No Funcionales Sı́ Como se indicó en la sección 2.1.1, la librerı́a desarrollada deber ser compatible con diferentes versiones de Java, para lo cual se utilizarán pruebas no funcionales para evaluar la compatibilidad de la librerı́a. 90 Tipo de Prueba Utilizado? Pruebas Estructurales Sı́ Descripción Un nivel alto de cobertura de pruebas es importante, para realizar un proceso de refactorización sin introducir nuevos errores al sistema. En la sección 2.4.4 a continuación se definirán algunos de los tipos de cobertura de pruebas utilizados en el proyecto. Pruebas de Regresión Sı́ Como se indicó en la sección 2.3.1 al utilizar TDD, para el desarrollo de la librerı́a, se obtiene un alto nivel de cobertura de pruebas, por lo tanto cualquier cambio en el comportamiento del sistema puede ser detectado por las pruebas escritas a lo largo del desarrollo. Tabla 2.8: Tipos de Pruebas Utilizados 2.4.3 PLANIFICACIÓN DE PRUEBAS Es importante destacar que dentro de la planificación, las pruebas del sistema forman parte de la etapa de desarrollo misma, ya que se utilizará un desarrollo guiado por pruebas, más conocido como TDD el cual involucra diseñar el software a partir de las pruebas. Herramientas A lo largo del proceso de desarrollo y pruebas, se utilizará un conjunto de herramientas y servicios externos para la construcción de la librerı́a funcional. Dichas herramientas y servicios externos se listan a continuación: 91 Gradle 1 Herramienta de lı́nea de comandos, utilizada para integración continua de proyectos escritos en Java. Una de las principales caracterı́sticas de Gradle es la simplicidad de su archivo de configuración, a diferencia de otras herramientas del mismo tipo como Maven las cuales usan archivos Extensible Markup Language (XML). La figura 2.28 a continuación, muestra el proceso de construcción de un proyecto escrito en Java: 1 > gradle build 2 : compileJava 3 : processResources 4 : classes 5 : jar 6 : assemble 7 : compileTestJava 8 : processTestResources 9 : testClasses 10 : test 11 : check 12 : build Fragmento de Código 2.28: Proceso de Construcción de Gradle A lo largo de la construcción de la librerı́a funcional, Gradle será utilizado para realizar las siguientes tareas: Resolución de dependencias. Construcción del proyecto. Ejecución de pruebas. 1 Sitio: https://gradle.org 92 JUnit 2 Es una librerı́a para la construcción de pruebas en Java, permite evaluar el comportamiento de métodos y clases mediante la creación de aserciones. JUnit también posee la capacidad de generar reportes de pruebas en formato HTML. A continuación un listado de las tareas que se realizarán utilizando JUnit: Creación de pruebas. Generación reporte de pruebas. PiTest 3 A diferencia de JUnit, no es una librerı́a utilizada para construcción de pruebas, en su lugar es utilizada para la generación de reportes. Dichos reportes de pruebas se generan a partir de la generación de pruebas de mutación, tal y como se indica en la sección 2.4.5, una vez ejecutadas dichas pruebas el reporte presenta el porcentaje de cobertura de código. Las tareas realizadas durante la construcción de la librerı́a funcional son las siguientes: Generación reporte de cobertura de pruebas. Bitbucket 4 Es un servicio externo utilizado para el versionamiento de proyectos. Dicho servicio en lı́nea permite compartir el proyecto con diferentes miembros de un equipo, como al igual que mantener un historial de cambios, lo cual resulta útil para revertir cambios que introduzcan errores o conflictos en el proyecto. 2 Sitio: http://junit.org Sitio: http://pitest.org 4 Sitio: https://bitbucket.org 3 93 Bitbucket al igual que GitHub, provee integración con algunas herramientas, principalmente aquellas utilizadas para la integración continua de proyectos como Travis, Shippable, entre otros. Principalmente a lo largo del proyecto, Shippable será utilizado para las siguientes tareas: Control de cambios. Integración continua. Shippable 5 Servicio externo utilizado para la integración continua 2.3.2 de proyectos de software. Shippable provee integración con repositorios tanto de GitHub, como de Bitbucket. A continuación un listado de las tareas a ser realizadas utilizando Shippable: Integración continua. Ejecución de pruebas (utilizando diferentes versiones de JDK). Planificación El diagrama a continuación 2.14, muestra el plan de pruebas para el presente proyecto junto con las herramientas utilizadas en cada etapa del proceso de pruebas. 5 Sitio: https://app.shippable.com 94 Crear la prueba Escribir/Cambiar la implementacion Ejecutar la prueba (Gradle + JUnit) Versionar el codigo (Git + Bitbucket) Integracion continua (Shippable) Analizar resultados de cobertura (Shippable) Figura 2.14: Diagrama de Flujo - Planificación de Pruebas 2.4.4 COBERTURA DE PRUEBAS Es la manera de medir que cantidad de código se encuentra probado, a diferencia de ejecutar una prueba en donde solo se comprueba que las aserciones definidas en la prueba sean las correctas, la cobertura de código mide que porcentaje de lı́neas de código fue ejecutado durante la prueba a diferencia de comprobar el comportamiento. Dijkstra una vez dijo: “Probar nunca prueba la ausencia de una falla, solo demuestra su existencia.” Antes de poder definir un porcentaje de cobertura de código aceptable en el presente proyecto se deben tomar en cuenta algunas recomendaciones propuestas por Martin Fowler sobre cuando se hacen suficientes pruebas: Cuando rara vez se escapan errores a producción. 95 Cuando rara vez se tiene miedo a cambiar código pensando que este produzca un error en producción. Los dos puntos mencionados anteriormente encajan perfectamente en el concepto de confidencia en el código. ¿Qué es confidencia de código? - Es la certeza que tenemos como desarrolladores en poder realizar modificaciones al código sin tener que preocuparnos por introducir nuevos errores. Para el presente proyecto se utilizaron dos diferentes maneras de medir la cobertura de código, una tradicional y una que nace a partir de un concepto denominado pruebas de mutación. Cobertura por Lı́neas de Código Es una las métricas de cobertura de código más tradicionales y simples. Si una sentencia de código es accionada durante la ejecución de una prueba se puede considerar que la sentencia se encuentra cubierta. Cobertura por Mutación En la sección a continuación se definirá de una manera más clara el concepto mutación, sin embargo de momento podemos afirmar que una mutación es una falla generada en el sistema la cual podrı́a o no ser detectada por las pruebas. La cobertura por mutación se encarga de medir el número de mutaciones detectadas por el conjunto de pruebas existentes, es decir a mayor cantidad de mutaciones sin detectar menor será el porcentaje de cobertura de código. 2.4.5 PRUEBAS DE MUTACIÓN Las pruebas de mutación consisten en identificar fallas introducidas en el sistema utilizando las pruebas definidas en el sistema. Dichas fallas introducidas se las conoce como mutaciones, una mutación se considera muerta cuando por lo menos 96 una prueba falla debido a dicho mutación caso contrario se considera que la mutación continua viva es decir no existen suficientes pruebas para identificar la falla introducida en el sistema. La figura 2.15 a continuación describe el proceso de identificación de mutaciones: Figura 2.15: Flujo de Trabajo Pruebas de Mutación Fuente: http://www3.nd.edu/ rsanteli/pate-sp12/readings/Mutation-survey.pdf El flujo de la figura 2.15 anterior, se detalla a continuación: 1. Entrada el programa a ser probado. 2. Crear mutantes. 3. Entradas conjunto de pruebas. 4. Ejecutar la prueba en el programa. 97 5. La prueba del programa se ejecuta correctamente? 5.1. No - Arreglar el programa. 5.2. Sı́ - Ejecutar las pruebas en cada una de las mutaciones existentes. 5.2.1. Todos las mutaciones han muerto? 5.2.1.1. Sı́ - Analizar y marcar mutaciones equivalentes, ir al punto 3. 5.2.1.2. No - Salir. 2.4.6 EJEMPLOS DE PRUEBAS La presente sección contiene algunos de los resultados obtenidos durante el proceso de pruebas, el objetivo principal es relacionar cada uno de las pruebas expuestas en las subsecciones a continuación, con los niveles y tipos de pruebas expuestos en las secciones 2.4.1 y 2.4.2 respectivamente. Pruebas de Compatibilidad Durante el proceso de integración continua, con el fin de garantizar la compatibilidad de la librerı́a con diferentes versiones de Java, las pruebas fueron ejecutadas utilizando diferentes configuraciones de entornos, tal y como se puede observar en la figura 2.16 a continuación: 98 Figura 2.16: Pruebas de Compatibilidad Fuente: https://app.shippable.com Este tipo de pruebas, encaja dentro de las categorı́as de prueba listadas en la tabla 2.9 a continuación: Categorı́a de Pruebas Pruebas Nivel Integración Nivel Sistema Tipo No Funcionales Tabla 2.9: Categorı́as de Pruebas - Pruebas de Compatibilidad Cobertura de Pruebas Como se indicó en la sección 2.4.4, un alto nivel de cobertura de pruebas tiene una menor oportunidad de contener errores, al igual que facilita la refactorización de código, debido a que existe un mayor número de pruebas de regresión. 99 La figura 2.17 a continuación muestra el porcentaje de cobertura de código, utilizando los dos métodos descritos en la sección 2.4.4: Figura 2.17: Resumen de Cobertura de Pruebas Fuente: http://jprelude.bitbucket.org/reports Este tipo de pruebas, encaja dentro de las categorı́as de prueba listadas en la tabla 2.10 a continuación: Categorı́a de Pruebas Pruebas Nivel Componente Tipo Regresión Tabla 2.10: Categorı́as de Pruebas - Cobertura de Pruebas Pruebas de Mutación Como se indicó en la sección 2.4.5, las pruebas de mutación ayudan a encontrar áreas de código sin probar, lo cuál permite examinar casos de prueba faltantes e identifica pruebas faltantes. La figura 2.18 a continuación, muestra el porcentaje de cobertura de código, por pruebas de mutación para la función isNothing: Figura 2.18: Resumen de Cobertura de Pruebas Fuente: http://jprelude.bitbucket.org/reports/jprelude.data.maybe/Maybes.java.html En la figura 2.19, a continuación se muestra listado de las mutaciones utilizadas, para la pruebas de mutación de la figura 2.18 anterior: 100 Figura 2.19: Mutantes Activos Fuente: http://jprelude.bitbucket.org/reports/jprelude.data.maybe/Maybes.java.html Este tipo de pruebas, encaja dentro de las categorı́as de prueba listadas en la tabla 2.10 a continuación: Categorı́a de Pruebas Pruebas Nivel Componente Tipo No Funcionales Tipo Estructurales Tabla 2.11: Categorı́as de Pruebas - Pruebas de Mutación Pruebas Unitarias Como se indicó en la sección 2.3.1, a lo largo de todo el desarrollo de la librerı́a se escribirá primero la prueba antes que la implementación, por lo tanto la cobertura de pruebas será alta ya que cada prueba definirá el comportamiento de las funciones importadas desde Haskell. A continuación un ejemplo 2.29 de las pruebas unitarias, utilizadas para describir el comportamiento de la función isNothing: 101 1 @Test 2 public void t e s t I s N o t h i n g R e t u r n s T r u e ( ) { 3 4 a s s e r t T h a t ( i s N o t h i n g (new Nothing ( ) , i s ( t r u e ) ) ; } 5 6 @Test 7 public void t e s t I s N o t h i n g R e t u r n s F a l s e ( ) { 8 9 a s s e r t T h a t ( i s N o t h i n g (new J u s t ( 1 ) , i s ( f a l s e ) ) ; } Fragmento de Código 2.29: Ejemplo de Prueba Unitaria Este tipo de pruebas, encaja dentro de las categorı́as de prueba listadas en la tabla 2.12 a continuación: Categorı́a de Pruebas Pruebas Nivel Componente Tipos Regresión Tabla 2.12: Categorı́as de Pruebas - Pruebas de Unitarias 2.4.7 REPORTE DE PRUEBAS Como se indicó anteriormente se medirá el rango de cobertura de código utilizando pruebas de mutación, para ello se emplea un herramienta que genere reportes de pruebas de mutación. La herramienta utilizada para este propósito se denomina PIT acrónimo de PiTest, es una herramienta escrita en Java la cual ejecuta pruebas de mutación y genera reportes en varios formatos incluidos HTML. La figura 2.20 a continuación muestra el reporte de pruebas obtenido: 102 Figura 2.20: Reporte de Pruebas Como se puede observar en la figura 2.20 anterior, existe un 94 % de cobertura de pruebas y un 97 % de cobertura de mutación, dichas cifras no solo son un indicador de los siguientes puntos: Un 94 % del código escrito ha sido ejecutado durante las pruebas. Un 97 % de cambios en el código es detectado por las pruebas escritas. Durante la refactorización del código existe un 97 % de detectar errores introducidos debido a la refactorización. El 94 % del comportamiento de las funciones escritas originalmente en Haskell, ha sido capturado por el conjunto de pruebas escrito en Java. CAPÍTULO 3. APLICACIÓN DE LA LIBRERÍA FUNCIONAL A UN CASO DE ESTUDIO El presente capı́tulo tiene como objetivo evaluar los resultados obtenidos al refactorizar un caso de estudio, utilizando la librerı́a funcional desarrollada. Dichos resultados serán utilizados para demostrar las mejoras obtenidas, al emplear patrones de programación funcional mediante el uso de la librerı́a, dentro de un lenguaje orientado a objetos como es Java. 3.1 REFACTORIZACIÓN FUNCIONAL DE UNA APLICACIÓN ORIENTADA A OBJETOS Para ilustrar el uso práctico de la librerı́a desarrollada, se procederá a seleccionar un caso de estudio, el cual se refactorizará utilizando la librerı́a funcional desarrollada. Una vez concluido el proceso de refactorización se evaluarán los resultados obtenidos. 3.1.1 SELECCIÓN DEL CASO DE ESTUDIO Existe una amplia gama de librerı́as dentro del mundo de desarrollo de Java, por lo cual es necesario definir un criterio de selección, el cual permita escoger el caso de estudio, que se ajuste a las necesidades del proyecto. 103 104 Criterio de Selección (CS) Antes de poder seleccionar un caso de estudio, es necesario definir un conjunto de métricas las cuales permitan seleccionar el caso de estudio de una manera objetiva. Las métricas utilizadas para la selección del caso de estudio se describen a continuación: Porcentaje de Uso (PU) Es el porcentaje de referencias que tiene una librerı́a, a lo largo de los diferentes proyectos almacenados en GitHub. Número de Problemas Reportados (PR) Es el número de problemas o inconvenientes encontrados, en un proyecto de software. Número de Solicitudes de Cambio (SC) Es el número de solicitudes de cambio enviadas a un proyecto de software. Es importante señalar, que los valores para las métricas mencionadas anteriormente, serán tomados tanto de GitHub como de JIRA, a continuación un ejemplo: Figura 3.1: Repositorio de JUnit en GitHub Como se puede observar en la figura 3.1 anterior, para la librerı́a JUnit, existe una cantidad de 114 Números de Problemas Reportados y 31 Número de Solicitudes de Cambio. Después de haber definido como serán medidos los valores para cada una de las métricas de selección, es necesario saber interpretar dichos valores, la tabla 3.1 a continuación muestra un ejemplo: 105 Métrica Valor Menor Valor Mayor Porcentaje de Uso Indica un menor interés Indica un mayor interés por parte de la comuni- por parte de la comuni- dad de software en la li- dad de software en la li- brerı́a, ya que existe un brerı́a, ya que existe un bajo número de referen- alto número de referen- cias dentro de otros pro- cias dentro de otros pro- yectos. yectos. Número de Problemas Indica que la librerı́a es Indica que la librerı́a es Reportados más estable, ya que exis- menos estable, ya que te una menor cantidad de existe una mayor canti- errores reportados. dad de errores reportados. Número de Solicitudes Indica un menor interés Indica un mayor interés de Cambio por parte de la comuni- por parte de la comuni- dad de software, en man- dad de software, en man- tener la librerı́a, como un tener la librerı́a, como un proyecto activo de soft- proyecto activo de soft- ware. ware. Tabla 3.1: Interpretación de Métricas Posibles Casos de Estudio Como ya se habı́a indicado anteriormente, existe una amplı́a gama de librerı́as en Java, por lo cual es necesario reducir el alcance de búsqueda, para lo cuál solo se tomarán en cuenta aquellas librerı́as que cumpla con las siguientes caracterı́sticas: Código Abierto Debido a que el caso de estudio seleccionado, será refactorizado, su código original será modificado, por lo que es necesario asegurarse de no violar los derechos de autor. Pruebas Cualquier caso de estudio que no contenga pruebas, será excluido ya que 106 después de la refactorización, se debe garantizar que el comportamiento del software sea el mismo, para lo cuál se requiere que existen pruebas previas. La figura 3.2 a continuación muestra el porcentaje de uso de las 5 librerı́as más utilizadas en Java: 64 % JUnit SLF4J 22 % Log4j 16.76 % Google Guava 15.6 % Apache Commons 12.63 % 0% 20 % 40 % 60 % 80 % Porcentaje de Uso 100 % Figura 3.2: Top 5 Librerı́as de Java más Utilizadas Fuente: JavaWorld [25] A continuación una breve descripción, de cada una de las librerı́as listadas en la figura 3.2 anterior: JUnit Es una librerı́a para la escritura de pruebas repetibles [26]. SLF4J Sirve como una abstracción sobre varias librerı́as de registro de eventos [36]. Log4j Es una librerı́a de registro de eventos [30]. Google Guava Es una librerı́a utilitaria, para el manejo de: colecciones, primitivos, concurrencia, anotaciones,procesamiento de cadenas de texto, IO, etc [17]. Apache Commons Es un proyecto de Apache, enfocado en la reusabilidad de componentes en Java [1]. 107 Selección del Caso de Estudio Una vez definidas tanto las métricas de selección en la sección 3.1.1, como los posibles casos de estudio en la sección 3.1.1, la presente sección tiene como objetivo el determinar el caso de estudio a ser refactorizado. Para la selección del caso de estudio, se usarán todas las métricas previamente definidas en la sección 3.1.1, la tabla 3.3 a continuación muestra los valores obtenidos para cada uno de los posibles casos de estudio: 31 JUnit 64 SLF4J 0 Log4j 2 0 114 44 22 16,76 16 Google Guava 584 15,6 0 Apache Commons 41 12,63 0 100 200 300 400 500 600 700 Número de Solicitudes de Cambio Número de Problemas Reportados Porcentaje de Uso Figura 3.3: Posibles Casos de Estudio (Datos Obtenidos) Como se puede observar en la figura 3.3 anterior, los datos para cada una de las métricas se encuentran en diferentes escalas, sin embargo con el fin de comparar los valores obtenidos es necesario normalizarlos, para lo cuál se utilizará la ecua- 108 ción 3.1 a continuación: X ′ = 100 × X − Xmin Xmax − Xmin (3.1) En donde, X ′ es el valor normalizado, X es el valor de la métrica (porcentaje de uso, número de problemas reportados o número de solicitudes de cambio), Xmin es el valor mı́nimo de la métrica y Xmax es el valor máximo de la métrica. A continuación un ejemplo de normalización de datos, aplicado a los datos previamente obtenidos (ver figura 3.3) de la librerı́a SLF4J: P U ′ = 100 × 22 − 12,63 64 − 12,63 (3.2) 0−0 584 − 0 (3.3) 44 − 0 44 − 0 (3.4) = 18,24 P R′ = 100 × =0 SC ′ = 100 × = 100 La figura 3.4 a continuación, muestra los resultados obtenidos al normalizar todos los valores previamente obtenidos (ver figura 3.3), para cada uno de los posibles casos de estudio: 109 70 20 JUnit SLF4J 0 Log4j 0 100 100 18,14 4 8,04 36 Google Guava 100 5,78 0 Apache Commons 0 0 7 10 20 30 40 50 60 70 80 90 100 Número de Solicitudes de Cambio Número de Problemas Reportados Porcentaje de Uso Figura 3.4: Posibles Casos de Estudio (Datos Normalizados) Antes de utilizar los valores normalizados de la figura 3.4 es necesario ajustarlos, ya que como se indicó en la sección 3.1.1 la interpretación de un valor, difiere dependiendo la métrica de selección sobre la cual sea evaluada. Dicho esto los valores de la métrica número de problemas reportados deben ser ajustados, para que su interpretación coincida con la de las otras métricas existentes, ya que a diferencia del porcentaje de uso o el número de solicitudes de cambio, un valor más alto no representa un mejor criterio de selección, al contrario significa que la librerı́a es más inestable comparada con las otras. El ajuste de datos se realizará solo sobre la métrica número de problemas reporta- 110 dos, para lo cual se utilizará la ecuación 3.5 a continuación: P R′′ = P Rmax − P R′ (3.5) En donde, P R′′ es el número de problemas reportados ajustado y P R′ es el número de problemas reportados normalizado. A continuación un ejemplo de ajuste de datos, aplicado a los datos previamente normalizados (ver figura 3.4) de la librerı́a JUnit: P R′′ = 100 − 20 (3.6) = 80 La figura 3.5 a continuación, muestra los valores ajustados para todos los casos de estudio evaluados: 111 70 80 JUnit SLF4J 100 100 100 18,14 4 Log4j 100 8,04 Google Guava 0 36 5,78 0 Apache Commons 93 0 0 10 20 30 40 50 60 70 80 90 100 Número de Solicitudes de Cambio Número de Problemas Reportados Porcentaje de Uso Figura 3.5: Posibles Casos de Estudio (Datos Ajustados) Una vez obtenidos normalizados y ajustados los valores para cada una de las métricas en la figura 3.5, se procederá a calcular el valor del criterio de selección para cada uno de los casos de estudio, utilizando la ecuación 3.7 a continuación: CS = P U ′′ + P R′′ + SC ′′ 3 (3.7) En donde, CS es el criterio de selección, P U ′′ es el porcentaje de uso normalizado, P R′′ es el número de problemas reportados, y SC ′′ es el número de solicitudes de cambio. Como se puede observar en la ecuación 3.7 anterior, el valor del criterio de selección es el promedio de las tres métricas (porcentaje de uso, número de problemas reportados y número de solicitudes de cambio). 112 La figura 3.6 a continuación muestra el valor del criterio de selección para cada uno de los posibles casos de estudio evaluados: 35.01 % 30.45 % 12.98 % JUnit SLF4J Log4j Google Guava Apache Commons 15.69 % 5.87 % Figura 3.6: Criterio de Selección Como se puede observar en la figura 3.6, la librerı́a que se ajusta mejor al criterio de selección es JUnit con un 35.01 %, por lo tanto será la librerı́a utilizada como caso de estudio. 3.1.2 REFACTORIZACIÓN DEL CASO DE ESTUDIO Una vez seleccionado el caso de estudio, es necesario definir el alcance de la refactorización, para lo cual se utilizará como criterio de selección el número de lı́neas de código (LOC) de cada clase. La tabla 3.2 a continuación muestra el listado de las 5 clases con mayor número de lı́neas de código dentro de la librerı́a JUnit: 113 Clase Lı́neas de Código org.junit.Assert 316 org.junit.runners.ParentRunner 261 org.junit.runners.BlockJUnit4ClassRunner 234 org.junit.experimental.categories.Categories 233 org.junit.runners.model.TestClass 233 Tabla 3.2: Número de Lı́neas de Código por Clase Como se puede observar en la tabla 3.2 anterior, la clase con mayor número de lı́neas de código es Assert. Es importante resaltar que la versión de JUnit sobre la cual se trabajó es la versión 4.12 (última versión a la fecha 24/12/2015). A continuación se listan 5 métodos de la librerı́a JUnit refactorizados, el resto se encuentran en el siguiente repositorio https://bitbucket.org/jprelude/junit en BitBucket: Método fail El método fail es ejecutado para lanzar una excepción, tomando un mensaje de error como argumento. Si el mensaje de error pasado como argumento es nulo, fail lanza una excepción sin mensaje de error, caso contrario lanza una excepción utilizando el mensaje de error pasado como argumento. El fragmento de código 3.1 a continuación, muestra el código fuente original del método fail: 1 s t a t i c public void f a i l ( S t r i n g message ) { 2 i f ( message == n u l l ) { 3 throw new A s s e r t i o n E r r o r ( ) ; 4 } 5 throw new A s s e r t i o n E r r o r ( message ) ; 6 } Fragmento de Código 3.1: Método fail Antes de la Refactorización 114 Una vez refactorizado el fragmento de código 3.1 original, el resultado obtenido es el siguiente: 1 s t a t i c public void f a i l ( S t r i n g message ) { 2 throw maybe (new A s s e r t i o n E r r o r ( ) , new F<S t r i n g , A s s e r t i o n E r r o r >() { 3 public A s s e r t i o n E r r o r c a l l ( S t r i n g message ) { 4 5 6 r e t u r n new A s s e r t i o n E r r o r ( message ) ; } } , j u s t ( message ) ) ; 7 } Fragmento de Código 3.2: Método fail Después de la Refactorización Comparando los dos fragmentos de código 3.1 y 3.2, se puede observar que se ha eliminado el condicional existente. Método equalsRegardingNull El método equalsRegardingNull es utilizado para comparar dos objetos, indistintamente, si son nulos o no. Si el primero objeto pasado como argumento es nulo y el segundo objeto es nulo, los objetos son iguales, caso contrario se procede a compáralos. El fragmento de código 3.3 a continuación, muestra el código fuente original del método equalsRegardingNull: 115 1 p r i v a t e s t a t i c boolean e q u a l s R e g a r d i n g N u l l ( O b j e c t expected , O b j e c t a c t u a l ) { 2 i f ( expected == n u l l ) { 3 4 r e t u r n a c t u a l == n u l l ; } 5 6 r e t u r n i s E q u a l s ( expected , a c t u a l ) ; 7 } Fragmento de Código 3.3: Método equalsRegardingNull Antes de la Refactorización Una vez refactorizado el fragmento de código 3.3 original, el resultado obtenido es el siguiente: 1 p r i v a t e s t a t i c boolean e q u a l s R e g a r d i n g N u l l ( O b j e c t expected , O b j e c t a c t u a l ) { 2 r e t u r n maybe ( a c t u a l == n u l l , new F<Object , Boolean >() { 3 public Boolean c a l l ( O b j e c t expected ) { 4 r e t u r n i s E q u a l s ( expected , a c t u a l ) ; 5 6 } } , j u s t ( expected ) ) ; 7 } Fragmento de Código 3.4: Método equalsRegardingNull Después de la Refactorización Comparando los dos fragmentos de código 3.3 y 3.4, se puede observar que se ha eliminado el condicional existente. Métodos doubleIsDifferent y floatIsDifferent Los métodos doubleIsDifferent y floatIsDifferent, comparan si dos números del tipo Double o Float son diferentes, primero compara dichos números utilizando el método compare, si dichos números son iguales comprueba si la diferencia del valor 116 absoluto de dichos números no es menor o igual al valor de desviación delta. Los fragmentos de código 3.5, 3.6 y 3.9 a continuación, muestra el código fuente original de los métodos doubleIsDifferent y floatIsDifferent: 1 s t a t i c p r i v a t e boolean d o u b l e I s D i f f e r e n t ( double d1 , double d2 , double d e l t a ) { 2 i f ( Double . compare ( d1 , d2 ) == 0 ) { 3 return false ; 4 } 5 i f ( ( Math . abs ( d1 − d2 ) <= d e l t a ) ) { 6 return false ; 7 } 8 return true ; 9 } Fragmento de Código 3.5: Método doubleIsDifferent Antes de la Refactorización 1 s t a t i c p r i v a t e boolean f l o a t I s D i f f e r e n t ( f l o a t f1 , f l o a t f2 , f l o a t d e l t a ) { 2 i f ( F l o a t . compare ( f1 , f 2 ) == 0 ) { 3 return false ; 4 } 5 i f ( ( Math . abs ( f 1 − f 2 ) <= d e l t a ) ) { 6 return false ; 7 } 8 return true ; 9 } Fragmento de Código 3.6: Método floatIsDifferent Antes de la Refactorización Una vez refactorizados los fragmentos de código 3.5 y 3.6 originales, el resultado obtenido es el siguiente: 117 1 s t a t i c p r i v a t e boolean d o u b l e I s D i f f e r e n t ( double d1 , double d2 , double d e l t a ) { 2 r e t u r n i s D i f f e r e n t (Nums .DOUBLE, d1 , d2 , d e l t a ) ; 3 } Fragmento de Código 3.7: Método doubleIsDifferent Después de la Refactorización 1 s t a t i c p r i v a t e boolean f l o a t I s D i f f e r e n t ( f l o a t f1 , f l o a t f2 , f l o a t d e l t a ) { 2 r e t u r n i s D i f f e r e n t (Nums . FLOAT, f1 , f2 , d e l t a ) ; 3 } Fragmento de Código 3.8: Método floatIsDifferent Después de la Refactorización Tanto la función doubleIsDifferent, como la función floatIsDifferent reutilizan la función isDifferent definida en el fragmento de código 3.9 a continuación: 1 s t a t i c p r i v a t e <T extends Comparable<T>> boolean i s D i f f e r e n t (Num<T> num , T n1 , T n2 , T d e l t a ) { 2 r e t u r n ! ( n1 . equals ( n2 ) | | num . abs (num . s u b t r a c t i o n ( n1 , n2 ) ) . compareTo ( d e l t a ) <= 0 ) ; 3 } Fragmento de Código 3.9: Método isDifferent Como se puede observar en los fragmentos de código anteriores, no solo se han eliminado todos los condicionales, se ha extraı́do todo el código duplicado de los métodos doubleIsDifferent y floatIsDifferent al método isDifferent. Método formatClassAndValue El fragmento de código 3.10 a continuación, muestra el código fuente original del método formatClassAndValue: 118 1 p r i v a t e s t a t i c S t r i n g formatClassAndValue ( O b j e c t value , String valueString ) { 2 S t r i n g className = v a l u e == n u l l ? ” n u l l ” : v a l u e . g e t C l a s s ( ) . getName ( ) ; 3 r e t u r n className + ”<” + v a l u e S t r i n g + ”>” ; 4 } Fragmento de Código 3.10: Método formatClassAndValue Antes de la Refactorización Una vez refactorizado el fragmento de código 3.10 original, el resultado obtenido es el siguiente: 1 p r i v a t e s t a t i c S t r i n g formatClassAndValue ( O b j e c t value , String valueString ) { 2 S t r i n g className = maybe ( ” n u l l ” , new F<Object , S t r i n g >() { 3 public S t r i n g c a l l ( O b j e c t v a l u e ) { 4 5 r e t u r n v a l u e . g e t C l a s s ( ) . getName ( ) ; } 6 } , j u s t ( value ) ) ; 7 r e t u r n className + ”<” + v a l u e S t r i n g + ”>” ; 8 } Fragmento de Código 3.11: Método formatClassAndValue Déspues de la Refactorización 3.2 ANÁLISIS DE RESULTADOS Antes de poder realizar un análisis de resultados, es necesario cuantificar los resultados obtenidos antes y después del proceso de refactorización, con el fin de poder analizar los resultados obtenidos. 119 El análisis de resultados, se lo realizará comparando la complejidad del código antes y después de la refactorización. La complejidad del código será medida utilizando la métrica complejidad ciclomática. 3.2.1 COMPLEJIDAD CICLOMÁTICA (CC) La complejidad ciclomática, mide la cantidad de lógica de decisión dentro de un módulo de software [2]. El cálculo de la complejidad ciclomática, se lo realiza en función de las métricas que se describen a continuación: Número de Condiciones Es el número de condiciones dentro de una función. Número de Retornos o Salidas Es el número de retornos dentro de una función o salidas, por ejemplo el lanzamiento de una excepción. Como se habı́a mencionado anteriormente, el cálculo de la complejidad ciclomática se lo realiza en función de otras métricas, tal y como se describe en la ecuación 3.8 a continuación: CC = C + RoS (3.8) En donde CC es la complejidad ciclomática, C es el número de condiciones, y RoS es el número de retornos o salidas de una función: A continuación un ejemplo de cálculo de la complejidad ciclomática para el método doubleIsDifferent definido dentro de la clase Assert: 120 1 s t a t i c p r i v a t e boolean d o u b l e I s D i f f e r e n t ( double d1 , double d2 , double d e l t a ) { 2 i f ( Double . compare ( d1 , d2 ) == 0 ) { 3 return false ; 4 } 5 i f ( ( Math . abs ( d1 − d2 ) <= d e l t a ) ) { 6 return false ; 7 } 8 return true ; 9 } Fragmento de Código 3.12: Método doubleIsDifferent Como se puede observar en el fragmento de código 3.12 anterior, existen 2 condiciones (lı́neas 2 y 5) y 3 retornos (lı́neas 3, 6 y 8). La ecuación a continuación, muestra el cálculo paso a paso de la complejidad ciclomática para el método doubleIsDifferent: CC = C + RoS =2+3 (3.9) =5 Por lo tanto la complejidad ciclomática para el método doubleIsDifferent, definido en el fragmento de código 3.12 es de 5, tal y como se puede observar en la ecuación 3.9. 3.2.2 COMPARACIÓN DE RESULTADOS Una vez definida la métrica sobre la cual se va a realizar la comparación de resultados, se procederá a calcular la complejidad ciclomática para los métodos de clase Assert refactorizados en la sección 3.1.2. 121 La tabla 3.3 a continuación muestra los resultados obtenidos antes y después de la refactorización: Método CC - Antes CC - Después fail 3 1 equalsRegardingNull 3 1 failsEquals 1 0 doubleIsDifferent 5 1 floatIsDifferent 5 1 failNotNull 1 0 failSame 1 0 failNotSame 1 0 format 4 1 formatClassAndValue 2 0 Tabla 3.3: Comparación de la Complejidad Ciclomática Es importante resaltar que en la tabla anterior, solo se encuentran los valores para los métodos de la clase Assert refactorizados, ya que la complejidad ciclomática para el resto de métodos no ha cambiado, debido a que dichos métodos no han sido modificados. A continuación, utilizando los valores previamente obtenidos, se procederá a calcular el valor de la complejidad ciclomática de la clase Assert, utilizando las siguientes ecuaciones 3.10 y 3.11: CC = n X CCn (3.10) CCn′ (3.11) i=1 ′ CC = n X i=1 En donde, CC es el valor de la complejidad ciclomática de la clase antes de la refactorización, CCn es el valor de la complejidad ciclomática de cada método antes 122 de la refactorización, CC ′ es el valor de la complejidad ciclomática de la clase después de la refactorización y CCn′ es el valor de la complejidad ciclomática de cada método después de la refactorización. P = 100 − (CC ′ ∗ 100) CC (3.12) La tabla 3.4 a continuación, muestra el porcentaje de mejora obtenido para cada una de las métricas definidas dentro de la presente sección, comparando los resultados antes y después de la refactorización: Métrica Antes Después Porcentaje de Reducción Número de Condiciones 26 12 54 % Número de Retornos 19 9 53 % Complejidad Ciclomática 45 21 53 % Tabla 3.4: Comparación de Resultados Como se puede observar en la tabla 3.4 anterior, se ha reducido la complejidad ciclomática de la clase Assert en un 53 %. Dicho valor demuestra que la refactorización realizada, empleado la librerı́a desarrollada, resulto ser efectiva, ya que como se puede observar en los valores de comparación de la tabla anterior, en todas las métricas se obtuvo un porcentaje de mejora mayor al 50 %. CAPÍTULO 4. CONCLUSIONES Y RECOMENDACIONES 4.1 CONCLUSIONES Durante la realización de la ingenierı́a inversa al módulo DATA de la librerı́a base de Haskell, se encontró que algunas de las funciones definidas en Haskell no pueden ser exportadas a Java debido a las limitaciones del lenguaje. Un claro ejemplo es la evaluación perezosa en Haskell la cual permite tomar elementos de una lista infinita, esto no es posible en Java debido a que la lista necesita ser evaluada completamente primero antes de poder acceder a sus elementos. El crear una convención de nombres entre Haskell y Java previa a la migración de módulos, ayudó a que sea fácil comprender la relación que existe entre las funciones escritas en Java y sus semejantes en Haskell. La documentación en Haskell de las funciones exportadas a Java fue fundamental durante el proceso de TDD, ya que se consiguió definir claramente el comportamiento de las funciones a ser importadas antes de ser implementadas en Java garantizado ası́ que el comportamiento sea el mismo en ambos lenguajes. Una vez elaborada la librerı́a funcional, se refactorizó la librerı́a JUnit demostrando mediante métricas las mejoras obtenidas en el código, como la disminución del número de bucles y condicionales en el código. Antes de proceder con la refactorización, es muy importante tomar en cuenta el tipo de licencia del proyecto, de no ser un proyecto de código abierto (OSS), se estarı́a violando la ley al modificar el código. Adicionalmente se debe tomar en cuenta que 123 124 la refactorización consiste en la mejora del diseño del código, sin la modificación del comportamiento previo, para lo cual es importante que exista pruebas de regresión, que nos ayuden a garantizar que después de la refactorización el comportamiento sea el mismo. Durante el desarrollo de la librerı́a funcional, se encontró varios proyectos similares para otros lenguajes orientados a objetos, adicionalmente hoy en dı́a muchos lenguajes - incluyendo java - han incluido caracterı́sticas funcionales como son el uso de lambdas y patrones de búsqueda, esto sin duda junto con la creciente cantidad de librerı́as funcionales, indican una tendencia dentro de la comunidad de desarrollo de moverse hacia una paradigma funcional. 4.2 RECOMENDACIONES La librerı́a funcional desarrollada en el presente proyecto, serı́a un aporte a la comunidad de software libre, cuando la Escuela Politécnica Nacional cree un repositorio de código público en donde se puedan publicar todos los trabajos de software elaborados, cumpliendo ası́ su rol de vinculación y responsabilidad social como universidad. Utilizar patrones de diseño funcionales, dentro de lenguajes orientados a objetos ayuda a disminuir la complejidad ciclomática del código, permitiendo ası́ facilitar su lectura y mantenimiento. Para la refactorización de un producto de software se debe tomar en cuenta dos criterios claves: el tipo de licencia del producto de software, y la existencia de pruebas. Dichos criterios son necesarios, debido a que el modificar software sin el previo consentimiento del autor puedo llegar a causar problemas legales. Con respecto a la existencia de pruebas, una vez refactorizado el código, las pruebas son las encargadas de verificar que el comportamiento del software, no se vea afectado debido a la refactorización. Antes de proceder a evaluar cualquier mejora de un producto de software, es nece- 125 sario definir un conjunto de métricas sobre las cuales se pueda realizar una comparación del código fuente original contra el código modificado. Dichas modificaciones deben ser realizadas sobre un sistema de control de cambios, para poder regresar a un punto seguro en caso de haber introducido algún error durante la modificación del código. Dentro del campo profesional, se debe considerar que el costo de inversión en desarrollar una aplicación funcional, puede ser mayor debido a que no existe un conjunto tan basto de librerı́as como el de lenguajes orientados a objetos. Sin embargo el costo de mantenimiento es menor debido a que el sistema de tipos y las semánticas de los lenguajes funcionales permiten escribir código menos propenso a errores. SIGLAS EJB Enterprise Java Beans. 13 FFI Foreign Function Interface. 32 FP Functional Programming. 15 HTML HyperText Markup Language. 74, 92, 101 IRC Internet Relay Chat. 41 ISTQB International Software Testing Qualifications Board. 85 JDK Java Development Kit. 55, 87 OOP Object Oriented Programming. 15 RMI Remote Method Invocation. 13 TDD Test-Driven Development. 76–79, 85, 87, 90 XML Extensible Markup Language. 91 126 GLOSARIO batch Consiste en la ejecucción de una serie de programas en una computadora sin la necesidad de intervención manual. 27 bindings Es la asociación existente entre objetos e implementaciones con nombres dentro de un lenguaje de programación. 32 Bitbucket Bitbucket es un servicio web utilizado para el almacenamiento de proyectos tanto en Git como en Mercurial. 80 cache Es un componente de software, utilizado para almacenar datos los cuales son solicitados con frecuencia. La segunda vez que los datos sean solicitados serán obtenidos más rapidamente debido a que se encontrarán previamente cargados en memoria. 27 callback Es utilizado en el desarrollo de software, para el manejo de llamadas ası́ncronas, es decir se ejecuta un proceso y la respuesta es manejada por un callback cuando este proceso términe. 47 Closures Es una técnica de programación utilizada para crear funciones asociadas a un contexto de ejecucción. 47 Currying Término matemático, introducido por Moses Schonfinkel y desarrollado por Haskell Curry. 38, 43 debugging Es el proceso de ubicación y correción de un error dentro de programa de computación. 36 127 128 delay Es una caracterı́stica muy importante a ser tomado en cuenta en diseño de sistemas distribuidos, se trata del retraso existente entre una solucitud y un respuesta. 46 Dijkstra Cientı́fico de la Computación y Matemático Holandés. Fı́sico teórico por entrenamiento, el trabajo como programador en el Centro Matemático de Amsterdam desde 1952 hasta 1962. Trabajo como profesor de matemáticas en la universidad de Eindhoven (1962-1984) y como investigador en la corporación de Burroughs (1973-1984). 94 Git Git es un proyecto de código abierto, utilizado para el control de versionamiento. 42, 80 GitHub GitHub es un servicio web utilizado por alrededor de 10 millones de personas, para el desarrollo de proyectos de software. 93, 104 JIRA Es una aplicación web, utilizada para el seguimiento de errores, incidentes y para la gestión de proyectos. 104 LISP Es el segundo lenguaje de programación más viejo, su principal caracterı́stica radica en la representación de la mayorı́a de los datos como listas, incluyendo su código. Esto facilita la creación de lenguajes de dominio especı́ficos, mediante el uso de su sistema de macros. 3 Martin Fowler Ingeniero de software británico, realiza conferencias públicas sobre desarrollo de software, programación orientada a objetos, análisis y diseño. 94 Maven Es una herramienta para la construcción automática de proyectos en Java. Incluye manejo de dependencias contrario a su predecesor Ant. 91 Mixin Es una clase la cual contiene métodos referenciados por otras clases, es una alternativa al uso de herencia para la reutilización de código. 38 Open-Source Es el nombre denominado a proyectos de software y hardware de libre distribución. 7 129 point-free Dentro de la programación funcional es la técnica utilizada para la composición de funciones sin la necesidad de mencionar los argumentos que serán aplicados. 43 Premio Turing Es un premio de las Ciencias de la Computación otorgado a quienes hayan contribuido de manera trascendental al campo de las ciencias de la computación. 3 protocolo Define un estándar para el intercambio de datos entre computadoras a través de la red. 46 proxy Es una clase la cual actual como una interfaz, sobre un objeto al cual delega la invocación de métodos. Un proxy posee la caracterı́stica de añadir funcionalidad extra alrededor de un método antes de que se invoque la llamada al objeto asociado. 34 shell Es una interfaz de usuario la cual permite acceso a los servicios del sistema operativo. 36 Shippable Shippable es un servicio web, utilizado para la integración continua de proyectos, al igual que el despliegue y pruebas de proyectos tanto en GitHub como en Bitbucket. 80 Trait Conjunto de métodos utilizados para extender la funcionalidad de una clase. 38 Travis Servicio de integración continua de código abierto, utilizado para la construcción y prueba de proyectos almacenados en GitHub. 93 BIBLIOGRAFÍA [1] Apache. Apache Commons. URL : https : / / commons . apache . org (visitado 01-08-2015). [2] Thomas J. McCabe Arthur H. Watson. NIST Special Publication 500-235. URL: http://www.mccabe.com/pdf/mccabe-nist235r.pdf (visitado 01-08-2015). [3] Jeremy Ashkenas. Functional Ruby. URL: https://github.com/jdantonio/functionalruby (visitado 01-07-2015). [4] Joshua Backfield. Becoming Functional: Steps for Transforming Into a Functional Programmer. First. O’Reilly Media, 2014. [5] John Backus. Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs. URL: https://www.cs.cmu.edu/ ∼crary/819-f09/Backus78.pdf [6] Bitbucket. URL : (visitado 01-07-2015). https://bitbucket.org (visitado 01-07-2015). [7] Don Stewart Bryan O’Sullivan John Goerzen. Real World: Haskell. First. O’Reilly Media, 2009. [8] ISTQB Exan Certification. What are Software Testing Levels? URL : http : / / istqbexamcertification.com/what-are-software-testing-levels (visitado 01-10-2015). [9] ISTQB Exan Certification. Why is Testing Necessary. URL: http://istqbexamcertification. com/why-is-testing-necessary (visitado 01-10-2015). [10] Clojure - home. [11] Elixir. URL: URL : http://clojure.org (visitado 01-07-2015). http://elixir-lang.org (visitado 01-07-2015). [12] Erlang Programming Language. URL: http://www.erlang.org (visitado 01-07-2015). 130 131 [13] Sebastián Estrella. JPrelude. URL : http://jprelude.bitbucket.org/docs (visitado 01-06-2015). [14] Neil Ford. Functional Thinking: Paradigm Over Syntax. First. O’Reilly Media, 2014. [15] Functional PHP. URL : https : / / github . com / lstrojny / functional - php (visitado 01-07-2015). [16] functional.js: The functional JavaScript library. URL: http : / / functionaljs . com (visitado 01-07-2015). [17] Guava. Guava. URL : https : / / code . google . com / p / guava - libraries (visitado 01-08-2015). [18] Haskell. Functional programming. URL : https : / / wiki . haskell . org / Functional programming (visitado 01-05-2015). [19] Haskell. Hackage. URL : https://hackage.haskell.org (visitado 01-05-2015). [20] Haskell. Referential transparency. URL: https://wiki.haskell.org/Referential transparency (visitado 01-05-2015). [21] Haskell Language. URL : https://www.haskell.org (visitado 01-07-2015). [22] Gastón C. Hillar. Learning Object-Oriented Programming. Packt, 2015. [23] ISTQB. Programa de Estudio de Nivel Básico. URL : http : / / www. istqb . org (visitado 01-06-2015). [24] Javaslang. Javaslang. URL : http://javaslang.com (visitado 01-07-2015). [25] JavaWorld. Java’s top 20: The most used Java libraries on GitHub. URL : http: //www.javaworld.com/article/2924315/open-source-tools/javas-top-20-themost-used-java-libraries-on-github.html (visitado 01-06-2015). [26] JUnit. JUnit. URL : [27] Korma. http://sqlkorma.com (visitado 01-07-2015). URL : http://junit.org (visitado 01-08-2015). [28] WordCamp Lima. TDD Circle of Life. URL : https://peru.wordcamp.org/2014/ files/2014/08/tdd-circle-of-life.png (visitado 01-05-2015). [29] LINQ. URL: https://msdn.microsoft.com/es-ec/library/bb397926.aspx (visitado 01-07-2015). 132 [30] Log4j. Log4j. URL : [31] OCaml – OCaml. http://logging.apache.org/log4j/2.x (visitado 01-08-2015). URL : https://ocaml.org (visitado 01-07-2015). [32] Oracle. ¿Qué es Java? URL: https://www.java.com/es/about/whatis java.jsp (visitado 01-06-2015). [33] Bryan O’Sullivan. Higher order functions. URL : http://learnyouahaskell.com/ higher-order-functions (visitado 01-05-2015). [34] M. Papalthomas. Concurrency Issues in Object-Oriented Programming Languages. URL: http://asg.unige.ch/site/papers/Papa89a.pdf (visitado 01-05-2015). [35] Shippable. URL : [36] SLF4J. SLF4J. https://app.shippable.com (visitado 01-07-2015). URL : http://www.slf4j.org (visitado 01-08-2015). [37] The Rust Programming Language. URL : https://www.rust- lang.org (visitado 01-07-2015). [38] The Scala Programming Language. URL : http://www.scala-lang.org (visitado 01-07-2015). [39] Underscore.js. URL: http://underscorejs.org (visitado 01-07-2015). ANEXOS .1 CAPTURAS DE PANTALLA DE GITHUB Figura 1: Repositorio de Google Guava Figura 2: Repositorio de JUnit Figura 3: Repositorio de Log4j 133 134 Figura 4: Repositorio de SLF4J .2 CAPTURAS DE PANTALLA DE JIRA Figura 5: Seguimiento de Incidentes de Commons BCEL Figura 6: Seguimiento de Incidentes de Commons BeanUtils 135 Figura 7: Seguimiento de Incidentes de Commons BSF Figura 8: Seguimiento de Incidentes de Commons Chain Figura 9: Seguimiento de Incidentes de Commons CLI 136 Figura 10: Seguimiento de Incidentes de Commons Codec Figura 11: Seguimiento de Incidentes de Commons Collections Figura 12: Seguimiento de Incidentes de Commons Compress 137 Figura 13: Seguimiento de Incidentes de Commons Configuration Figura 14: Seguimiento de Incidentes de Commons CSV Figura 15: Seguimiento de Incidentes de Commons Daemon 138 Figura 16: Seguimiento de Incidentes de Commons Dbcp Figura 17: Seguimiento de Incidentes de Commons Dbutils Figura 18: Seguimiento de Incidentes de Commons Digester 139 Figura 19: Seguimiento de Incidentes de Commons Discovery Figura 20: Seguimiento de Incidentes de Commons EL Figura 21: Seguimiento de Incidentes de Commons Email 140 Figura 22: Seguimiento de Incidentes de Commons Exec Figura 23: Seguimiento de Incidentes de Commons FileUpload Figura 24: Seguimiento de Incidentes de Commons Functor 141 Figura 25: Seguimiento de Incidentes de Commons Imaging Figura 26: Seguimiento de Incidentes de Commons IO Figura 27: Seguimiento de Incidentes de Commons JCI 142 Figura 28: Seguimiento de Incidentes de Commons JCS Figura 29: Seguimiento de Incidentes de Commons Jelly Figura 30: Seguimiento de Incidentes de Commons JEXL 143 Figura 31: Seguimiento de Incidentes de Commons JXPath Figura 32: Seguimiento de Incidentes de Commons JXPath Figura 33: Seguimiento de Incidentes de Commons Lang 144 Figura 34: Seguimiento de Incidentes de Commons Launcher Figura 35: Seguimiento de Incidentes de Commons Log4j Figura 36: Seguimiento de Incidentes de Commons Logging 145 Figura 37: Seguimiento de Incidentes de Commons Math Figura 38: Seguimiento de Incidentes de Commons Modeler Figura 39: Seguimiento de Incidentes de Commons Net 146 Figura 40: Seguimiento de Incidentes de Commons OGNL Figura 41: Seguimiento de Incidentes de Commons Pool Figura 42: Seguimiento de Incidentes de Commons Primitives 147 Figura 43: Seguimiento de Incidentes de Commons Proxy Figura 44: Seguimiento de Incidentes de Commons SCXML Figura 45: Seguimiento de Incidentes de Commons Validator 148 Figura 46: Seguimiento de Incidentes de Commons VFS Figura 47: Seguimiento de Incidentes de Commons VFS Figura 48: Seguimiento de Incidentes de Commons Weaver