- Die Ausführlichkeit der Fehlerbehandlung in Go gehört seit Langem zu den häufigsten Klagen der Nutzer
- Verschiedene Vorschläge zur Syntaxverbesserung (z. B.
check/handle, try, der ?-Operator usw.) wurden diskutiert und erprobt, aber ohne ausreichenden Konsens in der Community allesamt verworfen
- Wichtige Abwägungen sind die weitreichenden Auswirkungen von Sprachänderungen auf Code, Tools, Dokumentation usw. sowie das für Go typische Prinzip, die Sprache einfach zu halten
- Aufgrund der Klarheit des aktuellen Ansatzes, der einfachen Fehlersuche und der Präferenz mancher Nutzer gibt es nur eine schwache Begründung dafür, überhaupt eine Syntaxänderung einzuführen
- Auf absehbare Zeit gibt es keine Pläne für Änderungen an der Syntax der Fehlerbehandlung, und die entsprechenden Vorschläge sollen ohne weitere Untersuchung abgeschlossen werden
Das Problem der Ausführlichkeit bei der Fehlerbehandlung in Go
- Eine der langjährigen Beschwerden über Go ist, dass die Syntax der Fehlerbehandlung übermäßig ausführlich ist
- Typischerweise tauchen Muster wie
if err != nil im Code immer wieder auf
- Je mehr API-Aufrufe ein Programm benötigt, desto stärker fällt dieses Muster auf, sodass am Ende mehr Code für die Fehlerbehandlung als für die eigentliche Logik entsteht
- In den jährlichen Nutzerumfragen wird diese Beschwerde weiterhin regelmäßig weit oben genannt
Abstimmung mit der Community und frühe Vorschläge
- Das Go-Team misst dem Feedback aus der Community große Bedeutung bei und hat deshalb die Erforschung von Verbesserungen der Fehlerbehandlung fortgesetzt
- In den Diskussionen zum Projekt Go 2 im Jahr 2018 fasste Russ Cox die Kernprobleme der Fehlerbehandlung offiziell zusammen
- Enthalten war der von Marcel van Lohuizen vorgeschlagene Mechanismus
check und handle
- Dazu kamen Vergleiche mit ähnlichen Sprachen und die Prüfung verschiedener Alternativen
- Dieser Ansatz machte den Code zwar tatsächlich kompakter, wurde aber wegen der zunehmenden Komplexität nicht übernommen
Der try-Vorschlag und was danach geschah
- 2019 wurde ein deutlich vereinfachter Vorschlag für die eingebaute Funktion
try gemacht
- Im Code wurde nur die
check-Funktionalität übernommen, handle entfiel
- Der Vorschlag wurde dafür kritisiert, den Kontrollfluss zu verbergen, und nach Gegenwind aus der Community verworfen
- Diese Erfahrung machte das Risiko ausgereifter Vorschläge ohne ausreichendes Feedback deutlich
- Sie zeigte, dass bei Vorschlägen für größere Änderungen schon in der frühen Entwurfsphase breiteres Meinungsbild wichtig ist
Weitere Versuche und verschiedene Vorschläge
- Zahlreiche Varianten und alternative Ansätze zur Fehlerbehandlung wurden in der Community fortlaufend vorgeschlagen
- Ian Lance Taylor bündelte den Stand in einem Umbrella-Issue, und im Go Wiki sowie in Blogs wurden weiterhin Beispiele gesammelt
- 2024 gab es einen Vorschlag zur Übernahme des aus Rust bekannten
?-Operators
- In kleineren Usability-Tests wurde er als intuitiv bewertet, doch auch hier kam trotz vielfältiger Meinungen kein Konsens zustande
Stillstand in der Diskussion und Schlussfolgerung
- Es gab mehr als drei offizielle oder inoffizielle Vorschläge und Hunderte Community-Vorschläge, doch mangels ausreichender Zustimmung bzw. Konsens wurden sie alle abgelehnt
- Selbst innerhalb der internen Architektengruppe von Go gibt es keine Einigkeit über die Richtung
- Bis sich die Lage ändert oder ein besonderer Konsens entsteht, wurde beschlossen, Versuche zur Änderung der Syntax der Fehlerbehandlung selbst einzustellen
Zentrale Argumente für die Beibehaltung des aktuellen Ansatzes
- Wäre schon beim ursprünglichen Sprachdesign syntaktischer Zucker eingeführt worden, hätte es vielleicht keine Kontroverse gegeben, doch inzwischen gibt es ein Ökosystem, das an einen seit 15 Jahren verwendeten Ansatz gewöhnt ist
- Die Einführung neuer Syntax würde zwangsläufig die Sorge auslösen, dass zwischen bestehender und neuer Nutzerschaft Stilunterschiede im Code entstehen und die Konsistenz leidet
- Das passt auch zur Designphilosophie von Go, dieselbe Sache nicht auf mehrere Arten zu tun, und zum Prinzip, Kürze und Konsistenz hoch zu gewichten
- Auch die erlaubte Neudeklaration bei kurzen Variablendeklarationen (
:=) ist eine indirekte Folge der Fehlerbehandlung
- Die explizite Syntax der Fehlerbehandlung (über
if) hat intuitive Vorteile beim Lesen von Code, beim Debugging und beim Setzen von Breakpoints
- Sprachänderungen sind auch hinsichtlich des tatsächlichen Änderungsumfangs (Code, Dokumentation, Tools usw.) und der Kosten eine große Belastung
Alternative Verbesserungen und künftige Richtung
- Durch Ausbau der Standardbibliothek (z. B. Einführung von
cmp.Or) lässt sich ein Teil des wiederholten Codes reduzieren
- Mit Code Folding, Autovervollständigung, dem Einsatz von LLMs und anderen IDE- bzw. Entwicklungstools lässt sich die Ausführlichkeit in der Praxis bis zu einem gewissen Grad abfedern
- In wichtigen Gruppen von Go-Nutzern (z. B. Teilnehmern der Veranstaltung Google Cloud Next) überwiegt die skeptische Haltung gegenüber Sprachänderungen
- Je länger Go genutzt wird, desto weniger stark wird das Problem der Ausführlichkeit tatsächlich wahrgenommen
Argumente für den Bedarf an Syntaxverbesserungen
- Auf Basis des Nutzerfeedbacks besteht weiterhin der Wunsch nach Verbesserungen der Syntax der Fehlerbehandlung
- Eine Syntax für die Fehlerbehandlung, die nicht nur Zeichen spart, sondern die Klarheit erhöht, könnte zur Verbesserung von Codequalität und Sicherheit beitragen
- Es braucht genauere Forschung zur Fehlerbehandlung, die nicht nur einfache Fehlerprüfungen betrifft, sondern tatsächlich eine funktionale Rolle im Code spielt
Endgültige Schlussfolgerung und künftige Politik
- In Anerkennung der bisherigen Lage ohne nennenswerten Konsens oder praktische Veränderung wird erklärt, dass die Diskussion und Vorschläge zu syntaktischen Sprachänderungen für die Fehlerbehandlung auf absehbare Zeit vollständig eingestellt werden
- Die bisherigen Diskussionen und Untersuchungen haben dennoch indirekt zur Verbesserung des Go-Ökosystems und der Prozesse beigetragen
- Falls künftig eine klarere Problemdefinition und ein tragfähigerer Konsens entstehen, könnte die Diskussion wieder aufgenommen werden
- Vorerst soll der Schwerpunkt eher darauf liegen, die Robustheit und Einfachheit von Go selbst zu bewahren, statt neue Versuche zu starten
1 Kommentare
Hacker-News-Kommentare
Wer leichtfertig vorschlagen möchte, das Go-Team hätte auch andere Alternativen wählen können, sollte sich unbedingt den Go2ErrorHandlingFeedback-Wiki oder die GitHub-Issuesuche ansehen. Fast jede vorgeschlagene Idee wurde bereits ernsthaft diskutiert, und als Nutzer, der den transparenten Ansatz des Go-Teams schätzt, macht es mir große Freude, Go jeden Tag zu verwenden.
Im Entwurfsdokument werden C++, Rust und Swift erwähnt, aber do-Notation/for-comprehensions/monadic-let aus funktionalen Sprachen wie Haskell, Scala oder OCaml, nach denen ich suche, sind kaum zu finden. Das Go-Team wirkt zwar wie ein Meister der Sprachgestaltung, stößt aber beim Thema Fehlerbehandlung offenbar an die Grenzen statischer Typen ohne parametrischen Polymorphismus, ähnlich wie Java. Ich halte das für ein Problem, das aus dem grundlegenden Sprachdesign stammt.
Obwohl das Dokument von klugen und erfahrenen Leuten verfasst wurde, ist es sehr merkwürdig, dass Lösungen wie Haskells Maybe/Either-Monaden und der bind-Operator (do-Notation) nirgends erwähnt werden. Dabei sind sie in der Praxis weder schwierig noch akademisch, sondern ein sehr eleganter und bewährter Weg, Fehler sicher weiterzugeben. Ich verstehe nicht, warum die Go-Community das nicht aufgegriffen hat. Ich bin dankbar, dass diese Seite existiert, aber dass eine so bekannte Lösung übergangen wird, ist schwer nachzuvollziehen.
Fast alle Sprachen bieten verschiedene bessere Ansätze, daher frage ich mich, warum dieses Problem gerade in Go so stark hervorsticht. Liegt es einfach daran, dass es keinen Konsens gibt, oder gibt es eine Besonderheit der Sprache Go, wegen der Lösungen aus anderen Sprachen nicht passen?
In Go-Kritik sieht man oft das Phänomen, dass vergleichsweise weniger fachkundige Leute voraussetzen, Go-Entwickler verstünden weniger von Sprachen als sie selbst. Tatsächlich sind Go-Entwickler in den meisten Fällen deutlich erfahrener und wissen viel mehr. Nicht-Experten glauben oft, eine Sprache mit mehr Features sei automatisch besser, übersehen dabei aber, dass es in Wirklichkeit darauf ankommt, die Gesamtbalance gut hinzubekommen.
Ich denke, die Nutzer profitieren von Gos konservativer Haltung, neue Sprachfeatures nur mit Vorsicht hinzuzufügen. Bei Swift gibt es so viele Feature-Änderungen, dass das Lernen schwierig wird, und selbst auf aktuellen Macs erlebt man mitunter, dass nicht einmal ein einfaches Projekt gebaut werden kann. Weil ständig neue Keywords hinzukommen und sich ändern, fehlt Swift etwas an Beständigkeit, während genau diese Beständigkeit eine Stärke von Go ist.
Einmal hatte ich in Go einen ungewöhnlichen Fall, in dem eine Funktion erwartete, dass in einer internen Funktion ein Fehler auftritt, und wenn die interne Funktion keinen Fehler lieferte, musste die Funktion selbst einen Fehler zurückgeben. In dieser seltenen Struktur musste ich mit
if err == nilverzweigen, schrieb aber aus Gewohnheitif err != nil, und weil ich zu sehr an das übliche Muster gewöhnt war, brauchte ich lange, um den Fehler zu finden. Ich dachte mir damals, dass eine sprachliche Unterscheidung zwischen dem häufigenif err != nilund dem seltenenif err == nilsolche Fehler reduzieren könnte.if err == nilschreibe, füge ich den Kommentar// invertedhinzu, um das Muster hervorzuheben. Es wäre gut, wenn die Sprache das automatisch behandeln würde, aber derzeit lässt sich so die Unterscheidung zumindest deutlicher machen.if err == nil { return ... }könnte im Code sogar noch merkwürdiger aussehen. Es gibt die Ansicht, dass die aktuelle Go-Fehlerbehandlung klar und gut lesbar ist und deshalb von vielen bevorzugt wird.if fruit != "Apple"entstehen, daher sollte man das grundsätzlich nicht nur als Problem der Fehlerbehandlung sehen, sondern als allgemeines Problem von Zustandsverzweigungen. Fehler werden letztlich auch nur wie andere Zustandswerte behandelt.if err != nilwie ein Sonderzeichen rendern, damit es sich natürlicher in den Hintergrund einfügt und weniger auffällt, während ein anders geschriebenesif err == nilhervorgehoben wird. So ließen sich Fehler möglicherweise auf Editor-Ebene vermeiden.if err … {verkürzt dargestellt werden.Mir gefällt Gos explizite Fehlerbehandlung. Ich verstehe eine Funktion einfach so, dass sie entweder immer erfolgreich ist (minimal error) oder fehlschlagen kann. Funktionen, die scheitern können, müssen zwingend behandelt werden, bevor man zum nächsten Schritt übergehen kann. In vielen Sprachen werden Fehler über Exceptions die Stack-Ebene hinaufgeworfen, bis sie irgendwo gecatcht werden, was oft nur zeigt, wo der Fehler aufgetreten ist, aber wenig praktische Hinweise liefert. In Go hat man klar folgende Optionen: 1) Fehler ignorieren 2) bei Fehler sofort zurückkehren 3) Fehler wrappen und nützliche Informationen ergänzen 4) einen bestimmten Fehler interpretieren und verzweigt behandeln (z. B. in 404 umwandeln). In Go2 würde ich gern einen
Result<Value, Failure>-Typ oder spezifischere und aufzählbare Fehlertypen sehen. Wegen der Kompatibilität zu Go 1 wäre eine Einführung in Go 2 passender.Gos Art der Fehlerbehandlung gefiel mir anfangs nicht, aber nachdem ich den Blogpost errors-are-values gelesen und begonnen hatte,
panic(err)an passenden Stellen einzusetzen, war ich im Gegenteil sehr zufrieden. Für anomale Zustände, die der aufrufende Code nicht direkt behandeln sollte, konnte ich mit panic viele kleinteilige Fehlerverzweigungen stark reduzieren. Diese Art des Fehlermanagements hilft mir in der Praxis sehr.@-Operator kennt und dass es in bash Fehlermanagement-Techniken wie-egibt.Wenn behauptet wird, dass die Ausführlichkeit schnell verschwindet, sobald man Fehler tatsächlich behandelt, stellt sich die Frage, ob das manuelle Erzeugen von Stack Traces wirklich „Behandlung“ ist. Nach Gos Definition wären dann nicht auch Exceptions eine Form der Behandlung? Das ist ein amüsanter Gegenpunkt.
Mir gefällt nicht, dass dieser Artikel das Problem von Gos Fehlerbehandlung einfach als „die Syntax ist zu ausführlich“ behandelt. Die eigentlichen Probleme sind meiner Meinung nach: 1) Fehler gehen leicht stillschweigend verloren oder werden versehentlich ignoriert 2) Funktionsergebnisse lassen sich nicht so leicht wie Werte weiterreichen oder speichern 3) verschachtelte Fehler wie bei
errors.Isgreifen ungeschickt in das Typsystem ein 4) Fehler-Switching ist schwierig 5) Sentinel Values werden in der Standardbibliothek häufig verwendet 6) die Kombination mit Generics ist unglücklich, sodass zusätzlicher Paketbedarf entsteht.In Elixir (und Erlang) geben Funktionen normalerweise Tupel wie
{:ok, result}oder{:error, description}zurück. Dank derwith-Syntax in Elixir lässt sich Fehlerbehandlung am unteren Ende des Blocks bündeln, was die Lesbarkeit deutlich verbessert. Würde man etwas Ähnliches wiewithin Go einführen, könnte man den Code lesbarer machen, indem man nur dann fortfährt, wenn der Fehlernilist, und ganz unten einen Error-Handler-Block platziert.Ich verstehe nicht, warum man nicht einfach dem Rust-Stil folgt. Gerade jetzt, wo es Generics gibt, sollte sich etwas Ähnliches schnell umsetzen lassen. Ich kann auch die Kritik nicht nachvollziehen, dass der
?-Operator in Rust das Ignorieren von Fehlern fördere. Tatsächlich gibt es in Go viele Fälle, in denen man Fehler-Rückgabewerte ignoriert, ohne dass ein Compilerfehler entsteht. Nur wenn wie in Rust die Rückgabe eines Result-Typs erzwungen wird, lassen sich solche Fehler vermeiden. Wenn das aus Bequemlichkeitsgründen umstritten ist, müsste man konsequenterweise auch panic verbieten — so das starke Argument.?-Operator“ bedeute, dass man „wrapped errors nicht mehr verwenden wird“, lautet die Gegenrede, dass sich solche Features im Gegenteil so gestalten lassen, dass sie Wrapping fördern.Anders als bei einer Checkbox-Diskussion über Feature-Übernahme wie bei Rust sollte eine Sprache als Teil einer gesamten Konsistenz entworfen werden. Nur weil man eine Featureliste vollständig abhaken kann, heißt das nicht, dass sie auch wirklich zum Wesen der Sprache passt.