- Beim Programmieren von links nach rechts bleibt das Programm in dem Moment, in dem Code eingegeben wird, stets in einem gültigen Zustand, wodurch Tool-Unterstützung wie die Autovervollständigung des Editors maximiert wird
- Die List Comprehension in Python behindert die Autovervollständigung, weil nicht deklarierte Variablen und fehlende Typinferenz im Weg stehen
- Rust und JavaScript lassen sich natürlich von links nach rechts aufbauen, wodurch die Verwendung von Variablen und das Erkunden von Methoden intuitiver werden
- Der funktionale Stil in C und Python beeinträchtigt durch die schwache Auffindbarkeit von Funktionsnamen und Strukturen eine effiziente Coding-Erfahrung
- Bei Logik mit hoher Komplexität ist Code, der sich von links nach rechts entfaltet, leichter zu lesen und besser für Wartbarkeit und Erweiterbarkeit geeignet
Von links nach rechts programmieren
Code muss in dem Moment, in dem er eingegeben wird, gültig sein
Die Grenzen von Python-List-Comprehensions
- Die Python-Syntax für List Comprehensions
words_on_lines = [line.split() for line in text.splitlines()]verursacht das Problem, dass der Editor Autovervollständigung und Typinferenz nicht richtig bereitstellen kann, weil auf eine nicht deklarierte Variable (line) zugegriffen werden muss - Während der schrittweisen Eingabe des Codes
- wenn man etwa
words_on_lines = [line.speingibt, kennt der Editor den Typ vonlinenicht und kann daher keine Methoden vorschlagen - auch potenzielle Fehler wie Tippfehler im Variablennamen (
limeusw.) lassen sich schwer erkennen
- wenn man etwa
- Um korrekte Vorschläge zu erhalten, muss man unvollständigen Code schreiben, was unintuitiv und unbequem ist
Aufbau von links nach rechts in Rust
- Im Rust-Beispiel (
let words_on_lines = text.lines().map(|line| line.split_whitespace());)- gilt eine Variable (
line) in dem Moment, in dem sie zusammen mit der anonymen Funktion erstmals erscheint, als deklariert, sodass sofort Autovervollständigung und Methodenvorschläge möglich sind - auch die Methode
split_whitespaceließ sich dadurch leicht finden, weil sie automatisch vorgeschlagen wurde
- gilt eine Variable (
- Dieser Ansatz hält das Programm immer in einem zumindest teilweise gültigen Zustand, sodass IDEs und Editoren das Coding in Echtzeit unterstützen können
Progressive Disclosure und API-Usability
- Progressive Disclosure ist ein Gestaltungsprinzip, bei dem Nutzer nur so viel Komplexität sehen, wie sie gerade benötigen; das lässt sich auch auf das Programmieren anwenden
- Beispiel: ähnlich wie bei der UX einer Textverarbeitung, in der relevante Optionen erst erscheinen, wenn ein Bild eingefügt wird
- In C fehlt diese Unterstützung weitgehend
- da sich nicht alle zu
FILE *filegehörenden Funktionen überfile.erkunden lassen, muss man Namensmuster von Funktionen (fread,fcloseusw.) auswendig lernen, und Funktionen sind schwer zu entdecken - in einer idealen Sprache könnten passende Funktionen dagegen über Methodenvorschläge nach
file.leicht schrittweise entdeckt werden
- da sich nicht alle zu
Unterschied bei der Auffindbarkeit von Funktionen und Methoden
- Vergleich der Beispiele
map(len, text.split())in Python undtext.split(" ").map(word => word.length)in JavaScript- in Python muss man oft mehrere Versuche unternehmen, weil Funktionsnamen wie
len,lengthodersizenicht vorhersehbar sind - in JavaScript reicht es, nach
word.nur.leinzugeben, und der Editor schlägt Methoden wielengthvor, wodurch die Auffindbarkeit hoch ist - auch Higher-Order Functions wie
mapmachen den tatsächlichen Rückgabewert und den Datentyp unmittelbar klar erkennbar
- in Python muss man oft mehrere Versuche unternehmen, weil Funktionsnamen wie
Vorteile strukturierten Schreibens bei komplexerer Logik
- Bei Logik mit hoher Komplexität (langer Python-Code mit verschachteltem
filterundlambda)- muss man wiederholt Anfang und Ende des Codes prüfen, und Bedingungen oder Klammerzuordnungen verschlechtern Lesbarkeit und Verständlichkeit
- In der JavaScript-Version derselben Logik kann man den Code dagegen von oben nach unten und von links nach rechts sequenziell lesen und verstehen
Kernprinzip
Code muss in jedem Moment der Eingabe gültig sein
- Schon die alleinige Eingabe von
texthält das Programm in einem gültigen Zustand - Auch wenn man bis
text.split(" ")schreibt und anschließend.map(word => word.length)ergänzt, bleibt der Gesamtzustand durchgehend gültig - Dieses Coding-Muster erhöht die Möglichkeit Echtzeit-Unterstützung durch den Editor und erlaubt es in einer REPL-Umgebung, Ergebnisse sofort zu prüfen
Fazit
- API- und Sprachdesign sollten so gestaltet sein, dass Code auf natürliche Weise von links nach rechts eingegeben werden kann und in jedem Zwischenschritt ein gültiges Programm entsteht
- Gutes API-Design ist der Schlüssel zur Verbesserung dieser Coding-Erfahrung
1 Kommentare
Hacker-News-Kommentare
SELECTstatt mitFROMbeginnen. Dadurch ist nicht sofort klar, mit welcher Entität bzw. Tabelle gearbeitet wird, und es erschwert auch Smart-Editoren, beim Schreiben von Queries effizienter zu helfen. Die Reihenfolge FROM -> SELECT -> WHERE wäre natürlicher, insbesondere weil man imSELECT-Teil Spaltennamen festlegt und diese dann inWHEREreferenziert. Eigentlich, so finde ich, müsste man stattSELECT * FROM tableauch einfach nurFROM tableschreiben können und dieSELECT-Klausel wäre implizit. Vielleicht klinge ich mit diesem Ärger wie ein nörgelnder alter Mann, aber es ist einfach meine persönliche Sehnsucht.FROMzuerst kommt. BigQuery hat inzwischen auch eine Pipe-/Pfeilsyntax hinzugefügt, und es gibt Community-Extensions für DuckDB, die ich empfehlen kann: DuckDB - PSQL, DuckDB - PRQLWHEREkeine Spalten-Aliasse verwenden, weil die Selektion (WHERE) vor der Projektion (SELECT) stattfindet. Zur Info: In MySQL 8 gibt es auch die SyntaxTABLE <table>.SELECTdefinierte Spalten-Aliasse inGROUP BY,HAVINGundORDER BYverwendet werden, aber nicht inWHERE.FROMzuerst kommt. Ich halte diese Struktur für gut, weil man beim Schreiben anderer Klauseln in der IDE dank Autovervollständigung sofort Feldvorschläge bekommt.FROMbeginnen, deutlich aktiver einführen. Ich halte das auch nicht für schwierig; es fehlt einfach an Versuchen, die Nutzbarkeit zu verbessern.for-Schleifen mit festgelegtem Typ. Alle sorgen sich darum, dass Python-Typen locker sind, aber ausgerechnet die einzige Syntax, die den Rückgabetyp eindeutig festlegt, nämlich die Listen-Comprehension, wird zum Ziel. In den meisten anderen Sprachen, einschließlich Rust, ist die Reihenfolge ebenfalls nicht "from iter as var". Interessant ist auch, die Funktionsaufrufsyntax verschiedener Sprachen zu vergleichen, zumal es in Python ja auchfunctools.mapgibt.return.{3} for {2} in {1}möglich. Solche Werkzeuge schaffen einen Kompromiss zwischen "gut lesbarer Syntax" und "leicht zu tippender Syntax". Ich tendiere dazu, Lesbarkeit der Syntax zu priorisieren, selbst wenn man dafür Tooling ausnutzt. Ich finde nicht, dass man unbedingt an einerfor-in-Struktur festhalten muss.let foo = ...einer Variablen zuzuweisen, würde ich gern einmal eine Form wie... =: fooausprobieren.bake(divide(add(knead(mix(flour, water, sugar, butter)),eggs),12),450,12)zu schreiben, ist es mit Pipes alsmix(flour, water, sugar, butter) %>% knead() %>% add(eggs) %>% divide(12) %>% bake(temp=450, minutes=12)viel einfacher und übersichtlicher.from some_library import child_modulesehr intuitiv. In JS wirkt eine Struktur wieimport { asYetUnknownModule } from SomeLibrarydeutlich weniger intuitiv.len(list(filter(lambda line: all([abs(x) >= 1 and abs(x) <= 3 for x in line]) and (all([x > 0 for x in line]) or all([x < 0 for x in line])), diffs)))schreiben, sondern lieber mit einem NumPy-Array arbeiten; so muss man keine neuen Listen im Speicher anlegen und kann die gesamte Zeile viel besser auf einmal verarbeiten, zum Beispiel: Das spiegelt "von links nach rechts" deutlich besser wider.