Reinventar el diseño del sistema para Scala


35

Hace muchas, muchas lunas, hice mi maestría en Ingeniería de Software Orientada a Objetos. Cubrí todo: iniciación de proyectos, requisitos, análisis, diseño, arquitectura, desarrollo, etc. Mi libro favorito de TI de todos los tiempos fue Desarrollando software orientado a objetos, un enfoque basado en la experiencia (IBM-1996). Un libro creado por un grupo de verdaderos expertos de su tiempo. Describe un enfoque centrado en el producto del trabajo para los métodos de análisis, diseño y desarrollo orientados a objetos.

Diseñé y desarrollé y estaba feliz y en la cima de mi juego, pero comencé a sentirme un poco anticuado: los movimientos ágiles se convirtieron en la moda del día y cambiaron la marca de algunos enfoques iterativos e incrementales bien conocidos con nuevas palabras de moda. Desarrolladores repentinamente inexpertos comenzaron a fruncir el ceño cuando dije "requisitos" o "arquitectura" como si estas cosas fueran reemplazadas por la magia.

El diseño y el desarrollo perdieron su diversión y estaba a punto de dejar atrás a toda la industria de TI.

Entonces descubrí Scala. Oh, como lluvia suave en un camino polvoriento. Todo se volvió claro, el aire volvió a ser dulce y la poca luz de mi disco duro parpadeó profundamente en la noche, felizmente acompañándome en mi mundo de descubrimiento.

Me gusta el diseño del sistema. Soy un arquitecto de corazón, más que un programador. Me encanta analizar, diseñar, pensar, discutir, mejorar. Simplemente me encantan los diseños simples, limpios y nítidos.

¿Cómo diseñamos soluciones puras de Scala?

Seguramente, los diagramas de secuencia convencionales, los diagramas de interacción, los diagramas de objetos, etc., podrían ser reemplazados o mejorados por la bondad de combinar sistemas orientados a objetos imperativos y funcionales. ¡Seguramente hay una oportunidad para algo totalmente diferente!

No estoy buscando complejidad hinchada, estoy buscando simplicidad flexible. Al igual que Scala, en realidad es simple y fácil (aunque diferente), pero se puede extender para satisfacer sus necesidades como un par de pantalones de yoga. Debe haber un sistema de diseño para este lenguaje encantador que comenzará de manera simple y se puede extender para satisfacer sus requisitos y su dominio. ¡Seguramente UML no puede serlo!

Entonces, ¿cómo diseñamos sistemas Scala puros? Imagine por un momento que tiene el lujo de diseñar un sistema completo desde cero y sabe que solo usará Scala: ¿cómo se verán sus modelos? ¿Qué tipos de diagramas tendrías para describir la estructura y el comportamiento? ¿Cómo modelaría las Opciones, coincidencias, mixins, Objetos únicos, etc. sin verse atraído por la complejidad de extender las técnicas de modelado existentes en lugar de un conjunto de herramientas nuevo, ligero e innovador?

¿Existe tal diseño / proceso / solución , o es hora de inventar una?


+1 para "puede extenderse para ajustarse a sus necesidades como un par de pantalones de yoga".
Russell

FWIW, he visto una pregunta sobre UML y Scala, que podría ser de su interés. Además, si va al lado de la función, entonces vale la pena analizar las anotaciones de teoría de categorías. Gregory Meredith generalmente enlaza con algunas presentaciones interesantes al respecto, aunque no estoy seguro de lo que tiene en su blog; de todos modos, vale la pena echarle un vistazo.
Daniel C. Sobral

Vale mucho ;-) Me da instrucciones para cavar. Gracias Daniel
Jack

Vale la pena revisar, el UML "modificado" para Scala, github.com/mnn/Dia2Scala/blob/master/other/Notation.md
WeiChing Lin

Respuestas:


12

Realmente no puedo dar una respuesta a esta pregunta, pero permítanme ofrecer algunas ideas.

