axelerator.de

Wenn Kent Beck dein Doktor ist, ist Elm deine Apotheke

8 Minuten
#elm
24 August 2024

Kent Beck ist bekannt für seinen pragmatischen Ansatz im Softwaredesign sowie seinen Fokus auf die Verbesserung der Codequalität und Effizienz von Entwicklungsprozessen. In einem seiner Artikel “verschreibt” er eine Liste von “Rezepten”, um weniger fehleranfälligen Code zu schreiben. Und jetzt habe ich eine konkrete Liste von Gründen, warum ich es so sehr mag in Elm zu programmieren.



Elm ist eine funktionale Sprache, die nach JavaScript kompiliert wird. Sie hilft dir, Websites und Web-Apps zu erstellen. Sie legt großen Wert auf Einfachheit und qualitativ hochwertige Werkzeuge.

Das folgende Kent Beck-Zitat tauchte neulich im Elm Slack auf:

“Du hast keine Bugs, weil du keine Tests hast. Du hast Bugs, weil du Code schreibst, der es leicht macht, Bugs zu haben.”

Es stammt aus Kent Becks Artikel Scrape Then Paint, und ich empfehle jedem, ihn zu lesen. Jeremy, der das Zitat gepostet hat, sagte auch: “Das hat mich daran erinnert, wie schön es ist, in Elm zu programmieren.” Ich stimme dem instinktiv zu, wollte aber etwas tiefer graben warum wir so über Elm empfinden, aber nicht, oder weniger über andere Programmiersprachen.

Wenn du jemandem eine alternative Programmiersprache vorschlägst, ist die Antwort oft, dass der- oder diejeninge “ja bereits alles in der Sprache machen könne”, mit der er oder sie vertraut sind. Letztendlich kannst du fast jedes Problem, das du in einer Sprache lösen kannst, auch in jeder anderen Sprache lösen. Die Frage sollte also eher sein, wie effizient (mit wie viel Aufwand) kann ich das Problem lösen?

Wenn du den Aufwand des “Erlernens einer neuen Programmiersprache” zu einer Programmieraufgabe hinzufügst, wird die neue Sprache natürlich immer gegen die Lösung des Problems in der vertrauten Umgebung verlieren.

Wenn du nicht kurz vor der Rente stehst, würde ich argumentieren, dass dies eine kurzsichtige Sichtweise ist.

Verschiedene Sprachen, Frameworks und Bibliotheken machen unterschiedliche Kompromisse. Nur einen Weg zu kennen, um alle Programmierprobleme zu lösen, macht dich zu einem “One trick Pony”. Und selbst wenn du nicht alle Werkzeuge in deinem Werkzeuggürtel ständig benutzt, wird die Auseinandersetzung mit verschiedenen Ansätzen in einer Sprache deinen Horizont für Lösungen in anderen Sprachen erweitern.

Wie Elm liefert, was Kent verschreibt

Deshalb werbe ich weiterhin für die Programmiersprache Elm bei den Leuten.

Die meisten “Rezepte” die Kent Beck in seinem Artikel verschreibt löst Elm bereits auf der Sprachebene. Lass uns einen Blick darauf werfen welche Maßnahmen er vorschlägt:

  • Verbessere die Kohäsion durch Reduzierung der Elementgröße
  • Vereinheitliche duplizierte Logik
  • Ersetze duplizierte Bedingungen durch Polymorphismus
  • Verwende unveränderliche Datenstrukturen, wo möglich, um Aliasfehler zu eliminieren
  • Vereinfache Kontrollflüsse wo möglich
  • Eliminiere toten Code
  • Rationalisiere die Namensgebung, damit die richtigen Namen eher erraten werden können

Verbessere die Kohäsion durch Reduzierung der Elementgröße

Das Kapitel über Module im Elm-Leitfaden enthält einen Link zu diesem sehr aufschlussreichen Vortrag darüber, wie man Module um Datentypen herum organisiert.

