Neuerfindung des Systemdesigns für Scala


35

Vor vielen, vielen Monden habe ich meinen Master in objektorientierter Softwareentwicklung gemacht. Ich habe alles behandelt: Projektinitiierung, Anforderungen, Analyse, Design, Architektur, Entwicklung usw. Mein Lieblings-IT-Buch aller Zeiten war die Entwicklung objektorientierter Software, ein erfahrungsbasierter Ansatz (IBM-1996). Ein Buch, das von einer Gruppe wahrer Experten ihrer Zeit geschrieben wurde. Es beschreibt einen arbeitsproduktzentrierten Ansatz für objektorientierte Analyse-, Design- und Entwicklungsmethoden.

Ich entwarf und entwickelte und war glücklich und ganz oben in meinem Spiel, aber ich fühlte mich ein wenig altmodisch: Agile Bewegungen wurden zur Mode des Tages und benannten einige bekannte iterative und inkrementelle Ansätze mit neuen, angesagten Worten um. Plötzlich runzelten unerfahrene Entwickler die Stirn, als ich "Anforderungen" oder "Architektur" sagte, als ob diese Dinge von Magie abgelöst würden.

Design und Entwicklung haben ihren Spaß verloren und ich wollte die gesamte IT-Branche hinter mir lassen.

Dann entdeckte ich Scala. Oh, wie sanfter Regen auf einer staubigen Straße. Alles wurde klar, die Luft wurde wieder süß und das kleine Licht meiner Festplatte flackerte tief in die Nacht und hielt mich fröhlich in meiner Entdeckungswelt.

Ich mag Systemdesign. Ich bin im Grunde ein Architekt, mehr als ein Programmierer. Ich liebe es zu analysieren, zu entwerfen, zu denken, zu streiten, mich zu verbessern - ich liebe einfache, saubere und klare Designs.

Wie gestalten wir reine Scala-Lösungen?

Sicherlich könnten konventionelle Sequenzdiagramme, Interaktionsdiagramme, Objektdiagramme usw. ersetzt oder verbessert werden, um imperative und funktionale objektorientierte Systeme miteinander zu verschmelzen. Sicher gibt es eine Öffnung für etwas ganz anderes!

Ich bin nicht auf der Suche nach aufgeblähter Komplexität, sondern nach flexibler Einfachheit. Ähnlich wie Scala ist Scala eigentlich einfach und leicht (wenn auch anders), kann aber wie eine Yogahose erweitert werden, um Ihren Anforderungen zu entsprechen. Es muss ein Design-System für diese schöne Sprache geben, das einfach zu starten ist und erweitert werden kann, um Ihren Anforderungen und Ihrer Domain zu entsprechen. UML kann es doch nicht sein!

Wie entwerfen wir reine Scala-Systeme? Stellen Sie sich für einen Moment vor, Sie haben den Luxus, ein komplettes System von Grund auf neu zu entwerfen, und wissen, dass Sie nur Scala verwenden werden - wie sehen Ihre Modelle aus? Welche Diagrammtypen müssten Sie in Bezug auf Struktur und Verhalten beschreiben? Wie würden Sie Optionen, Übereinstimmungen, Mixins, Singleton-Objekte usw. modellieren, ohne in die Komplexität der Erweiterung vorhandener Modellierungstechniken einzugreifen, anstatt ein neues, leichtes, innovatives Tool-Set.

Existiert ein solches Design / ein solches Verfahren / eine solche Lösung oder ist es an der Zeit, eine solche zu erfinden?


+1 für "Kann wie eine Yogahose nach Ihren Wünschen erweitert werden."
Russell

FWIW, ich habe eine Frage zu UML und Scala gesehen, die Sie interessieren könnte. Wenn Sie auf die Funktionsseite gehen, sind Notationen der Kategorietheorie möglicherweise einen Blick wert. Gregory Meredith verlinkt normalerweise auf einige interessante Präsentationen darüber, obwohl ich nicht sicher bin, was er in seinem Blog hat - es sollte sich trotzdem lohnen, es auszuprobieren.
Daniel C. Sobral