Soy estudiante de ingeniería informática en una universidad, y el semestre pasado, un grupo y yo hicimos un gran proyecto de software en el que nuestro idioma principal era el Scala. Hicimos nuestro diseño de la manera tradicional: casos de uso, un modelo de dominio, diagramas de secuencia, diagramas de clase UML; La carga habitual. Y, como ya sabe, encajan mal. Aquí hay algunas razones.

Primero, considere los diagramas de secuencia. La sensación de un diagrama de secuencia es de unos pocos actores estables, longevos y semiautónomos que hablan entre sí. En Scala, los objetos tienden a crearse por poco tiempo. Además, las clases de casos fomentan los objetos "tontos", con solo datos y sin código, por lo que el concepto de pasar mensajes está fuera de lugar. Pero el desafío más difícil para los diagramas de secuencia es que no tienen una buena manera de incorporar funciones de primera clase; en un diseño OO usando solo una devolución de llamada aquí o allá, puede hacerse funcionar, pero si ese es su principal medio de transferencia de control, encontrará que el diagrama de secuencia refleja poco de su código real.

Los diagramas de clase UML son más susceptibles a Scala; Sí, los mixins no funcionan bien, pero eso es algo que podría "modificarse" en lugar de revisarse. Pero creo que eso podría estar perdiendo el punto.

Considere nuevamente por qué Scala tiene "objetos tontos". Si escribe una clase de caso sin métodos:

case class NewInvite(user: User, league: League)

no necesita ningún método, porque los tipos de miembros prometen algo sobre lo que puede hacer con ellos. De hecho, prometen todo . Y, en cada ámbito dentro del programa, las variables dentro del ámbito y sus tipos prometen algo sobre dónde puede ir el código en ese punto.

Entonces, tal vez, si retrocedemos un poco y, en lugar de pensar minuciosamente en los tipos per se, diseñamos nuestro programa en términos de en qué contextos vivirá nuestro código y qué se le promete al programador (y, eventualmente, al usuario ) en cada uno de estos contextos, encontraremos una descripción más apropiada para Scala. Sin embargo, solo estoy adivinando aquí, y creo que tienes razón en que se necesita explorar mucho más en esta área.


"Lo que se le promete al programador" - eso suena encantador ;-)
Jack

12

UML surgió de las "Guerras de la burbuja" de finales de los 80 y principios de los 90. Siempre fue un compromiso para la notación , no un método de diseño. Muchas de las calumnias atribuidas a UML son secretamente el resultado de un método de diseño que dice "Paso 1: dibujar un modelo de clase".

Sugiero que no necesitamos nuevos métodos de diseño tanto como deberíamos revitalizar algunos antiguos. Comience definiendo y asignando responsabilidades, luego pase a comprender sus implementaciones y siga con buenas anotaciones y representaciones para comunicar esas ideas.

Responsabilidades

CRC funciona tan bien para Scala como para cualquier otro lenguaje OO, lo que significa que funciona muy bien. Modelar la asignación de responsabilidades antropomorfizando a los actores y entendiendo sus colaboraciones todavía funciona bien.

A gran escala, aún debe diseñar con objetos. Los límites de objeto son límites de encapsulación. Mantenga el estado mutable y los detalles de implementación dentro de esos límites de objeto. Las clases de casos son buenos objetos de interfaz, al igual que las colecciones de vainilla y las estructuras de datos.

Comprender las implementaciones

Pase estas estructuras de datos a través de los límites de los objetos en lugar de otros objetos, y encontrará que a) es más fácil mantener las entrañas de un objeto ocultas en su interior, yb) las implementaciones funcionales de los comportamientos de los objetos tienen mucho sentido.

No querrá tratar la estructura a gran escala de su programa como "un vasto conjunto de pequeñas funciones". En cambio, naturalmente gravitará hacia interfaces estrechas entre partes aisladas del sistema. De todos modos, se parecen mucho a los objetos, ¡así que adelante e impleméntelos como objetos!

