1 Punkte von GN⁺ 2025-06-04 | 1 Kommentare | Auf WhatsApp teilen
  • Die wiederholten if err != nil-Prüfungen in Go sind seit Jahren ein großer Kritikpunkt in Nutzerumfragen, doch das Go-Team hat entschieden, Syntaxänderungen für die Fehlerbehandlung vorerst nicht weiterzuverfolgen
  • Die Vorschläge check/handle von 2018, try von 2019 und ? von 2024 fanden allesamt keinen ausreichenden Konsens; insbesondere try stieß wegen verdecktem Kontrollfluss auf starken Widerstand
  • Nach dem Go-Vorschlagsprozess werden Vorschläge ohne allgemeinen Konsens üblicherweise abgelehnt, und selbst unter erfahrenen Mitgliedern des Go-Teams bei Google gibt es derzeit keine Einstimmigkeit über die beste Richtung
  • Befürworter des Status quo argumentieren, Go habe bereits eine funktionierende Art der Fehlerbehandlung, und neue Syntax würde hohe Kosten für Code-Stil, Debugging, Dokumentation, Tools und bestehenden Code verursachen
  • Das Go-Team schließt offene und neue Vorschläge, deren Hauptziel Syntax für Fehlerbehandlung ist, ohne weitere Untersuchung und konzentriert sich auf andere Verbesserungsmöglichkeiten, bis ein klareres Problemverständnis entsteht

Die alte Unzufriedenheit mit if err != nil

  • Einer der am längsten bestehenden Kritikpunkte an Go ist die Ausführlichkeit von Fehlerbehandlungscode
  • Das typische Muster sieht so aus
x, err := call()
if err != nil {
    // handle err
}
  • In Programmen mit vielen API-Aufrufen, die Fehler einfach zurückgeben, kann if err != nil den Rest des Codes dominieren
  • In der Beispielfunktion printSum bestehen von 10 Zeilen Funktionsrumpf nur 4 Zeilen aus dem, was wie eigentliche Arbeit aussieht – Aufrufe, Ausgabe und Rückgabe eingeschlossen –, während die übrigen 6 Zeilen wie Rauschen wirken
  • In den jährlichen Go-Nutzerumfragen gehörte Fehlerbehandlung über Jahre zu den wichtigsten Beschwerden; eine Zeit lang lag das Fehlen von Generics davor, doch nachdem Go Generics unterstützte, kehrte die Fehlerbehandlung wieder an die Spitze der Kritikpunkte zurück

Drei wichtige Syntaxvorschläge

  • Der erste explizite Versuch des Go-Teams begann 2018 im Rahmen der Go-2-Bemühungen, als Russ Cox das Problem offiziell zusammenfasste
  • Der Designentwurf von Marcel van Lohuizen basierte auf einem check- und handle-Mechanismus und enthielt auch Analysen anderer Sprachansätze und Alternativen
func printSum(a, b string) error {
    handle err { return err }
    x := check strconv.Atoi(a)
    y := check strconv.Atoi(b)
    fmt.Println("result:", x + y)
    return nil
}
  • Der check/handle-Ansatz wurde als zu komplex bewertet, und 2019 folgte ein einfacherer try-Vorschlag
    • Das check-ähnliche Schlüsselwort wurde zur eingebauten Funktion try
    • Der handle-Teil entfiel
    • Das Tool tryhard wurde erstellt, um bestehenden Fehlerbehandlungscode in die try-Form umzuwandeln
    • Das zugehörige GitHub-Issue wurde mit fast 900 Kommentaren intensiv diskutiert
func printSum(a, b string) error {
    // use a defer statement to augment errors before returning
    x := try(strconv.Atoi(a))
    y := try(strconv.Atoi(b))
    fmt.Println("result:", x + y)
    return nil
}
  • try beeinflusste den Kontrollfluss, indem es bei einem Fehler aus der umschließenden Funktion zurückkehrte; da eine Rückgabe auch tief in verschachtelten Ausdrücken erfolgen konnte, war das für viele Nutzer schwer akzeptabel
  • Damals wäre die Einführung eines neuen Schlüsselworts möglicherweise besser gewesen; heute lässt sich die Sprachversion über go.mod-Dateien und dateispezifische Direktiven feiner steuern
  • Der jüngste Vorschlag von Jimmy Frasche geht zurück zum ursprünglichen check/handle-Design und adressiert dabei einige seiner Nachteile

