La ingeniería de software, tal como se enseña hoy, se centra por completo en la programación orientada a objetos y la visión 'natural' orientada a objetos del mundo. Existe una metodología detallada que describe cómo transformar un modelo de dominio en un modelo de clase con varios pasos y muchos artefactos (UML) como diagramas de casos de uso o diagramas de clases. Muchos programadores han internalizado este enfoque y tienen una buena idea sobre cómo diseñar una aplicación orientada a objetos desde cero.
El nuevo bombo es la programación funcional, que se enseña en muchos libros y tutoriales. Pero, ¿qué pasa con la ingeniería de software funcional? Mientras leía sobre Lisp y Clojure, descubrí dos declaraciones interesantes:
Los programas funcionales a menudo se desarrollan de abajo hacia arriba en lugar de de arriba hacia abajo ('On Lisp', Paul Graham)
Los programadores funcionales usan mapas donde los programadores OO usan objetos / clases ('Clojure for Java Programmers', charla de Rich Hickley).
Entonces, ¿cuál es la metodología para un diseño sistemático (basado en el modelo) de una aplicación funcional, es decir, en Lisp o Clojure? ¿Cuáles son los pasos comunes, qué artefactos utilizo, cómo los mapeo desde el espacio del problema al espacio de la solución?
Respuestas:
Gracias a Dios que la gente de ingeniería de software aún no ha descubierto la programación funcional. Aquí hay algunos paralelos:
Muchos "patrones de diseño" OO se capturan como funciones de orden superior. Por ejemplo, el patrón Visitor se conoce en el mundo funcional como un "pliegue" (o si eres un teórico puntiagudo, un "catamorfismo"). En lenguajes funcionales, los tipos de datos son principalmente árboles o tuplas, y cada tipo de árbol tiene un catamorfismo natural asociado.
Estas funciones de orden superior a menudo vienen con ciertas leyes de programación, también conocidas como "teoremas libres".
Los programadores funcionales usan diagramas mucho menos que los programadores OO. Gran parte de lo que se expresa en los diagramas OO se expresa en cambio en tipos , o en "firmas", que debe considerar como "tipos de módulos". Haskell también tiene "clases de tipo", que es un poco como un tipo de interfaz.
Esos programadores funcionales que usan tipos generalmente piensan que "una vez que obtienes los tipos correctos, el código prácticamente se escribe solo".
No todos los lenguajes funcionales usan tipos explícitos, pero el libro Cómo diseñar programas , un excelente libro para aprender Scheme / Lisp / Clojure, depende en gran medida de "descripciones de datos", que están estrechamente relacionadas con los tipos.
Cualquier método de diseño basado en la abstracción de datos funciona bien. Creo que esto es más fácil cuando el lenguaje tiene tipos explícitos, pero funciona incluso sin él. Un buen libro sobre métodos de diseño para tipos de datos abstractos, que se adapta fácilmente a la programación funcional, es Abstracción y especificación en el desarrollo de programas de Barbara Liskov y John Guttag, la primera edición. Liskov ganó el premio Turing en parte por ese trabajo.
Otra metodología de diseño que es única para Lisp es decidir qué extensiones de idioma serían útiles en el dominio del problema en el que está trabajando, y luego usar macros higiénicas para agregar estas construcciones a su idioma. Un buen lugar para leer sobre este tipo de diseño es el artículo de Matthew Flatt Creando idiomas en la raqueta . El artículo puede estar detrás de un muro de pago. También puede encontrar material más general sobre este tipo de diseño buscando el término "lenguaje incrustado específico del dominio"; para obtener consejos y ejemplos particulares más allá de lo que cubre Matthew Flatt, probablemente comenzaría con Graham's On Lisp o quizás ANSI Common Lisp .
Pasos comunes:
Identifique los datos en su programa y las operaciones en él, y defina un tipo de datos abstracto que represente estos datos.
Identifique acciones o patrones comunes de cálculo y expréselos como funciones de orden superior o macros. Espere dar este paso como parte de la refactorización.
Si está utilizando un lenguaje funcional escrito, use el verificador de tipos temprano y con frecuencia. Si está utilizando Lisp o Clojure, la mejor práctica es escribir primero los contratos de funciones, incluidas las pruebas unitarias, es el desarrollo basado en pruebas al máximo. Y querrá usar cualquier versión de QuickCheck que haya sido portada a su plataforma, que en su caso parece que se llama ClojureCheck . Es una biblioteca extremadamente poderosa para construir pruebas aleatorias de código que utiliza funciones de orden superior.
fuente
Para Clojure, recomiendo volver a los viejos modelos relacionales. Out of the Tarpit es una lectura inspiradora.
fuente
Personalmente, creo que todas las buenas prácticas habituales del desarrollo OO se aplican también a la programación funcional, solo con algunos pequeños ajustes para tener en cuenta la visión funcional del mundo. Desde una perspectiva metodológica, realmente no necesitas hacer nada fundamentalmente diferente.
Mi experiencia proviene de haberme mudado de Java a Clojure en los últimos años.
Algunos ejemplos:
Comprenda su dominio comercial / modelo de datos , igualmente importante si va a diseñar un modelo de objetos o crear una estructura de datos funcional con mapas anidados. De alguna manera, FP puede ser más fácil porque lo alienta a pensar sobre el modelo de datos por separado de las funciones / procesos, pero aún tiene que hacer ambas cosas.
Orientación del servicio en el diseño : en realidad funciona muy bien desde una perspectiva de FP, ya que un servicio típico es realmente solo una función con algunos efectos secundarios. Creo que la visión "de abajo hacia arriba" del desarrollo de software, a veces adoptada en el mundo de Lisp, en realidad es solo un buen principio de diseño de API orientado al servicio en otro aspecto.
Desarrollo guiado por pruebas : funciona bien en lenguajes FP, de hecho a veces incluso mejor porque las funciones puras se prestan extremadamente bien para escribir pruebas claras y repetibles sin necesidad de configurar un entorno con estado. También es posible que desee crear pruebas separadas para verificar la integridad de los datos (p. Ej., Este mapa tiene todas las claves que espero, para equilibrar el hecho de que en un lenguaje OO la definición de clase lo aplicaría en el momento de la compilación).
Creación de prototipos / iteración : funciona igual de bien con FP. Incluso podría crear prototipos en vivo con los usuarios si se vuelve extremadamente bueno construyendo herramientas / DSL y usándolas en REPL.
fuente
La programación OO combina estrechamente los datos con el comportamiento. La programación funcional separa a los dos. Por lo tanto, no tiene diagramas de clases, pero sí tiene estructuras de datos y, en particular, tiene tipos de datos algebraicos. Esos tipos se pueden escribir para que coincidan muy estrechamente con su dominio, incluida la eliminación de valores imposibles por construcción.
Entonces, no hay libros y libros sobre él, pero hay un enfoque bien establecido para, como dice el dicho, hacer que los valores imposibles sean irrepresentables.
Al hacerlo, puede hacer una variedad de elecciones sobre la representación de ciertos tipos de datos como funciones y, a la inversa, representar ciertas funciones como una unión de tipos de datos para que pueda obtener, por ejemplo, serialización, especificaciones más estrictas, optimización, etc. .
Luego, dado que, escribe funciones sobre sus anuncios de modo que establezca algún tipo de álgebra , es decir, hay leyes fijas que se cumplen para estas funciones. Algunos son quizás idempotentes, lo mismo después de múltiples aplicaciones. Algunos son asociativos. Algunos son transitivos, etc.
Ahora tiene un dominio sobre el que tiene funciones que se componen de acuerdo con las leyes de buen comportamiento. ¡Un simple DSL incrustado!
Ah, y dadas las propiedades, por supuesto, puede escribir pruebas aleatorias automatizadas de ellos (ala QuickCheck) ... y eso es solo el comienzo.
fuente
El diseño orientado a objetos no es lo mismo que la ingeniería de software. La ingeniería de software tiene que ver con todo el proceso de cómo pasamos de los requisitos a un sistema de trabajo, a tiempo y con una baja tasa de defectos. La programación funcional puede ser diferente de la OO, pero no elimina los requisitos, los diseños detallados y de alto nivel, la verificación y las pruebas, las métricas de software, la estimación y todas esas otras "cosas de ingeniería de software".
Además, los programas funcionales exhiben modularidad y otra estructura. Sus diseños detallados deben expresarse en términos de los conceptos de esa estructura.
fuente
Un enfoque es crear un DSL interno dentro del lenguaje de programación funcional de elección. El "modelo" es un conjunto de reglas comerciales expresadas en el DSL.
fuente
Vea mi respuesta a otra publicación:
¿Cómo aborda Clojure la separación de las preocupaciones?
Estoy de acuerdo en que se necesita escribir más sobre el tema sobre cómo estructurar aplicaciones grandes que usan un enfoque de FP (Además, se necesita hacer más para documentar las UI impulsadas por FP)
fuente
Si bien esto podría considerarse ingenuo y simplista, creo que las "recetas de diseño" (un enfoque sistemático para la resolución de problemas aplicado a la programación como lo recomiendan Felleisen et al. En su libro HtDP ) estaría cerca de lo que parece estar buscando.
Aquí, algunos enlaces:
http://www.northeastern.edu/magazine/0301/programming.html
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.86.8371
fuente
Recientemente encontré este libro: Modelado de dominio funcional y reactivo
Creo que está perfectamente en línea con tu pregunta.
De la descripción del libro:
fuente
Existe el estilo de "cálculo de programa" / "diseño por cálculo" asociado con el Prof. Richard Bird y el grupo de Álgebra de Programación de la Universidad de Oxford (Reino Unido), no creo que sea demasiado descabellado considerar esta metodología.
Personalmente, aunque me gusta el trabajo producido por el grupo AoP, no tengo la disciplina para practicar el diseño de esta manera. Sin embargo, esa es mi deficiencia, y no una de cálculo de programa.
fuente
He descubierto que Behavior Driven Development es una opción natural para desarrollar código rápidamente tanto en Clojure como en SBCL. La verdadera ventaja de aprovechar BDD con un lenguaje funcional es que tiendo a escribir pruebas unitarias de grano mucho más finas de lo que suelo hacer cuando uso lenguajes de procedimiento porque hago un trabajo mucho mejor descomponiendo el problema en trozos más pequeños de funcionalidad.
fuente
Honestamente, si desea recetas de diseño para programas funcionales, eche un vistazo a las bibliotecas de funciones estándar, como el Preludio de Haskell. En FP, los patrones generalmente son capturados por procedimientos de orden superior (funciones que operan en funciones) ellos mismos. Entonces, si se ve un patrón, a menudo se crea simplemente una función de orden superior para capturar ese patrón.
Un buen ejemplo es fmap. Esta función toma una función como argumento y la aplica a todos los "elementos" del segundo argumento. Como es parte de la clase de tipo Functor, cualquier instancia de un Functor (como una lista, gráfico, etc.) puede pasarse como un segundo argumento para esta función. Captura el comportamiento general de aplicar una función a cada elemento de su segundo argumento.
fuente
Bien,
En general, muchos lenguajes de programación funcional se utilizan en las universidades durante mucho tiempo para "pequeños problemas de juguetes".
Se están volviendo más populares ahora ya que OOP tiene dificultades con la "programación paralela" debido al "estado". Y a veces el estilo funcional es mejor para problemas como Google MapReduce.
Estoy seguro de que, cuando los muchachos funcionales golpeen la pared [intente implementar sistemas de más de 1,000,000 de líneas de código], algunos de ellos vendrán con nuevas metodologías de ingeniería de software con palabras de moda :-). Deberían responder a la vieja pregunta: ¿Cómo dividir el sistema en piezas para que podamos "morder" cada pieza una a la vez? [trabajo iterativo, inceremental en forma evolutiva] usando el estilo funcional.
Pero, ¿se utilizarán programas funcionales para sistemas tan grandes? ¿Se convertirán en una corriente principal? Esa es la pregunta .
Y nadie puede venir con una metodología realista sin implementar un sistema tan grande, ensuciando sus manos. Primero debes ensuciarte las manos y luego sugerir una solución. Soluciones-Sugerencias sin "dolores y suciedad reales" serán "fantasía".
fuente