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