Prozessreflexion nach try und der ?-Vorschlag

  • Nach dem try-Vorschlag reflektierte Russ Cox den Vorschlagsprozess in der Reihe „Thinking about the Go Proposal Process“
  • „Go Proposal Process: Large Changes“ kam zu dem Schluss, dass try eher ein zweiter Designentwurf hätte sein sollen und kein Vorschlag mit Implementierungszeitplan
  • In den folgenden Jahren trieb das Go-Team keine Syntaxänderungen für die Fehlerbehandlung voran, während aus der Community weiterhin ähnliche, interessante, schwer verständliche oder nicht praktikable Vorschläge eingingen
  • Ian Lance Taylor erstellte ein Umbrella-Issue, das den Stand der Vorschläge zur Verbesserung der Fehlerbehandlung zusammenfasst, und es entstand auch ein Go-Wiki, das entsprechendes Feedback und Diskussionen sammelt
  • Sean K. H. Liaos „go error handling proposals“ verfolgt viele Fehlerbehandlungsvorschläge aus mehreren Jahren
  • Als die Unzufriedenheit anhielt, veröffentlichte Ian Lance Taylor 2024 einen Vorschlag, mit ? Boilerplate bei der Fehlerbehandlung zu reduzieren
    • Die Schreibweise war vom ?-Operator in Rust entlehnt
    • In einer kleinen informellen Nutzerstudie errieten die meisten Teilnehmenden die Bedeutung von Go-Code mit ? korrekt
    • Außerdem entstanden ein Tool zur Umwandlung normalen Go-Codes in die neue Syntax und ein Compiler-Prototyp
func printSum(a, b string) error {
    x := strconv.Atoi(a) ?
    y := strconv.Atoi(b) ?
    fmt.Println("result:", x + y)
    return nil
}
  • Auch dieser Vorschlag wurde rasch von vielen Kommentaren und präferenzbasierten Detailänderungsvorschlägen überflutet; Ian schloss den Vorschlag und verschob den Inhalt in eine Discussion
  • Eine leicht überarbeitete Version erhielt etwas positivere Reaktionen, aber keine breite Unterstützung

Warum man jetzt innehalten will

  • Das Go-Team ist zu dem Schluss gekommen, dass es auf absehbare Zeit aufhören sollte, das Syntaxproblem der Fehlerbehandlung lösen zu wollen
  • Der Vorschlagsprozess stützt diese Entscheidung
    • Ziel des Vorschlagsprozesses ist es, rechtzeitig allgemeinen Konsens über ein Ergebnis zu erreichen
    • Wenn in der Diskussion im Issue-Tracker kein allgemeiner Konsens gefunden wird, wird ein Vorschlag üblicherweise abgelehnt
    • Wenn weder Konsens noch ein nächster Schritt gefunden werden, prüfen die Go Architects die Diskussion und versuchen, intern Einigkeit zu erzielen
  • Kein Vorschlag zur Fehlerbehandlung erhielt auch nur annähernd konsensfähige Unterstützung, und alle wurden abgelehnt
  • Selbst die erfahrenen Mitglieder des Go-Teams bei Google sind derzeit nicht einstimmig, was die beste Vorgehensweise betrifft; ohne starken Konsens kann man vernünftigerweise nicht fortfahren

