Software Engineering, wie es heute gelehrt wird, konzentriert sich ausschließlich auf objektorientierte Programmierung und die "natürliche" objektorientierte Sicht auf die Welt. Es gibt eine detaillierte Methodik, die beschreibt, wie ein Domänenmodell mit mehreren Schritten und vielen (UML-) Artefakten wie Anwendungsfalldiagrammen oder Klassendiagrammen in ein Klassenmodell umgewandelt wird. Viele Programmierer haben diesen Ansatz verinnerlicht und haben eine gute Vorstellung davon, wie eine objektorientierte Anwendung von Grund auf neu entworfen werden kann.
Der neue Hype ist die funktionale Programmierung, die in vielen Büchern und Tutorials gelehrt wird. Aber was ist mit funktionaler Softwareentwicklung? Beim Lesen über Lisp und Clojure kam ich zu zwei interessanten Aussagen:
Funktionsprogramme werden oft von unten nach oben statt von oben nach unten entwickelt ('On Lisp', Paul Graham)
Funktionale Programmierer verwenden Maps, in denen OO-Programmierer Objekte / Klassen verwenden ('Clojure for Java Programmers', Vortrag von Rich Hickley).
Wie lautet also die Methodik für ein systematisches (modellbasiertes?) Design einer funktionalen Anwendung, dh in Lisp oder Clojure? Was sind die gängigen Schritte, welche Artefakte verwende ich, wie ordne ich sie vom Problemraum dem Lösungsraum zu?
Antworten:
Gott sei Dank, dass die Softwareentwickler die funktionale Programmierung noch nicht entdeckt haben. Hier sind einige Parallelen:
Viele OO- "Entwurfsmuster" werden als Funktionen höherer Ordnung erfasst. Zum Beispiel ist das Besuchermuster in der Funktionswelt als "Falte" bekannt (oder, wenn Sie ein spitzer Theoretiker sind, als "Katamorphismus"). In funktionalen Sprachen sind Datentypen meistens Bäume oder Tupel, und mit jedem Baumtyp ist ein natürlicher Katamorphismus verbunden.
Diese Funktionen höherer Ordnung enthalten häufig bestimmte Programmiergesetze, auch "freie Theoreme" genannt.
Funktionale Programmierer verwenden Diagramme viel weniger häufig als OO-Programmierer. Vieles, was in OO-Diagrammen ausgedrückt wird , wird stattdessen in Typen oder in "Signaturen" ausgedrückt , die Sie als "Modultypen" betrachten sollten. Haskell hat auch "Typklassen", die ein bisschen wie ein Schnittstellentyp sind.
Diejenigen funktionalen Programmierer, die Typen verwenden, denken im Allgemeinen: "Sobald Sie die Typen richtig eingestellt haben, schreibt sich der Code praktisch von selbst."
Nicht alle funktionalen Sprachen verwenden explizite Typen, aber das Buch How To Design Programs , ein ausgezeichnetes Buch zum Lernen von Scheme / Lisp / Clojure, stützt sich stark auf "Datenbeschreibungen", die eng mit Typen verwandt sind.
Jede auf Datenabstraktion basierende Entwurfsmethode funktioniert gut. Ich denke, dass dies einfacher ist, wenn die Sprache explizite Typen hat, aber es funktioniert auch ohne. Ein gutes Buch über Entwurfsmethoden für abstrakte Datentypen, das sich leicht an die funktionale Programmierung anpassen lässt, ist Abstraction and Specification in Program Development von Barbara Liskov und John Guttag, der ersten Ausgabe. Liskov gewann den Turing-Preis teilweise für diese Arbeit.
Eine andere für Lisp einzigartige Entwurfsmethode besteht darin, zu entscheiden, welche Spracherweiterungen in dem Problembereich, in dem Sie arbeiten, nützlich sind, und diese Konstrukte dann mithilfe hygienischer Makros zu Ihrer Sprache hinzuzufügen. Ein guter Ort, um über diese Art von Design zu lesen, ist Matthew Flatts Artikel Erstellen von Sprachen in Schlägern . Der Artikel befindet sich möglicherweise hinter einer Paywall. Sie können auch allgemeineres Material zu dieser Art von Design finden, indem Sie nach dem Begriff "domänenspezifische eingebettete Sprache" suchen. Für besondere Ratschläge und Beispiele, die über das hinausgehen, was Matthew Flatt behandelt, würde ich wahrscheinlich mit Graham's On Lisp oder vielleicht ANSI Common Lisp beginnen .
Allgemeine Schritte:
Identifizieren Sie die Daten in Ihrem Programm und die Operationen darauf und definieren Sie einen abstrakten Datentyp, der diese Daten darstellt.
Identifizieren Sie allgemeine Aktionen oder Berechnungsmuster und drücken Sie sie als Funktionen oder Makros höherer Ordnung aus. Erwarten Sie diesen Schritt im Rahmen des Refactorings.
Wenn Sie eine typisierte Funktionssprache verwenden, verwenden Sie die Typprüfung früh und häufig. Wenn Sie Lisp oder Clojure verwenden, empfiehlt es sich, zuerst Funktionsverträge einschließlich Komponententests zu schreiben - dies ist eine testgetriebene Entwicklung bis zum Maximum. Und Sie sollten jede Version von QuickCheck verwenden, die auf Ihre Plattform portiert wurde. In Ihrem Fall sieht es so aus, als würde es ClojureCheck heißen . Es ist eine äußerst leistungsfähige Bibliothek zum Erstellen von zufälligen Codetests, die Funktionen höherer Ordnung verwenden.
quelle
Für Clojure empfehle ich, zu einer guten alten relationalen Modellierung zurückzukehren. Out of the Tarpit ist eine inspirierende Lektüre.
quelle
Persönlich finde ich, dass alle üblichen bewährten Methoden aus der OO-Entwicklung auch für die funktionale Programmierung gelten - nur mit ein paar kleinen Änderungen, um die funktionale Weltanschauung zu berücksichtigen. Aus methodischer Sicht müssen Sie nichts grundlegend anderes tun.
Meine Erfahrung stammt aus dem Umzug von Java nach Clojure in den letzten Jahren.
Einige Beispiele:
Verstehen Sie Ihre Geschäftsdomäne / Ihr Datenmodell - ebenso wichtig, ob Sie ein Objektmodell entwerfen oder eine funktionale Datenstruktur mit verschachtelten Karten erstellen möchten. In mancher Hinsicht kann FP einfacher sein, da es Sie dazu ermutigt, das Datenmodell getrennt von Funktionen / Prozessen zu betrachten, aber Sie müssen trotzdem beides tun.
Serviceorientierung im Design - funktioniert aus FP-Sicht sehr gut, da ein typischer Service eigentlich nur eine Funktion mit einigen Nebenwirkungen ist. Ich denke, dass die "Bottom-up" -Ansicht der Softwareentwicklung, die manchmal in der Lisp-Welt vertreten wird, eigentlich nur gute serviceorientierte API-Designprinzipien in einem anderen Gewand sind.
Testgesteuerte Entwicklung - funktioniert gut in FP-Sprachen, manchmal sogar noch besser, da sich reine Funktionen hervorragend zum Schreiben klarer, wiederholbarer Tests eignen, ohne dass eine zustandsbehaftete Umgebung eingerichtet werden muss. Möglicherweise möchten Sie auch separate Tests erstellen, um die Datenintegrität zu überprüfen (z. B. enthält diese Zuordnung alle Schlüssel, die ich erwarte, um die Tatsache auszugleichen, dass in einer OO-Sprache die Klassendefinition dies zum Zeitpunkt der Kompilierung für Sie erzwingen würde).
Prototying / Iteration - funktioniert genauso gut mit FP. Möglicherweise können Sie sogar live mit Benutzern Prototypen erstellen, wenn Sie sehr gut darin sind, Tools / DSL zu erstellen und diese auf der REPL zu verwenden.
quelle
Die OO-Programmierung verbindet Daten eng mit dem Verhalten. Die funktionale Programmierung trennt die beiden. Sie haben also keine Klassendiagramme, aber Sie haben Datenstrukturen und Sie haben insbesondere algebraische Datentypen. Diese Typen können so geschrieben werden, dass sie sehr genau zu Ihrer Domain passen, einschließlich der Beseitigung unmöglicher Werte durch Konstruktion.
Es gibt also keine Bücher und Bücher darüber, aber es gibt einen gut etablierten Ansatz, um, wie das Sprichwort sagt, unmögliche Werte nicht darstellbar zu machen.
Auf diese Weise können Sie eine Reihe von Optionen für die Darstellung bestimmter Datentypen als Funktionen und umgekehrt für die Darstellung bestimmter Funktionen als Vereinigung von Datentypen treffen, um z. B. Serialisierung, strengere Spezifikation, Optimierung usw. Zu erhalten .
Dann schreiben Sie Funktionen über Ihre Anzeigen, so dass Sie eine Art Algebra etablieren - dh es gibt feste Gesetze, die für diese Funktionen gelten. Einige sind vielleicht idempotent - das gleiche nach mehreren Anwendungen. Einige sind assoziativ. Einige sind transitiv usw.
Jetzt haben Sie eine Domain, über die Sie Funktionen haben, die nach gut erzogenen Gesetzen zusammengesetzt sind. Ein einfaches eingebettetes DSL!
Oh, und angesichts der Eigenschaften können Sie natürlich automatisierte randomisierte Tests von ihnen schreiben (ala QuickCheck). Und das ist erst der Anfang.
quelle
Objektorientiertes Design ist nicht dasselbe wie Software-Engineering. Software-Engineering hat mit dem gesamten Prozess zu tun, wie wir pünktlich und mit einer geringen Fehlerrate von den Anforderungen zu einem funktionierenden System gelangen. Die funktionale Programmierung kann sich von der OO unterscheiden, beseitigt jedoch nicht Anforderungen, hochrangige und detaillierte Designs, Verifizierung und Tests, Softwaremetriken, Schätzungen und all das andere "Software-Engineering-Zeug".
Darüber hinaus weisen Funktionsprogramme Modularität und andere Strukturen auf. Ihre detaillierten Entwürfe müssen in Form der Konzepte in dieser Struktur ausgedrückt werden.
quelle
Ein Ansatz besteht darin, ein internes DSL innerhalb der funktionalen Programmiersprache Ihrer Wahl zu erstellen. Das "Modell" ist dann eine Reihe von Geschäftsregeln, die im DSL ausgedrückt werden.
quelle
Siehe meine Antwort auf einen anderen Beitrag:
Wie geht Clojure die Trennung von Bedenken an?
Ich bin damit einverstanden, dass mehr zu diesem Thema geschrieben werden muss, wie große Anwendungen strukturiert werden können, die einen FP-Ansatz verwenden (und es muss mehr getan werden, um FP-gesteuerte Benutzeroberflächen zu dokumentieren).
quelle
Obwohl dies als naiv und simpel angesehen werden kann, denke ich, dass "Designrezepte" (ein systematischer Ansatz zur Problemlösung bei der Programmierung, wie er von Felleisen et al. In ihrem Buch HtDP vertreten wird ) nahe an dem liegen, wonach Sie suchen.
Hier ein paar Links:
http://www.northeastern.edu/magazine/0301/programming.html
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.86.8371
quelle
Ich habe kürzlich dieses Buch gefunden: Funktionale und reaktive Domänenmodellierung
Ich denke, das passt perfekt zu Ihrer Frage.
Aus der Buchbeschreibung:
quelle
Es gibt den Stil "Programmberechnung" / "Entwurf durch Berechnung", der mit Prof. Richard Bird und der Gruppe "Algebra of Programming" an der Universität Oxford (UK) verbunden ist. Ich denke nicht, dass er zu weit hergeholt ist, um dies als Methodik zu betrachten.
Persönlich mag ich die Arbeit der AoP-Gruppe, aber ich habe nicht die Disziplin, Design auf diese Weise selbst zu üben. Dies ist jedoch mein Mangel und nicht einer der Programmberechnung.
quelle
Ich habe festgestellt, dass verhaltensgesteuerte Entwicklung eine natürliche Ergänzung für die schnelle Entwicklung von Code in Clojure und SBCL ist. Der eigentliche Vorteil der Nutzung von BDD mit einer funktionalen Sprache besteht darin, dass ich dazu neige, viel feinere Korn-Unit-Tests zu schreiben als normalerweise, wenn ich prozedurale Sprachen verwende, da ich das Problem viel besser in kleinere Funktionsblöcke zerlege.
quelle
Wenn Sie ehrlich gesagt Designrezepte für Funktionsprogramme entwerfen möchten, schauen Sie sich die Standardfunktionsbibliotheken wie Haskells Prelude an. In FP werden Muster normalerweise von Prozeduren höherer Ordnung (Funktionen, die mit Funktionen arbeiten) selbst erfasst. Wenn also ein Muster gesehen wird, wird häufig einfach eine Funktion höherer Ordnung erstellt, um dieses Muster zu erfassen.
Ein gutes Beispiel ist fmap. Diese Funktion nimmt eine Funktion als Argument und wendet sie auf alle "Elemente" des zweiten Arguments an. Da es Teil der Functor-Typklasse ist, kann jede Instanz eines Functor (wie eine Liste, ein Diagramm usw.) als zweites Argument an diese Funktion übergeben werden. Es erfasst das allgemeine Verhalten beim Anwenden einer Funktion auf jedes Element seines zweiten Arguments.
quelle
Gut,
Im Allgemeinen werden viele funktionale Programmiersprachen an Universitäten seit langem für "kleine Spielzeugprobleme" verwendet.
Sie werden jetzt immer beliebter, da OOP aufgrund von "Status" Probleme mit der "Parallelprogrammierung" hat. Und manchmal ist der funktionale Stil besser für Probleme wie Google MapReduce.
Ich bin mir sicher, dass einige von ihnen, wenn sie an die Wand stoßen [versuchen, Systeme mit mehr als 1.000.000 Codezeilen zu implementieren], neue Methoden für das Software-Engineering mit Schlagworten haben werden :-). Sie sollten die alte Frage beantworten: Wie kann man das System in Teile teilen, damit wir jedes Teil einzeln "beißen" können? [iterativ, inkrementell und evolutionär arbeiten] mit Functional Style.
Aber werden funktionale Programme für so große Systeme verwendet? Werden sie zum Hauptstrom? Das ist die Frage .
Und niemand kann mit einer realistischen Methodik kommen, ohne ein so großes System zu implementieren, das seine Hände schmutzig macht. Zuerst sollten Sie sich die Hände schmutzig machen und dann eine Lösung vorschlagen. Lösungsvorschläge ohne "echte Schmerzen und Schmutz" werden "Fantasie" sein.
quelle