Es ist viel wert ;-) Es gibt mir die Richtung zu graben. Danke Daniel
Jack

Es lohnt sich zu überprüfen, die "modifizierte" UML für Scala, github.com/mnn/Dia2Scala/blob/master/other/Notation.md
WeiChing Lin

Antworten:


12

Ich kann diese Frage nicht wirklich beantworten, aber lassen Sie mich einige Gedanken machen.

Ich bin Informatikstudent an einer Universität und habe im letzten Semester mit einer Gruppe ein großes Softwareprojekt durchgeführt, in dem unsere Hauptsprache Scala war. Wir haben unser Design auf traditionelle Weise erstellt: Anwendungsfälle, ein Domänenmodell, Sequenzdiagramme, UML-Klassendiagramme; die übliche Belastung. Und, wie Sie bereits wissen, sind sie eine schlechte Passform. Hier sind einige Gründe.

Betrachten Sie zunächst Sequenzdiagramme. Das Gefühl eines Sequenzdiagramms besteht darin, dass einige wenige stabile, langlebige, halbautonome Akteure miteinander sprechen. In Scala werden Objekte in der Regel für kurze Zeit erstellt. Darüber hinaus ermutigen Case-Klassen "dumme" Objekte, die nur Daten und keinen Code enthalten, so dass das Konzept der Weitergabe von Nachrichten nicht korrekt ist. Die größte Herausforderung für Sequenzdiagramme ist jedoch, dass sie keine guten Möglichkeiten haben, erstklassige Funktionen zu integrieren. In einem OO-Entwurf, in dem nur ein Rückruf hier oder dort verwendet wird, kann er zum Funktionieren gebracht werden. Wenn dies jedoch Ihr primäres Mittel zum Übertragen der Steuerung ist, finden Sie, dass das Sequenzdiagramm nur wenig von Ihrem tatsächlichen Code widerspiegelt.

UML-Klassendiagramme sind für Scala besser zugänglich. Ja, Mixins kommen nicht gut durch, aber das ist etwas, das "optimiert" werden könnte, anstatt überarbeitet zu werden. Aber ich denke, das könnte den Punkt verfehlen.

Überlegen Sie noch einmal, warum Scala "dumme Gegenstände" hat. Wenn Sie eine Fallklasse ohne Methoden schreiben:

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

Sie brauchen keine Methoden, weil die Arten der Mitglieder etwas darüber versprechen, was Sie mit ihnen machen können. Tatsächlich versprechen sie alles . Und in jedem Bereich innerhalb des Programms versprechen die Variablen innerhalb des Bereichs und ihre Typen etwas darüber, wohin der Code an diesem Punkt gehen kann.

Wenn wir also einen Schritt zurücktreten und nicht nur über Typen per se nachdenken, sondern unser Programm in Bezug auf die Kontexte, in denen unser Code leben wird, und was dem Programmierer (und schließlich dem Benutzer) versprochen wird, entwerfen ) In jedem dieser Kontexte finden wir eine Beschreibung, die besser zu Scala passt. Ich rate jedoch nur, und ich denke, Sie haben Recht, dass in diesem Bereich noch viel mehr erforscht werden muss.


"Was dem Programmierer versprochen wird" - das hat einen schönen Klang ;-)
Jack

12

UML ist aus den "Bubble Wars" der späten 80er und frühen 90er Jahre hervorgegangen. Es war immer ein Kompromiss für die Notation , keine Entwurfsmethode. Viele der Verleumdungen, die UML zugeschrieben werden, sind das Ergebnis einer Entwurfsmethode mit der Aufschrift "Schritt 1: Zeichnen eines Klassenmodells".

Ich schlage vor, dass wir nicht so sehr neue Entwurfsmethoden benötigen, als dass wir einige alte neu beleben sollten. Beginnen Sie mit der Definition und Zuweisung von Verantwortlichkeiten, und versuchen Sie dann, deren Implementierung zu verstehen. Führen Sie anschließend gute Notationen und Darstellungen durch, um diese Ideen zu kommunizieren.

Verantwortlichkeiten

CRC funktioniert für Scala genauso gut wie für jede andere OO-Sprache, das heißt, es funktioniert sehr gut! Die Modellierung der Zuweisung von Verantwortlichkeiten durch die Anthropomorphisierung von Akteuren und das Verständnis ihrer Zusammenarbeit funktioniert immer noch gut.

Im großen Maßstab sollten Sie immer noch mit Objekten entwerfen. Objektgrenzen sind Kapselungsgrenzen. Behalten Sie Details zum veränderlichen Status und zur Implementierung innerhalb dieser Objektgrenzen bei. Fallklassen sind gute Schnittstellenobjekte, ebenso Vanille-Sammlungen und Datenstrukturen.

Implementierungen verstehen

Wenn Sie diese Datenstrukturen über Objektgrenzen hinweg weitergeben als über andere Objekte, werden Sie feststellen, dass a) die Eingeweide eines Objekts leichter verborgen bleiben und b) funktionale Implementierungen des Objektverhaltens sehr sinnvoll sind.

Sie möchten die große Struktur Ihres Programms nicht als "großen Pool kleiner Funktionen" behandeln. Stattdessen tendieren Sie natürlich zu engen Schnittstellen zwischen isolierten Teilen des Systems. Diese sehen und fühlen sich sowieso sehr ähnlich wie Objekte, also setzen Sie sie als Objekte um!

Darstellung und Notation

Innerhalb dieser Designstruktur müssen Sie also immer noch die Gedanken aus dem Kopf bekommen und sie mit jemand anderem teilen.

Ich schlage vor, dass UML immer noch nützlich sein kann, um die Struktur Ihres Systems im großen Maßstab zu beschreiben. Bei niedrigeren Detaillierungsgraden hilft dies jedoch nicht.

Versuchen Sie es mit detaillierten Programmiertechniken.

Ich benutze auch gerne Minidocs. Ein Minidoc ist ein ein- oder zweiseitiges Dokument, das bestimmte Aspekte des Systemverhaltens beschreibt. Es sollte sich in der Nähe des Codes befinden, damit er aktualisiert wird, und sollte kurz genug sein, um Just-in-Time-Lernen zu ermöglichen. Es bedarf einiger Übung, um die richtige Abstraktionsebene zu erreichen: Sie muss spezifisch genug sein, um nützlich zu sein, aber nicht so spezifisch, dass sie bei der nächsten Codeänderung ungültig wird.


9

Wie gestalten wir reine Scala-Lösungen?

Ich denke, dass Sie mit dieser Frage den falschen Blickwinkel einnehmen. Ihr Gesamtentwurf für Lösungen sollte nicht an eine bestimmte Sprache gebunden sein. Vielmehr sollte es dem vorliegenden Problem entsprechen. Das Tolle an Scala ist, dass es flexibel genug ist, um sich nicht in die Quere zu kommen, wenn es darum geht, Ihr Design umzusetzen. Java erzwingt andererseits einige Designentscheidungen (hauptsächlich die Alles-ist-eine-Klasse-Mentalität), die dazu führen, dass es sich ein bisschen klobig anfühlt.

Versuchen Sie beim Entwerfen, den Status explizit zu modellieren . Unveränderliche Daten und explizite Zustände führen zu Konstruktionen, über die man viel leichter nachdenken kann. In den meisten Fällen führt dies zu einer funktionalen Programmierlösung, obwohl Sie gelegentlich feststellen werden, dass es effizienter oder einfacher ist, Mutationen und einen imperativen Stil zu verwenden. Scala beherbergt beide recht gut.

Ein weiterer Grund, warum Scala Ihnen so gut aus dem Weg geht, ist die Möglichkeit , DSLs zu erstellen, die Ihren Anforderungen entsprechen . Sie können Ihre eigene kleine Sprache erstellen, die ein bestimmtes Problem auf "normale" Weise löst, und diese dann einfach in Scala implementieren.