Argumente für Status quo und Veränderung

  • Für den Status quo sprechen praktische Gründe wie die Reife von Go und die Kosten für das Ökosystem
    • Hätte Go früh syntaktischen Zucker speziell für Fehlerbehandlung eingeführt, gäbe es heute wohl weniger Streit; doch Go ist 15 Jahre alt und hat bereits eine funktionierende Fehlerbehandlung
    • Selbst wenn heute eine perfekte Lösung gefunden würde, könnte sich die Situation nur so verschieben, dass statt der Änderungsbefürworter nun die Anhänger des Status quo unzufrieden wären
    • Generics müssen Nutzer nicht zwingend selbst direkt verwenden, doch neue Syntax für Fehlerbehandlung könnte Code, der sie nicht nutzt, unidiomatisch wirken lassen, sodass sie faktisch von den meisten verwendet werden müsste
    • Das Hinzufügen neuer Syntax könnte auch mit der Go-Designregel kollidieren, nicht mehrere Wege für dieselbe Sache anzubieten
  • Die Redeclaration-Funktion der kurzen Variablendeklaration := wurde eingeführt, um ein durch Fehlerbehandlung entstandenes Problem zu lösen
    • Ohne Redeclaration wären bei aufeinanderfolgenden Fehlerprüfungen unterschiedliche err-Namen oder separate Variablendeklarationen nötig gewesen
    • Hätte es damals bessere Syntaxunterstützung für Fehlerbehandlung gegeben, wären die Redeclaration-Regeln und die damit verbundene Komplexität womöglich nicht nötig gewesen
  • Wenn Fehler angemessen angereichert und behandelt werden, nimmt der Anteil einfacher Wiederholungen ab
    • In Nutzerumfragen gibt es wiederholt Rückmeldungen, dass Fehler keinen Stacktrace enthalten
    • Es ist möglich, mit Hilfsfunktionen angereicherte Fehler zu erzeugen und zurückzugeben
    • Wenn man wie bei fmt.Errorf("invalid integer: %q", a) Informationen zum Eingabewert hinzufügt, wird der relative Anteil der Boilerplate kleiner
  • Auch Funktionen der Standardbibliothek können Boilerplate bei der Fehlerbehandlung reduzieren
    • Das entspricht der Richtung von Rob Pikes „Errors are values“
    • In manchen Fällen lassen sich mit cmp.Or mehrere Fehler auf einmal behandeln
  • Schreiben, Lesen und Debuggen sind unterschiedliche Tätigkeiten
    • Wiederholte Fehlerprüfungen zu schreiben ist mühsam, aber IDEs und LLM-gestützte Codevervollständigung können einfache Fehlerprüfungen leicht erzeugen
    • Beim Lesen fällt die Ausführlichkeit stärker auf; eine IDE könnte einen Toggle anbieten, um Fehlerbehandlungscode auszublenden
    • Beim Debuggen erleichtert eine vorhandene separate if-Anweisung das Hinzufügen von println oder das Setzen von Breakpoints
    • Wenn Fehlerbehandlung hinter check, try oder ? verborgen ist, muss man sie womöglich oft wieder in if-Anweisungen zurückverwandeln; dieser Prozess kann das Debugging verkomplizieren oder subtile Bugs erzeugen
  • Sprachänderungen verursachen nicht nur Design- und Implementierungsaufwand, sondern auch Kosten für Änderungen an bestehendem Code, Aktualisierung der Dokumentation und Anpassung von Tools
    • Das Go-Team ist relativ klein und hat viele andere Prioritäten zu bearbeiten
    • Prioritäten und Teamgröße können sich ändern
  • Einige Go-Nutzer, die das Go-Team auf der Google Cloud Next 2025 traf, sprachen sich klar dagegen aus, die Sprache für bessere Fehlerbehandlung zu ändern
    • Sie sagten, wenn man gerade aus anderen Sprachen zu Go wechselt, falle das Fehlen spezieller Fehlerbehandlungssyntax am stärksten auf; sobald man idiomatischeren Go-Code schreibe, werde es weniger wichtig
    • Diese Stichprobe ist nicht groß genug, um repräsentativ zu sein, könnte aber eine andere Gruppe sein als die Menschen, die man auf GitHub sieht
  • Auch die Argumente für Veränderung bleiben gültig
    • Das Fehlen besserer Unterstützung für Fehlerbehandlung bleibt einer der wichtigsten Kritikpunkte in Nutzerumfragen
    • Ein Ansatz, der sich nur auf die Reduzierung der Zeichenzahl konzentriert, könnte in die falsche Richtung gehen
    • Wenn grundlegende Fehlerbehandlung per Schlüsselwort gut sichtbar gemacht und zugleich err != nil-Boilerplate entfernt würde, ließe sich in Code-Reviews leichter erkennen, ob Fehler behandelt werden
    • Ob der Kern des Problems einfach syntaktische Ausführlichkeit ist oder die Ausführlichkeit guter Fehlerbehandlung, die für APIs sowie Entwickler und Endnutzer aussagekräftige Fehler konstruiert, ist noch nicht ausreichend bekannt

Entscheidung des Go-Teams

  • Bislang hat kein Versuch, Fehlerbehandlung anzugehen, genügend Schwung entwickelt
  • Das Go-Team sieht kein gemeinsames Problemverständnis und stellt fest, dass nicht einmal alle darin übereinstimmen, ob es überhaupt ein Problem gibt
  • Auf absehbare Zeit werden keine syntaktischen Sprachänderungen für Fehlerbehandlung vorangetrieben
  • Offene Vorschläge und künftig eingehende Vorschläge, deren Hauptziel Syntax für Fehlerbehandlung ist, sollen ohne weitere Untersuchung geschlossen werden
  • Die Erkundungen und Diskussionen der Community haben zwar nicht zu Syntaxänderungen für Fehlerbehandlung geführt, aber zu mehreren Verbesserungen der Go-Sprache und ihrer Prozesse