Representación y notación

Entonces, dentro de esta estructura de diseño, aún necesita sacar los pensamientos de su cabeza y compartirlos con otra persona.

Sugiero que UML aún puede ser útil para describir la estructura a gran escala de su sistema. Sin embargo, no ayudará en los niveles más bajos de detalle.

A la perfección, prueba técnicas de programación alfabetizadas.

También me gusta usar minidocs. Un minidoc es un documento de una o dos páginas que describe una faceta específica del comportamiento del sistema. Debería estar cerca del código para que se actualice, y debería ser lo suficientemente corto como para permitir el aprendizaje justo a tiempo. Se necesita algo de práctica para alcanzar el nivel correcto de abstracción: debe ser lo suficientemente específico como para ser útil, pero no tan específico como para que el próximo cambio de código lo invalide.


9

¿Cómo diseñamos soluciones puras de Scala?

Creo que estás tomando el ángulo equivocado con esta pregunta. Su diseño general para las soluciones no debe estar vinculado a un idioma en particular; más bien, debería corresponder al problema en cuestión. Lo mejor de Scala es que es lo suficientemente flexible como para no interponerse en su camino a la hora de implementar su diseño. Java, por otro lado, impone algunas decisiones de diseño (principalmente, la mentalidad de todo es una clase) que lo harán sentir un poco torpe.

Al diseñar, intente modelar el estado explícitamente . Los datos inmutables y el estado explícito conducen a diseños que son mucho más fáciles de razonar. La mayoría de las veces, esto se traducirá agradablemente en una solución de programación funcional, aunque ocasionalmente puede encontrar que es más eficiente o más sencillo usar la mutación y un estilo imperativo; Scala acomoda a ambos bastante bien.

Otra razón por la que Scala es tan excelente para apartarse de su camino es su capacidad para crear DSL que se adapten a sus necesidades . Puede crear su propio lenguaje pequeño que resuelva un problema determinado de la manera "normal", y luego simplemente implementarlo en Scala.

En resumen, mi punto principal es que no debería intentar "diseñar soluciones Scala puras". Debe diseñar soluciones de la manera más natural y comprensible posible, y resulta que Scala es una herramienta increíblemente flexible que puede ayudarlo a implementar esas soluciones de varias maneras. Cuando todo lo que sabes es un martillo, cada problema comienza a parecerse a un clavo, así que asegúrate de explorar los diversos enfoques disponibles. No intente cerrar el proceso de diseño a los pasos X, Y y Z, para no perderse las posibilidades de la A a la W.


4

Tienes razón en que OOD es limitado y anticuado. Su principal ventaja es que está muy bien especificada y ritualizada, con nombres elegantes para cada patrón trivial y truco de diseño. No encontrará ningún sistema comparable para los enfoques más modernos y racionales del diseño de sistemas.

Solo puedo enumerar un par de cosas: un enfoque semántico para el diseño, un subconjunto del cual se llama Programación Orientada al Lenguaje (y es muy utilizada por la comunidad Scala) y Diseño Dirigido por Dominio. Esta última es una vista simplificada y simplificada en un enfoque de diseño semántico más genérico, solo podrá disfrutarla si le gusta OOD.

También recomendaría aprender un truco o dos de las otras comunidades de programación funcional, por ejemplo, Haskell y Scheme, no todos sus conocimientos de diseño ya se habían transferido a Scala.


3

La pregunta era: ¿Cómo diseñamos soluciones Scala puras?

Respuestas como,

  1. Usamos XYZ porque ...
  2. Usamos ABC pero así ...
  3. Comenzamos a usar XYZ, pero luego encontramos que ABC era mejor porque ...

habría indicado cierta madurez, o existencia generalmente aceptada, de tal conjunto de herramientas o solución.

Los comentarios sobre si es apropiado considerar un conjunto de herramientas de diseño específico de Scala es un asunto diferente. Mi opinión sobre esto es que Scala es revolucionaria en la forma en que combina la programación imperativa y funcional. Scala captura un aliento asombroso de prácticas de desarrollo, conceptos y principios, y así, al encontrar una solución que se ajuste perfectamente a Scala, probablemente descubriremos una solución para la cual un subconjunto también servirá muy bien en muchos otros idiomas.

¿Es seguro decir que sabemos la respuesta a la pregunta alternativa:

"... ¿Es hora de inventar uno?"

¿Podría ser si ? (Dónde ' ' no prescribe los medios para la solución. Podría lograrse extendiendo una solución existente o creando una desde cero. Sin embargo, admite que existen deficiencias significativas en las opciones de diseño existentes)

Puede rechazar mi respuesta si no está de acuerdo. Lo tomaré como un hombre.


¿Revolucionario? ¿Por qué? Lisp había sido una combinación perfecta de enfoques imperativos y funcionales durante décadas. Scala es interesante por su sistema de tipos.
SK-logic

3
Disculpas si 'revolucionario' es una palabra fuerte y fuerte. No estoy insinuando que Scala reinventó la rueda, pero seguro que volvió a moldear la goma. De hecho, Scala es una mezcla encantadora de todas las bondades de muchos lenguajes de programación. Estoy seguro de que también toma prestado mucho de Lisp. Para mí, y no estoy reclamando a otros, el lenguaje Scala se siente revolucionario porque estoy pensando en el código de una manera que es bastante natural. Un lenguaje de diseño / modelado que haga lo mismo seguramente sería complementario. Ese era mi único punto: no ofende a Lisp ni a todos los demás lenguajes geniales.
Jack

2