Zusammenfassend möchte ich sagen, dass Sie nicht versuchen sollten, "reine Scala-Lösungen" zu entwerfen. Sie sollten die Lösungen auf möglichst natürliche und verständliche Weise entwerfen, und es kommt vor, dass Scala ein unglaublich flexibles Tool ist, mit dem Sie diese Lösungen auf verschiedene Arten implementieren können. Wenn Sie nur über einen Hammer Bescheid wissen, sieht jedes Problem wie ein Nagel aus. Machen Sie sich also mit den verschiedenen Ansätzen vertraut, die Ihnen zur Verfügung stehen. Versuchen Sie nicht, den Entwurfsprozess für die Schritte X, Y und Z zu beenden, damit Sie die Möglichkeiten A bis W nicht verpassen.


4

Sie haben Recht, dass OOD begrenzt und veraltet ist. Sein Hauptvorteil ist, dass es so gut spezifiziert und ritualisiert ist, mit ausgefallenen Namen für jedes Trivialmuster und jeden Designtrick. Sie werden kein vergleichbares System für die moderneren und rationaleren Ansätze des Systemdesigns finden.

Ich kann nur ein paar Dinge auflisten: eine semantische Herangehensweise an das Design, von der eine Teilmenge sprachorientierte Programmierung (die von der Scala-Community häufig verwendet wird) und domänenorientiertes Design genannt wird. Letzteres ist eine vereinfachte und verallgemeinerte Sicht auf einen allgemeineren semantischen Designansatz. Sie werden es nur genießen können, wenn Sie OOD mögen.

Ich würde auch empfehlen, ein oder zwei Tricks von den anderen Communitys für funktionale Programmierung zu lernen - z. B. Haskell und Scheme, deren Design-Know-how noch nicht vollständig auf Scala übertragen wurde.


3

Die Frage war: Wie entwerfen wir reine Scala-Lösungen?

Antworten wie

  1. Wir benutzen XYZ weil ...
  2. Wir benutzen ABC aber wie so ....
  3. Wir haben angefangen, XYZ zu verwenden, aber dann haben wir festgestellt, dass ABC besser ist, weil ...

hätte eine gewisse Reife oder allgemein akzeptierte Existenz eines solchen Werkzeugsatzes oder einer solchen Lösung angezeigt.

Anders verhält es sich mit der Frage, ob es angebracht ist, ein Scala-spezifisches Design-Tool-Set zu berücksichtigen . Meiner Meinung nach ist Scala revolutionär in der Art, wie es imperative und funktionale Programmierung kombiniert. Scala fängt einen erstaunlichen Atemzug von Entwicklungspraktiken, -konzepten und -prinzipien ein. Wenn wir also eine Lösung finden, die perfekt zu Scala passt, werden wir wahrscheinlich eine Lösung finden, für die eine Untergruppe auch viele andere Sprachen sehr gut unterstützt.

Ist es dann sicher zu sagen, dass wir die Antwort auf die Fallback-Frage kennen:

"... ist es Zeit, einen zu erfinden?"

Könnte es ja sein ? (Wenn " Ja " die Mittel für die Lösung nicht vorschreibt. Dies könnte entweder durch Erweiterung einer vorhandenen Lösung oder durch Schaffung einer von Grund auf neuen Lösung erreicht werden. Es wird jedoch eingeräumt, dass die vorhandenen Entwurfsoptionen erhebliche Mängel aufweisen.)

Sie können gerne meine Antwort ablehnen, wenn Sie anderer Meinung sind. Ich werde es wie ein Mann nehmen.


Revolutionär? Warum? Lisp war seit Jahrzehnten eine perfekte Kombination aus imperativen und funktionalen Ansätzen. Scala ist wegen seines Typensystems interessant.
SK-logic