1 Kommentare

 
GN⁺ 2025-06-04
Meinungen auf Hacker News
  • Wenn man dem Go-Team leichtfertig Vorschläge im Sinne von „So hätte man es machen sollen“ hinwerfen möchte, sollte man sich zuerst die im Artikel verlinkte Wiki-Seite https://go.dev/wiki/Go2ErrorHandlingFeedback und die GitHub-Issue-Suche https://github.com/golang/go/issues?q=+is%3Aissue+label%3Aerror-handling ansehen.
    Was man gerade vorschlagen will, ist mit ziemlicher Sicherheit nicht neu, und vieles davon wurde vermutlich bereits gründlich geprüft.
    Ich finde diesen ehrlichen Ansatz des Go-Teams gut und nutze Go weiterhin jeden Tag gern bei der Arbeit.

    • Im Design-Entwurf, der die Grundlage des Feedbacks bildete, kommen C++, Rust und Swift vor, aber in dem umfangreichen verlinkten Feedback-Dokument konnte ich keine Ansätze wie die in Haskell/Scala/OCaml verwendete do-Notation, for-Comprehensions oder monadic-let finden.
      Auch auf einigen Seiten der GitHub-Issues mit den meisten Kommentaren war nichts Ähnliches zu sehen, und es ist übertrieben anzunehmen, das Go-Team bestehe aus Zauberern des Sprachdesigns und habe die Lösungen, die Leute hier beiläufig einwerfen, selbstverständlich geprüft.
      Das Go-Team hat denselben Fehler wie Java gemacht, nämlich eine statisch typisierte Sprache ohne parametrische Polymorphie zu bauen; genau darin liegt auch die Wurzel dieses Fehlerbehandlungsproblems, und es wirkt so, als würden sie die Hände heben und es nicht korrigieren.
    • Es ist erstaunlich, dass wirklich kluge und erfahrene Leute diese Seite geschrieben und jahrelang diskutiert haben, dort aber nirgends die Haskell-Lösung mit Maybe/Either-Monaden und do-Notation über den bind-Operator auftaucht.
      Das mag hochtrabend und einschüchternd klingen, ist aber eine elegante, funktional reine Methode, Fehler bis zu der Stelle weiterzureichen, an der sie behandelt werden können, und sie dabei nicht in Vergessenheit geraten zu lassen.
      Für Leute, die Haskell-Code schreiben, ist dieses Muster so tief verankert, dass ich nicht verstehe, dass es in der Go-Community niemanden gab, der es kannte und mochte.
      Danke für die Seite selbst und die Links, aber es fühlt sich verwirrend an, dass Menschen, denen ihre Sprache so wichtig ist, eine derart gut etablierte Lösung übersprungen haben.
    • Die Antwort steht sicher schon irgendwo, aber ich frage mich, warum das gerade in Go ein besonders schwieriges Problem ist.
      Fast jede Sprache hat auf ihre Weise einen besseren Ansatz; ich würde gern wissen, ob Go sich einfach nicht entscheiden kann oder nicht alle zufriedenstellen kann, oder ob es konkrete Gründe in der Sprache Go selbst gibt, weshalb die Lösungen anderer Sprachen nicht passen.
    • Ein häufiges Muster bei Go-Kritik ist, dass vergleichsweise amateurhafte Leute annehmen, die Leute, die Go bauen, verstünden weniger von Programmiersprachen als sie selbst.
      In Wirklichkeit wissen sie in fast allen Fällen deutlich mehr.
      Amateure glauben naiv, die Sprache mit den meisten hineingestopften Features sei die beste, erst recht, wenn Features nach ihrem eigenen Geschmack dabei sind.
      Das ist ungefähr so, als würde jemand, der gerade erst mit dem Messermachen angefangen hat, ein japanisches Kochmesser sehen, es für unzureichend halten und denken, es wäre besser, einen 3D-gedruckten Griff mit Fingermulden, Geheimfach, Feuerzeug und Bluetooth-Lautsprecher daran zu bauen.
    • Es ist seltsam, etwas immer noch Wiki zu nennen, obwohl man inzwischen eine Genehmigung braucht, um es zu bearbeiten.
  • Man erstellt eine Checkbox-Liste, diskutiert jeden Punkt und füllt sie aus; solange kein fataler semantischer Fehler oder keine Soundness-Lücke gefunden wird, nimmt man die Punkte nicht wieder heraus.
    Wenn alles ausgefüllt ist, implementiert man es, und die Leute, die darüber gestritten haben, ob man .await, /await oder .await!() schreiben soll, verschwinden wieder.
    Rust funktioniert auf diese Weise; manche Issues verzögern sich zwar über zehn Jahre, aber am Ende werden die Punkte abgehakt und im aktuellen Nightly stabilisiert.
    Wenn Go ein einzelnes Problem, auf das alle sofort stoßen, nicht lösen kann, obwohl es mehrere ausgereifte Vorschläge gibt, nur weil man sich nicht für einen davon entscheiden kann und darauf wartet, dass das Bikeshedding aufhört, dann ist dieses Verfahren eine Komödie.

    • So etwas wie „mehrere fertige, perfekte Vorschläge“ gibt es nicht.
    • Genau so bekommt Rust den Ruf, wegen Design per Komitee eine hässlich zu lesende Sprache mit inkonsistenter Syntax zu sein.
    • Eine Programmiersprache ist ein entworfenes System und muss als Ganzes Sinn ergeben.
      Sie ist keine Sammlung von Features, die einfach hinzugefügt werden, sobald sie Checkbox-Anforderungen erfüllen.
    • Ich würde mir gern eine Erinnerung für in 25 Jahren setzen.
      Wird Rust so ein Durcheinander geworden sein wie C++? Wird Go eine zeitlose Sprache geblieben sein, so wie zum Zeitpunkt seiner Veröffentlichung?
    • „Go kann ein einzelnes Problem nicht lösen, das alle sofort erleben“ ist eine seltsame Aussage.
      In der Umfrage nannten 13 % Fehlerbehandlung, und es gibt auch Leute, die den derzeitigen Ansatz bevorzugen.
      https://go.dev/blog/survey2024-h1-results
  • Ich habe früher einmal eine ungewöhnliche Go-Funktion geschrieben, bei der erwartet wurde, dass eine innere Funktion einen Fehler zurückgibt.
    Wenn die innere Funktion also keinen Fehler zurückgab, musste die äußere Funktion einen Fehler zurückgeben und etwas anderes tun; wenn die innere Funktion einen Fehler zurückgab, musste sie nil zurückgeben.
    Kurz gesagt: Ich musste nicht if err != nil { ... }, sondern if err == nil { // return an error } schreiben, habe aber aus Gewohnheit Ersteres geschrieben, und das Debugging hat ziemlich lange gedauert.
    Der Grund war, dass ich gegenüber if err != nil so abgestumpft war, dass mein Gehirn die Möglichkeit, dass diese Konstruktion dort gar nicht stehen sollte, überhaupt nicht in Betracht zog.
    Deshalb denke ich, dass häufige Ausdrücke syntaktischen Zucker brauchen. Wenn der Unterschied zwischen dem extrem häufigen if err != nil und dem seltenen if err == nil deutlicher gewesen wäre, hätte das tatsächlich geholfen.

    • Jedes Mal, wenn ich if err == nil verwende, schreibe ich einen Kommentar // inverted dazu, damit es auffällt.
      Schön wäre es, wenn das auf Sprachebene behandelt würde, aber ich teile es zumindest als Methode, es sichtbarer zu machen.
    • Natürlich kann if fruit != "Apple" { ... } dieselbe Situation erzeugen.
      Ich frage mich, ob es dafür eine allgemeine Lösung gibt; das nur als Problem der Fehlerbehandlung zu sehen, scheint etwas am Ziel vorbeizugehen.
      An Fehlern ist nichts Besonderes oder Einzigartiges; sie sind nur ein Zustand wie jeder andere.
    • Das ist eher ein Argument gegen eine Syntaxänderung.
      Wenn ein häufiges Muster if err == nil { return ... } entsteht, liegt dann eben dieses überall im Code herum.
      Die aktuelle Lösung ist in Ordnung, und offenbar mögen sie vor allem Leute nicht, die gerade erst mit Go anfangen oder noch Anfänger sind.
      In meinem Umfeld mag man die „umständliche“ Fehlerbehandlung, weil sie explizit, eindeutig und gut lesbar ist.
    • Um mal des Teufels Advokat zu spielen: IDE und Schriftart könnten im Go-Syntaxmodus nur if err != nil wie ein einzelnes, kleines Ligatur-Symbol hervorheben oder im Hintergrund abgeschwächt rendern.
      Dann würden Formen, die sich exakt von dieser Zeichenkette unterscheiden, wie if err == nil, eher auffallen.
    • Guter Punkt. Das ließe sich im Editor vielleicht auch mit einer eingeklappten Darstellung wie if err … { lösen.
  • Mir gefällt Go’s explizite Fehlerbehandlung.
    Eine Funktion ist entweder immer erfolgreich oder kann erfolgreich sein oder fehlschlagen. Funktionen, die immer erfolgreich sind, sind einfach; wenn eine Funktion, die fehlschlagen kann, fehlschlägt, kann der äußere Code nicht im Erfolgszustand weitermachen und muss das behandeln.
    An dieser Stelle unterscheiden sich Sprachen. Viele Sprachen werfen Exceptions, reichen sie nach oben weiter, bis jemand sie explizit fängt, und liefern eine Art Stacktrace.
    An Go gefällt mir, dass man beim Schreiben des Codes immer eine Wahl treffen muss: den Fehler ignorieren und fortfahren (foo, _ := doSomething()), ohne aussagekräftige Information früh zurückkehren (return nil, err), mit nützlichem Kontext früh zurückkehren oder den erhaltenen Fehler interpretieren und verzweigen.
    Wenn etwa in der Datenbank keine zu ändernde Zeile gefunden wird, kann die Service-Schicht einen not-found-Fehler zurückgeben, der in der API zu 404 wird; oder eine idempotente Delete-Funktion kann not found als Erfolg interpretieren.
    In Go 2 oder einer anderen Sprache fände ich statt nil-fähiger Tupel einen Result-Typ im Stil von Rust/Swift gut, dazu besser typisierte und aufzählbare Fehlertypen, statt immer direkt error zu verwenden.
    Fügt man Result aber über die idiomatische Tupel-Rückgabe von Go 1 hinzu, gibt es mehrere Wege, dasselbe zu tun, was Verwirrung und Spaltung erzeugt; deshalb passt es eher zu Go 2 oder einer neuen Sprache.

    • Meiner Erfahrung nach sollte die Fehlerbehandlungspolitik dem Aufrufer überlassen werden.
      Die unteren Schichten des Stacks wissen meistens nicht, was zu tun ist, und sollten Fehler daher nicht behandeln.
      Die Politik, einen Fehler zu behandeln, wird am Ende leicht zu der Politik, ihn zu wrappen und wieder den Stack hinauf zurückzugeben, und das wird zu erheblicher Fleißarbeit.
    • „Wenn eine Funktion fehlschlägt, muss dieser Fehlschlag behandelt werden“ ist genau der Punkt, an dem Go scheitert.
      Go erlaubt es, Fehler vollständig zu ignorieren, was zu Abstürzen führen kann.
      Ich verstehe nicht ganz, wie man die Anforderungen an robuste Software so genau benennen und zugleich Go’s Fehlerbehandlung mögen kann.
    • Ich wünschte, die Syntax von borgo[1] würde unverändert zur Sprache Go 2. Träumen darf man ja.
      [1]: https://borgo-lang.github.io/ | https://github.com/borgo-lang/borgo
    • Bei diesem Tempo wirkt Go2 wie ein Ideenlabor, das nie veröffentlicht werden wird.
    • Ich würde fragen wollen: im Vergleich womit ist das gut?
      Alle funktionalen Sprachen, viele moderne Sprachen wie Rust und sogar Java mit checked exceptions bieten das.
      In einer Sprache mit Generics lässt sich Go-artige „Fehlerbehandlung“ weitgehend nachbilden, und vermutlich kommt dabei besserer Code heraus.
      Wenn die Antwort JavaScript oder Python ist, ist das natürlich ein gängiges Vergleichsmuster.
  • Für Go ist das die richtige Entscheidung. Als ich Go zum ersten Mal kennengelernt habe, mochte ich die Fehlerbehandlung nicht, aber inzwischen mag ich sie wirklich sehr.
    Der Wandel hatte zwei Auslöser. Nachdem ich den Artikel https://go.dev/blog/errors-are-values gelesen hatte, habe ich die Perspektive „Fehler sind Werte“ richtig angenommen, und darauf aufbauend auch ein einigermaßen populäres Paket gebaut: https://github.com/stytchauth/sqx.
    Außerdem habe ich mich nach und nach daran gewöhnt, bei wirklich unsinnigen, ungültigen Zuständen panic(err) zu verwenden.
    Es gibt keinen Grund, den übergeordneten Code dazu zu zwingen, jeden abwegigen Zustand zu behandeln, für den er gar keinen sinnvollen Umgang hat; ein oder zwei gut platzierte panics können hunderte Fehlerprüfungen in einer Codebasis entfernen.
    Man kann zum Beispiel an die Frage denken, ob im ctx ein Standard-Logger vorhanden ist.

    • Das ist bedauerlich. Keines der beiden angeführten Argumente hat etwas damit zu tun, wie schlecht Go’s Fehlerbehandlung ist, und sie würde auch nicht schlechter, wenn man die Fehlerbehandlung verbessert.
      Im Gegenteil, sie würde sehr wahrscheinlich besser.
    • Sogar PHP hat dank Fehlerstufen und dem @-Operator, mit dem sich Fehler an der Aufrufstelle unterdrücken lassen, eine bessere Fehlerbehandlung.
      bash hat auch -e.
    • Mir gefällt der Go-Ansatz ebenfalls. Wenn ich dadurch genauer weiß, was passiert, nehme ich mehr Codezeilen in Kauf.
      Früher, als ich C# zum ersten Mal benutzt habe, hielt ich es für klug, den Ablauf von try/catch/finally, using, Verschachtelung, was passiert, wenn im catch ein Fehler auftritt, und was passiert, wenn er im finally auftritt, zu verstehen.
      Heute finde ich es besser, über solche Dinge nicht nachdenken zu müssen.
    • Auch Sum-Type-Fehler im Rust-Stil sind Werte.
  • Mir gefällt nicht, wie dieser Beitrag nahelegt, das Hauptproblem bei Go-Fehlerbehandlung sei, dass die Syntax zu ausführlich ist. Das ist mir ziemlich egal
    Wichtiger ist, dass Fehler stillschweigend verworfen oder versehentlich ignoriert werden können, dass das Ergebnis eines Funktionsaufrufs kein Wert ist und sich daher nicht einfach speichern oder weiterreichen lässt, und dass errors.Is nötig ist und das ganze „verschachtelte“ Fehlerkonzept ein seltsamer Runtime-Mechanismus ist, der nicht gut zum Typsystem passt
    Auch ein switch über Fehler ist schwierig, die Standardbibliothek verwendet Sentinel-Werte, und die Interaktion mit Generics ist schlecht, sodass Pakete wie errgroup nötig werden
    Habe ich noch etwas übersehen?

    • 90 % der Zeit, in der ich professionell mit Go arbeite, besteht darin, Testfälle zu erzwingen, nur um jeden Rückgabezweig für Fehler mit Statement Coverage abzudecken
      In einer Sprache mit Exceptions würde das niemand tun
    • Ich sehe nicht, dass dieser Beitrag irgendwo behauptet, „das Hauptproblem sei, dass die Syntax zu ausführlich ist“
      Man hat beschlossen, in absehbarer Zukunft keine weiteren Versuche zu unternehmen, die Syntax der Fehlerbehandlung zu ändern, und dadurch entsteht Aufmerksamkeit, um sich andere Probleme anzusehen, ob bei Fehlern oder bei anderen Themen
    • Man sollte sich daran erinnern, dass es unglaublich lange gedauert hat, bis Go überhaupt irgendeine Form von Generics unterstützt hat
      Die Entwicklung von Go ist gletscherhaft langsam, und für viele ist das kein Bug, sondern ein Feature
    • Stimme zu 100 % zu. Beide sind Googler, und es ist sehr schade, wieder einmal vom Go-Team enttäuscht zu werden
    • Punkt 1 stimme ich zu, aber mit Entwicklertools wie errcheck lässt sich das bis zu einem gewissen Grad abmildern: https://github.com/kisielk/errcheck?tab=readme-ov-file
  • Die Erklärung, Umfragefeedback wie „Fehler haben keine Stacktraces“ könne gelöst werden, indem Hilfsfunktionen angereicherte Fehler erzeugen und zurückgeben, ist lächerlich
    Es ist amüsant, Stacktraces manuell bereitzustellen wie in if err != nil { return fmt.Errorf("invalid integer: %q", a) } und das dann „Fehlerbehandlung“ zu nennen
    Nach der Definition des Go-Teams würden Exceptions Fehler also automatisch behandeln. Natürlich in Sprachen außer C++

    • Es ist lustig, dass Leute einen bildschirmfüllenden Stacktrace sehen und ihn klar und nützlich nennen
      Mag sein, aber braucht man wirklich all das? Und was ist mit den Logging-Kosten?
      Ein einzeiliger gewrappter Fehler, der Framework- und Runtime-Rauschen abschneidet, ist meiner Ansicht nach viel besser
      Wenn er gut gewrappt ist, lässt er sich sehr leicht suchen und meist effektiver nachverfolgen als ein Stacktrace
      In über 10 Jahren Vollzeit-Go habe ich nie die ausschweifenden Störgeräusche von Runtime-Funktionen oder Call Stacks gebraucht
  • Aus Sicht eines Elixir-Entwicklers wirkt das verrückt
    In Erlang/Elixir wird das normalerweise gelöst, indem eine Funktion ein Tupel {:ok, result} oder {:error, description_or_struct} zurückgibt
    Verwendet man dazu Elixirs with-Anweisung, kann man die Fehlerbehandlung unten bündeln, was viel lesbarer ist
    Go könnte ebenfalls etwas Entsprechendes zu einer with-Klausel hinzufügen, die Funktionen weiterlaufen lässt, solange der Fehler nil ist, und unten eine Klausel zur Fehlerbehandlung vorsieht

    • Nach den verfügbaren Anzeichen scheint es keinerlei Chance zu geben, dass Go eine with-Anweisung übernimmt
      Interessant ist, dass Go grundlegende und offensichtlich wertvolle Konstrukte wie Generics, Fehlerbehandlung und Paketverwaltung wegen fehlendem Community-Konsens sehr lange aufschiebt
      Generics brauchten 13 Jahre nach der Open-Source-Veröffentlichung, nach 16 Jahren gibt es immer noch keine Fehlerbehandlung, und die Paketverwaltung dauerte etwa 9 Jahre
      Überlegung hat ihren Wert, aber Releases haben ebenfalls ihren Wert. Die Leute, die 900 GitHub-Kommentare schreiben, werden am Ende trotzdem weiter Go verwenden, und etwas in die Sprache aufzunehmen, wäre vermutlich besser gewesen, als es immer weiter aufzuschieben
    • Gos mehrfache Rückgabewerte an sich wirken aus meiner Sicht seltsam
      Mit einer Funktion, die mehrere Rückgabetypen hat, kann man außer der Zuweisung an Variablen nicht viel anfangen
    • Haskell-Nutzer und Rust-Fans betrachten Sum Types als ihr Eigentum, und nachdem Leute ihre Kommentare und Beiträge lesen und ihnen glauben, schrecken sie vor Sum Types zurück, weil sie nicht in das furchterregende Hindley-Milner-Kaninchenloch fallen wollen
      In Erlang und Elixir sind sie jedoch völlig idiomatisch, ganz ohne Ballast
      Tatsächlich sind sie sogar viel mächtiger als in der ML-Familie, weil Sum Types dort offen sind
  • Ich habe diese Diskussion nicht im Detail verfolgt, aber ich verstehe nicht, warum man nicht einfach den Rust-Ansatz übernimmt.
    Seit Go Generics hat, ist das auch der Ansatz, den ich sofort ergänze.
    In dem verlinkten Beitrag sehe ich nur die Erklärung: „Rust hat nichts, was handle entspricht, und die Bequemlichkeit des Operators ? könnte dazu führen, dass angemessene Behandlung ausgelassen wird.“
    Aber ich verstehe nicht, warum bequem bedeuten sollte, Fehler zu ignorieren.
    Die Hälfte des Problems am Go-Ansatz ist, dass er für Ergebnisse nichts erzwingt und auch Fehlerprüfungen nur minimal erzwingt.
    x, err := strconv.Atoi("123"); fmt.Println("result:", x) führt zwar zu declared and not used: err, aber wenn man nach der zweiten Umwandlung err nicht prüft, kann das wegen des Default-Werts 0 von y unbemerkt problemlos weiterlaufen.
    Selbst if err != nil { } mit leerem Block kompiliert und läuft, ohne dass man erkennt, dass etwas schiefgelaufen ist.
    Wenn man den Rückgabewert zu einem Result macht, wird erzwungen, dass eine Entscheidung getroffen wird. Selbst wenn jemand großzügig ! verwendet oder Fehler bequem mit ? nach oben weiterreicht, ohne den Fehlerfall zu behandeln: Würde man dann auch panic verbieten?

    • Go hat keine Sum Types, daher kann es kein Result haben.
      Und wegen der seltsamen Fixierung darauf, dass jeder Typ einen festgelegten Zero Value haben muss, können auch keine Sum Types hinzugefügt werden.
    • Ich verstehe es so, dass, wenn ? bequem zu verwenden ist, niemand mehr Fehler wrappen würde.
      Eine sehr fragwürdige Logik.
      Man müsste ? von Anfang an so entwerfen, dass es Error Wrapping fördert.
    • Der Grund ist, dass nicht klar ist, was genau die äquivalente Form wäre, wenn man den Rust-Stil in Go einbaut.
      Wie sollte zum Beispiel das Gegenstück zu Rusts From in Go aussehen?
    • ? ist wenig sichtbar und versteckt einen Kontrollflusszweig innerhalb einer einzelnen Anweisung oder eines Ausdrucks.
      Das ist auch einer der Gründe, warum Go auf den ternären Operator verzichtet und sich für if-Anweisungen entschieden hat, bei denen jeder Zweig in einer eigenen Zeile steht.
      Breakpoints sind damit ebenfalls nicht leicht zu setzen, und es begünstigt, Fehler unverändert nach oben durchzureichen, statt sie anzureichern oder zu behandeln.
    • Ich dachte, := sei Deklaration und Zuweisung in einer einzelnen Anweisung. Wird in Zeile 5 des Beispiels err nicht erneut deklariert, sodass das neue err das bestehende err verdeckt?
      Dann müsste es doch mit declared and not used: err fehlschlagen, weil die neue Variable err nicht verwendet wird.
      Oder verhält sich := einfach wie eine normale Zuweisung, wenn die Variable bereits existiert?
  • Die Aussage „Dass Fehler keine Stack Traces haben, lässt sich lösen, indem Helper-Funktionen angereicherte Fehler erzeugen und zurückgeben“ ist viel zu optimistisch.
    Sprachen mit Stack Traces liefern das kostenlos, in Go muss man es jedes Mal selbst implementieren.
    Man selbst mag ein disziplinierter Entwickler sein, der immer Details anhängt, aber nicht alle Teammitglieder haben dieselbe Disziplin.
    Das Beste an einem Stack Trace ist, dass er den Aufrufpfad bis zum Fehler liefert.
    Wenn ein Fehler in einer Methode auftritt, die von mehreren Stellen aufgerufen wird, erkennt man mit einem Stack Trace sofort, welcher Pfad genommen wurde.
    Ich habe über viele Jahre hinweg in einer Art Sysadmin-/SRE-Rolle viele Probleme gelöst; bei vorhandenen Stack Traces waren die einfachen Fälle wegen der offensichtlichen Ursache in ein bis zwei Minuten erledigt.
    In Go wird selbst ein einfaches Problem zur Detektivarbeit und dauert länger, wenn jemand den Fehler nicht anreichert oder dieselbe Fehlermeldung wiederverwendet.