No diferenciaría entre idiomas, lo haría de la misma manera en Java. Sin embargo, soy de la escuela OO más que funcional (algebraico Haskell o el lisp). El diseño de la aplicación Haskell es realmente diferente de Scala o Clojure. Además, el diseño de una aplicación Scala.js es diferente debido al DOM con estado: es difícil luchar contra una parte con estado de su aplicación que es obligatoria. Con el tiempo me acostumbré a hacerlo de la manera OO, mientras trataba de eliminar el estado y la mutabilidad tanto como sea posible. Si fuera un fanático de typelevel o scalaz, mis aplicaciones probablemente se verían bastante diferentes.

  1. Decidir sobre la pila de tecnología y las principales decisiones de diseño como la responsabilidad del cliente / servidor, la naturaleza distribuida: ¿realmente necesitamos persistencia de datos? ¿Qué se adapta mejor a nuestra aplicación? (ciertamente no bibliotecas o marcos todavía)

  2. Comenzando con un solo App.scalaarchivo donde defino los tipos clave (rasgos y clases de casos) y roles: mucha contemplación y pensamiento mientras estoy AFK. Es muy fácil jugar con él mientras está en un archivo. Incluso el prototipo podría surgir si se trata de una aplicación bastante simple. Dedico mucho tiempo a esta fase porque nada es más importante que tener el prototipo más correcto, principalmente transparente y sensible posible. Todo ayuda a que nuestra contemplación sea más precisa y aumenta la probabilidad de éxito.

  3. Ahora, cuando hemos comprobado la corrección de nuestra solución, tenemos una visión clara y se revelan todos los problemas potenciales, es hora de pensar qué tipo de bibliotecas o metodologías de terceros incorporar. ¿Necesitamos jugar framework o solo algunas bibliotecas? ¿Vamos a hacer estos actores con estado, realmente necesitamos la resistencia de Akka, ..., o tal vez no? ¿Usamos Rx para estas transmisiones? ¿Qué utilizamos para la interfaz de usuario para que no nos volvamos locos después de que tenga 10 características interactivas?

  4. dividió el App.scalaarchivo en múltiples porque aparecieron nuevos rasgos y clases y se hicieron más grandes. Su interfaz debe informar sobre sus roles. Ahora también es hora de pensar en utilizar clases de tipo y polimorfismo ad-hoc cuando sea posible. Para que quien llame a su interfaz, debe ser flexible para ellos. El punto es implementar solo la funcionalidad general, dejando los detalles a la persona que llama. A veces, lo que está tratando de implementar puede ser similar a Turing, por lo que debe proporcionar una forma para que las personas que llaman implementen los detalles por sí mismos en lugar de acumular solicitudes de características en GitHub. Esto es difícil de agregar más tarde. Debe pensarse desde el principio. Además de diseñar un DSL.

  5. pensando en el diseño porque la aplicación crecerá y debe estar lista para soportar nuevas características, optimizaciones, persistencia, interfaz de usuario, comunicación remota, etc., cosas que aún no se implementaron o de alguna manera se burlaron o se abstrajeron. Recuerde que si bien está todo en un archivo, es bastante fácil cambiar las cosas. El cerebro humano comienza a perderlo después de que se extiende a más de 10 archivos. Y es como un tumor que crece, así es como comienza la sobreingeniería. Noté que mis aplicaciones terminan con algunas funciones parciales largas que forman una especie de columna vertebral. Me resulta fácil trabajar con él. Supongo que me infectaron los actores Akka.

  6. Ahora que existen las primeras características reales, no solo el funcionamiento central, es hora de comenzar a escribir pruebas. ¿Por qué tan tarde? Debido a que durante este tiempo probablemente haya realizado reescrituras importantes y habría perdido el tiempo manteniendo esas pruebas. No se deben escribir pruebas a menos que se esté realmente seguro de que no habrá rediseños importantes. Prefiero las pruebas de integración que resisten fácilmente la refactorización continua y no pasaré más tiempo probando que realmente desarrollando cosas. Me pasó varias veces que pasé demasiado tiempo manteniendo pruebas. Corregir errores potenciales sin duda pagaría más en lugar de malas pruebas. Las malas pruebas o las pruebas escritas desde el primer momento pueden causar mucho dolor. Tenga en cuenta que ciertamente no soy fanático de TDD, en mi humilde opinión, es una mierda.

  7. Refactorizando, haciendo y manteniendo el diseño de la aplicación legible, los roles de los componentes son claros. A medida que la aplicación crece, no hay forma de que permanezca así a menos que sea un programador genio y si lee esta respuesta probablemente no lo esté :-) Yo tampoco. Por lo general, existen algunas cosas con estado porque no sabía cómo evitar eso, que puede causar problemas, intenta eliminarlos. También asegúrese de eliminar los olores de código que le han molestado por algún tiempo. En este punto, su gerente de proyecto (si tiene uno) probablemente comienza a sacudir la cabeza. Dile que valdrá la pena.

  8. Hecho, ¿está bien diseñada la aplicación? Depende: ¿Tienen los componentes roles bien definidos que realmente tienen sentido incluso fuera de tu cabeza? ¿Podrías tomar algunos y abrirlos sin mucho esfuerzo? ¿Existe una clara separación de preocupación entre ellos? ¿Puede agregar una nueva función sin preocuparse de que se bloquee en otro lugar (señal de que sabe exactamente lo que está haciendo)? En términos generales, debería ser legible como un libro con soluciones pragmáticas directas a los problemas. Lo cual es, en mi humilde opinión, la principal señal de buen diseño.

Con todo, es solo un trabajo duro y un tiempo que probablemente no recibirás de tus gerentes. Todo lo que describí aquí requiere mucho tiempo, esfuerzo, café, té y especialmente pensar en AFK. Cuanto más le des, mejor será el diseño. No existe una receta general para el diseño de aplicaciones, sentido común, pragmatismo, KISS, trabajo duro, experiencia significa buen diseño.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.