3
Entschuldigung, wenn "revolutionär" ein starkes starkes Wort ist. Ich gehe nicht davon aus, dass Scala das Rad neu erfunden hat - aber es hat das Gummi sicher umgeformt. In der Tat ist Scala eine schöne Mischung aus all dem Guten vieler Programmiersprachen. Ich bin sicher, es leiht sich auch viel von Lisp. Für mich, und ich mache keinen Anspruch auf andere, fühlt sich die Scala-Sprache revolutionär an, da ich über den Code auf eine Weise nachdenke, die ganz natürlich ist. Eine Design- / Modellierungssprache, die dasselbe tut, wäre sicherlich kostenlos. Das war mein einziger Punkt - keine Beleidigung für Lisp und all die anderen großartigen Sprachen.
Jack

2

Ich würde nicht zwischen Sprachen unterscheiden, ich würde es genauso in Java machen. Ich komme jedoch eher aus der OO-Schule als aus der funktionalen (algebraische Haskell-Schule oder der Lispel-Schule). Das Design der Haskell-App unterscheidet sich grundlegend von Scala oder Clojure. Auch das Design einer Scala.js-App unterscheidet sich aufgrund des statusbehafteten DOM - es ist schwierig, einen obligatorischen statusbehafteten Teil Ihrer App zu bekämpfen. Im Laufe der Zeit habe ich mich daran gewöhnt, den Zustand und die Veränderlichkeit so weit wie möglich zu beseitigen. Wenn ich ein Typelevel- oder Scalaz-Fan wäre, würden meine Apps wahrscheinlich ganz anders aussehen.

  1. Entscheiden Sie sich für den Technologie-Stack und die wichtigsten Entwurfsentscheidungen wie Client / Server-Verantwortung, verteilte Natur. Brauchen wir wirklich Datenpersistenz? Was passt am besten zu unserer Anwendung? (sicherlich noch keine Bibliotheken oder Frameworks)

  2. Beginnend mit einer einzigen App.scalaDatei, in der ich die Schlüsseltypen (Merkmale und Fallklassen) und Rollen definiere - viel Nachdenken und Nachdenken, während ich AFK bin. Es ist so einfach, damit zu spielen, während es in einer Datei ist. Sogar der Prototyp könnte entstehen, wenn es sich eher um eine einfache Anwendung handelt. Ich widme dieser Phase viel Zeit, denn nichts ist wichtiger als ein möglichst korrekter und überwiegend transparenter und vernünftiger Prototyp. All dies trägt dazu bei, unser weiteres Nachdenken genauer zu gestalten und die Erfolgswahrscheinlichkeit zu erhöhen.

  3. Nachdem wir die Richtigkeit unserer Lösung bewiesen haben, eine klare Vision haben und alle potenziellen Probleme aufgedeckt sind, ist es an der Zeit, darüber nachzudenken, welche Bibliotheken oder Methoden von Drittanbietern einzubringen sind. Benötigen wir ein Spiel-Framework oder nur ein paar Bibliotheken? Werden wir diese Stateful Actors machen, brauchen wir wirklich Akkas Resilienz, ..., ... oder vielleicht auch nicht? Verwenden wir Rx für diese Streams? Was verwenden wir für die Benutzeroberfläche, damit wir nach 10 interaktiven Funktionen nicht verrückt werden?

  4. Teilen Sie die App.scalaDatei in mehrere auf, da neue Eigenschaften und Klassen erschienen und größer wurden. Ihre Schnittstelle sollte über ihre Rollen erzählen. Jetzt ist es auch an der Zeit, über die Verwendung von Typklassen und Ad-hoc-Polymorphismen nachzudenken, wo dies möglich ist. Damit jeder, der Ihre Schnittstelle aufruft, für sie flexibel sein muss. Es geht darum, nur allgemeine Funktionen zu implementieren und die Details dem Anrufer zu überlassen. Manchmal ist das, was Sie zu implementieren versuchen, Turing-vollständig, sodass Sie Anrufern die Möglichkeit bieten sollten, bestimmte Funktionen selbst zu implementieren, anstatt Feature-Anforderungen auf GitHub zu häufen. Dieses Zeug ist später schwer hinzuzufügen. Es muss von Anfang an durchdacht werden. Sowie das Entwerfen einer DSL.

  5. Denken Sie über das Design nach, weil die Anwendung wachsen wird und bereit sein muss, neuen Funktionen, Optimierungen, Persistenz, Benutzeroberfläche, Remoting usw. standzuhalten. Denken Sie daran, dass es recht einfach ist, Dinge zu ändern, obwohl alles in einer Datei enthalten ist. Das menschliche Gehirn beginnt, es zu verlieren, nachdem es über mehr als 10 Dateien verteilt wurde. Und es ist wie ein Tumor, der wächst. So beginnt das Über-Engineering. Mir ist aufgefallen, dass meine Anwendungen einige lange Teilfunktionen haben, die eine Art Rückgrat bilden. Ich finde es einfach, damit zu arbeiten. Ich glaube, ich wurde von Akka-Schauspielern infiziert.

  6. Nun, da die ersten tatsächlichen Funktionen vorhanden sind, nicht nur die Kernfunktionen, ist es an der Zeit, Tests zu schreiben. Warum so spät? Weil Sie in dieser Zeit wahrscheinlich größere Umschreibungen durchlaufen haben und viel Zeit damit verschwendet hätten, diese Tests zu pflegen. Man sollte keine Tests schreiben, es sei denn, man ist sich wirklich sicher, dass es keine größeren Redesigns geben wird. Ich bevorzuge Integrationstests, die ein kontinuierliches Refactoring problemlos überstehen, und ich werde nicht mehr Zeit damit verbringen, Tests durchzuführen, die tatsächlich entwickelt werden. Es ist mir ein paar Mal passiert, dass ich viel zu viel Zeit mit der Wartung von Tests verbracht habe. Das Beheben potenzieller Fehler würde sich eher auszahlen als schlechte Tests. Schlechte Tests oder Tests, die vom ersten Moment an geschrieben wurden, können starke Schmerzen verursachen. Beachten Sie, dass ich definitiv kein Fan von TDD bin - IMHO, das ist Schwachsinn.

  7. Refactoring, Lesbarkeit des Anwendungsdesigns und Übersichtlichkeit der Komponentenrollen. Da die Anwendung wächst, kann es nicht anders sein, es sei denn, Sie sind ein genialer Programmierer, und wenn Sie diese Antwort lesen, sind Sie wahrscheinlich nicht :-) Ich auch nicht. In der Regel existieren einige zustandsbehaftete Dinge, weil Sie nicht wissen, wie Sie sie vermeiden sollen dass - das könnte Probleme verursachen, versuchen Sie, sie zu beseitigen. Vermeiden Sie auch Code-Gerüche, die Sie seit einiger Zeit gestört haben. Zu diesem Zeitpunkt fängt Ihr Projektmanager (falls Sie einen haben) wahrscheinlich an, den Kopf zu schütteln. Sag ihm, es wird sich auszahlen.

  8. Fertig, ist die App gut gestaltet? Es kommt darauf an: Haben die Komponenten genau definierte Rollen, die auch außerhalb Ihres Kopfes Sinn machen? Könntest du einige von ihnen nehmen und sie ohne großen Aufwand öffnen? Gibt es eine klare Trennung zwischen ihnen? Können Sie eine neue Funktion hinzufügen, ohne zu befürchten, dass sie an anderer Stelle abstürzt (ein Zeichen, dass Sie genau wissen, was Sie tun)? Im Allgemeinen sollte es als Buch mit einfachen pragmatischen Problemlösungen lesbar sein. Welches ist meiner Meinung nach das wichtigste Zeichen für gutes Design.

Alles in allem ist es nur eine harte Arbeit und Zeit, die Sie wahrscheinlich nicht von Ihren Managern bekommen werden. Alles, was ich hier beschrieben habe, kostet viel Zeit, Mühe, Kaffee, Tee und vor allem AFK. Je mehr Sie geben, desto besser das Design. Es gibt kein allgemeines Rezept für die Gestaltung von Anwendungen, gesunden Menschenverstand, Pragmatismus, KISS, harte Arbeit, Erfahrung bedeutet gutes Design.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.