Elm hat ein einfaches, aber leistungsfähiges Modulsystem. Es ermöglicht die Kapselung von Datenstrukturen und den Funktionen, die auf ihnen operieren, ohne auf die komplizierten Semantiken vieler objektorientierter Sprachen zurückzugreifen.

Abhöngigkeiten zwischen Modulen können effektiv begrenzt werden, indem explizit ausgewählt wird, welche Teile eines Moduls veröffentlicht werden.

Vereinheitliche duplizierte Logik

Es gibt nichts Eingebautes in Elm, das dich davon abhält, Logik zu duplizieren. Da Elm jedoch eine rein funktionale Sprache ist, ist es einfacher, Duplikationen zu erkennen und zu vereinheitlichen.

In imperativen Sprachen mischt man oft “Berechnen” und “Dinge tun” - zum Beispiel den Zugriff auf einige Felder in einem Objekt und das Schreiben in die Datenbank. In diesen Fällen muss ich das mentale “Pattern Matching” auf “halbe” Methoden durchführen, um einen Teil der Logik zu erkennen und zu extrahieren, um sie wiederzuverwenden.

In Elm ist es viel einfacher Funktionen zu extrahieren. Wegen der genauen Typüberprüfung und der Tatsache das man in Funktionen nicht mehr als “neue Werte” berechnen kann, sind Refaktoriesierungen einfacher.

Ersetze duplizierte Bedingungen durch Polymorphismus

Ich war mir zunächst nicht sicher, was er damit meinte, also habe ich das “Prinzip” gegoogelt und einen Artikel gefunden, der meinen Punkt wunderbar belegt. Disclaimer: Ich will kein schlechtes Licht auf diese spezielle Lösung werfen, aber der Umfang der objektorientierten Lösung hier sieht für wahrscheinlich jeden Elm-Entwickler überwältigend kompliziert aus.

In Elm ist es sehr natürlich, zuerst in Typen zu denken - nicht in Bedingungen. Eine wahrscheinliche Lösung wäre also, mit einen Datentyp type Role = Admin | Subscriber | Other zu beginnen.

Jetzt können wir ein case (switch oder match in anderen Sprachen) verwenden, um zu entscheiden, was wir in unseren views anzeigen.

case user.role of
	Admin -> ..show admin stuff..
	Subscriber -> ..show subscriber stuff..
	Other -> ..

Elm hat exhaustiveness checking eingebaut. Das bedeutet, dass das Programm solange nicht kompiliert, bis du alle Varianten behandelt wurden. Das wiederum bedeuted, dass jedesmal wenn wir einen neue Variante hinzufügen oder entfernen, wird der Compiler uns genau die Stellen markieren die wir anpassen müssen.

Verwende unveränderliche Datenstrukturen, wo möglich, um Aliasfehler zu eliminieren

Dies ist die einfachste Umsetzung eines “Rezepts”: Alles ist in Elm unveränderlich (immutable). Dies macht die Entscheidung wo unveränderliche Datenstrukturen zu verwenden sind vollständig überflüssig.

Das Einzige, was in einer Webanwendung veränderlich sein muss, ist der Zustand der Benutzeroberfläche, also der DOM. Der Zustand des DOMs wird vollständig von der Elm-Laufzeitumgebung verwaltet. In einer Elm-App haben wir eine view Funktion, die den aktuellen Anwendungszustand nimmt und Elm mitteilt, wie wir wollen, dass der DOM aussieht.

“Aber wie ändern wir den Zustand?” fragst du dich?. Darauf werden wir im nächsten Rezept eingehen.

Vereinfache Kontrollflüsse wo möglich

Alle Elm-Apps folgen “der Elm Architektur”. Jede App hat eine View-Funktion, die den aktuellen Zustand (Model) nimmt und HTML zusammen mit potenziellen Benutzerinteraktionen generiert, die als Msg (Nachrichten) dargestellt werden. Wenn eine Msg ausgelöst wird, verarbeitet die Update-Funktion sie, um einen neuen Zustand zu produzieren, der dann eine aktualisierte Ansicht generiert und den Zyklus des unidirektionalen Datenflusses aufrechterhält.

Hier vermischt sich Elm zwischen Sprache und Framework. Der beste Vergleich, den ich hier machen kann, ist React. Elm hat einen strengen unidirektionalen Informationsfluss. In Elm kannst den Zustand von nichts selbst ändern. Stattdessen sendet du Messages aus deiner View. Und dann ruft die Elm-Laufzeitumgebung die Update-Funktion auf, die du ebenfalls bereitstellst. Sie erhält die Message, und du kannst einen neuen Zustand deiner Anwendung zurückgeben. Das was in der React-Welt am nächsten an diese Architektur kommt ist die Verwendung von Redux. Der große Unterschied in Elm ist, dass es Teil der (streng prüfenden) Sprache ist und du es nicht (versehentlich) umgehen kannst.

Lies: Vermeide dieses häufige React Hook Antipattern darüber, warum die Verwendung von useState und useEffect zur Berechnung synchroner Werte zu einer suboptimalen Benutzererfahrung und subtilen Bugs führt, die schwer zu debuggen sein können.

Aufgrund der Einfachheit der Sprache und der Strenge des Compilers gibt es weniger Möglichkeiten, dasselbe auf unterschiedlichen Wegen zu tun. Da React JavaScript (oder TypeScript) verwendet, hat es weniger Kontrolle über die Umgebung, in der es läuft, und hat folglich viel mehr “Fußfallen”, wie zum Beispiel die gemeinsame Verwendung von useEffect und useState.

Kurz gesagt: Weil es weniger Möglichkeiten gibt, “das Falsche zu tun”, ist es einfacher, das Richtige zu tun. Und weil es weniger Möglichkeiten gibt, den Kontrollfluss zu steuern, werden dieser meistens auch einfacher.

Eliminiere toten Code

elm-review ist ein Elm-Paket, das dein Elm-Projekt analysiert, um Fehler zu finden, bevor deine Benutzer sie finden.

Der Elm-Compiler selbst hilft dir nicht viel dabei, unbenutzten Code zu erkennen. Aber es gibt ein ausgezeichnetes Tool namens elm-review, das dank des strengen Typsystems von Elm sehr effizient unbenutzten Code erkennen und entfernen kann.

Lies mehr darüber, wie Leute mühelos Tausende von Codezeilen entfernt haben und wie das Elm-Typsystem eine großartige statische Code-Analyse ermöglicht, um alle möglichen Arten von Optimierungen vorzunehmen.

Rationalisiere die Namensgebung, damit die richtigen Namen eher erraten werden können

Auch hier gibt der Elm-Compiler nur wenig Hilfe in Form von Regeln darüber, wie Bezeichner der verschiedenen syntaktischen Elemente (Typen, Funktionen, Variablen) formatiert werden können. Aber ich wüsste auch, wie die eigentliche Namensgebung von der Sprache selbst unterstützt werden kann. Vielleicht werden LLMs dies eines Tages für uns alle lösen 🙈.

Fazit

Elm ist nicht geeignet, um jedes Programmierproblem zu lösen, aber es ist die angenehmste Art für Frontend-Arbeit die ich je hatte. Und dieser Artikel kratzt nur an der Oberfläche der Liste von Funktionen, die es angenehm zu benutzen machen. Viele der Techniken, die ich für Elm lernen musste habe ich in Projekte in anderen Sprachen angwendet, um von den “Rezepten” die Kent Beck vorschlägt zu profitieren ohne dass ich davon wusste.

Hier ist eine Liste von Ressourcen, die du dir ansehen kannst, wenn du Elm mal etwas genauer ansehen willst: