Go verfolgt im Vergleich zu Sprachen wie Java, C++ und JavaScript sowie Python eine relativ andere Vorstellung von Fehlern: Ein Fehler ist in Go ein Wert des integrierten Typs error, den eine Funktion normalerweise zusammen mit den übrigen Rückgabewerten an den Aufrufer zurückgibt. Dadurch werden die Prüfung und Behandlung des Fehlers zu einem sichtbaren Bestandteil des Programmpfads, anstatt sie in eine separate Ausnahmebehandlung zu verlagern.
Der JetBrains-Leitfaden, der ursprünglich vom Community-Mitwirkenden Christoph Berger verfasst und anschließend in den JetBrains-Go-Blog übertragen wurde, stellt eine Reihe von Techniken und Praktiken für den Umgang mit Fehlern bei Ein-/Ausgabe, Netzwerken, Datenvalidierung und weiteren Bereichen vor. Er wurde im August 2026 aktualisiert, um die neuesten Änderungen der Programmiersprache Go zu berücksichtigen.
Die Rückgabe ist der Ausgangspunkt
Wenn eine Funktion nicht über ausreichend Kontext verfügt, um ein Problem zu behandeln, sollte sie den Fehler an die aufrufende Funktion zurückgeben. Go platziert den Fehlerwert normalerweise an der letzten Stelle der Rückgabeliste; eine Funktion wie ReadFile() gibt beispielsweise den Dateiinhalt und einen Fehler zurück, dessen Wert bei Erfolg nil und bei einem Fehlschlag ein anderer Wert ist.
Der Leitfaden erklärt, dass der Aufrufer den Fehler sofort prüfen sollte, anstatt ihn zu ignorieren. Wenn die Funktion eine Ressource wie eine Datei oder eine Netzwerkverbindung geöffnet hat, sollte defer verwendet werden, um sie beim Verlassen zu bereinigen – allerdings erst, nachdem sichergestellt wurde, dass das Öffnen erfolgreich war, da der Versuch, eine ungültige Ressource zu schließen, ein zusätzliches Problem verursachen kann.
Kontext hinzufügen, ohne die Fehlerstruktur zu beschädigen
Ein Fehler kann eine Kette von Funktionen durchlaufen, bevor er behandelt oder protokolliert wird. Während dieses Übergangs kann jede Funktion nützliche Informationen hinzufügen, etwa die fehlgeschlagene Operation oder den gerade bearbeiteten Pfad. Wird der Fehler jedoch durch Zusammenfügen von err.Error() in Text umgewandelt, gehen die Kette und die typbezogene Struktur des Fehlers verloren.
Der richtige Ansatz besteht darin, fmt.Errorf() mit dem Verb %w zu verwenden, um den Fehler zu wrappen und dabei den ursprünglichen Fehler zu erhalten. Dadurch können später mit errors.Unwrap() einzelne Ebenen erreicht oder mit errors.Is() und errors.As() bestimmte Fehler innerhalb der Wrapping-Kette geprüft werden. Go 1.26 fügt die generische Funktion errors.AsType() als typsichere Alternative zu As() hinzu. Sie gibt den passenden Fehler und einen booleschen Wert zurück und ermöglicht es dem Compiler, Typfehler zu erkennen, die bei der älteren Methode erst zur Laufzeit auftreten könnten.
Mehrere Fehler und abgebrochener Kontext
Fehler bilden nicht immer eine lineare Kette. Bei der Verarbeitung einer Gruppe von Dateien können beispielsweise einige Operationen erfolgreich sein und andere fehlschlagen. Das Standardpaket stellt mit errors.Join(), verfügbar seit Go 1.20, eine Möglichkeit bereit, mehrere Fehler in einem einzigen Wert zusammenzuführen und dabei den erfolgreich verarbeiteten Inhalt zu erhalten.
Der Leitfaden weist darauf hin, dass errors.Unwrap() einen einzelnen Wert zurückgibt und daher bei einem zusammengeführten Fehler nil liefert. Um auf die zusammengeführten Fehler zuzugreifen, muss geprüft werden, ob der Wert die Schnittstelle implementiert, die Unwrap() []error bereitstellt. Seit Go 1.20 kann außerdem context.WithCancelCause() verwendet werden, um die Abbruchursache eines Kontexts mit einem benutzerdefinierten Grund zu verknüpfen und diesen Grund anschließend über context.Cause(ctx) abzurufen, statt sich auf den allgemeinen Wert context.Canceled zu beschränken.
Wann ist panic angemessen?
panic und recover sollten kein üblicher Ersatz für die Fehlerprüfung sein. Erwartbare Fehler wie ungültige Benutzereingaben, fehlende Dateien oder Netzwerk-Timeouts sollten behandelt und über den normalen Rückgabepfad zurückgegeben werden.
Ein Panic ist angemessen, wenn das Problem unerwartet ist und keine sinnvolle Behandlung dafür existiert, etwa beim Fehlschlagen der Kompilierung eines konstanten regulären Ausdrucks, dessen Gültigkeit zuvor hätte geprüft werden müssen. In HTTP-Servern kann die Anwendung recover() innerhalb einer verzögerten Funktion verwenden, um zu verhindern, dass der Absturz der Verarbeitung der aktuellen Anfrage andere Anfragen beeinträchtigt, sofern dies möglich ist. Fälle wie ein erschöpfter Speicher lassen der Anwendung möglicherweise keine praktikable Möglichkeit zur Fortsetzung.
Was ist für Entwickler praktisch wichtig?
- Fehler nicht ignorieren: Das Zuweisen des Fehlerwerts an den Blank Identifier oder das Verwerfen des einzigen Rückgabewerts kann die Entdeckung des Problems verzögern und die spätere Diagnose seiner Auswirkungen erschweren.
- Am richtigen Ort protokollieren: Eine Funktion sollte den Fehler behandeln oder an den Aufrufer zurückgeben. Der Leitfaden empfiehlt, dass Bibliotheken nicht eigenständig protokollieren, da ihre Nutzer möglicherweise andere Protokolle oder Ausgabeziele bevorzugen.
- Geeignete Typen verwenden: Benutzerdefinierte Fehlertypen können zusätzliche Informationen enthalten, wie fs.PathError, das die Operation, den Pfad und den internen Fehler bereitstellt.
- Netzwerkfehler unterscheiden: net.OpError ermöglicht die Prüfung der Art des Verbindungsfehlers. Ein temporärer Fehler kann eine Wiederholung oder eine Strategie wie exponentielles Backoff erlauben.
- Die Byteanzahl bei Ein-/Ausgabeoperationen nutzen: Die Schnittstelle io.Reader gibt zusammen mit dem Fehler die Anzahl der verarbeiteten Bytes zurück, was helfen kann, die Operation fortzusetzen, statt alle Daten erneut zu verarbeiten.
- Auf io.EOF achten: Dieser Wert kennzeichnet gemäß der Semantik des Pakets das erfolgreiche Ende eines Lesestroms und ist nicht unbedingt ein Fehlschlag, der als gewöhnlicher Fehler protokolliert werden muss.
- log.Fatal() nicht unbedacht verwenden: Die Funktion ruft os.Exit() auf, wodurch verzögerte Funktionen übersprungen werden. Daher empfiehlt der Leitfaden, ihre Verwendung zu begrenzen oder os.Exit() in der Funktion main() einzusetzen.
Die redaktionelle Schlussfolgerung von certi.news lautet, dass die Klarheit des Fehlerpfads ein Bestandteil des Programmdesigns und nicht lediglich ein ausführlicher Schreibstil ist. Strukturiertes Wrapping, geeignete Typen und verantwortungsvolle Protokollierung machen die Diagnose umsetzbar, während die Abhängigkeit von panic, allgemeinen Meldungen oder dem Verwerfen von Fehlern die Informationen verbirgt, die Entwickler später benötigen. Die praktische Abwägung bleibt vom Anwendungskontext abhängig: Ein Anforderungsserver benötigt möglicherweise eine begrenzte Wiederherstellung, während ein nicht behebbarer Fehler ein Beenden und einen Neustart erforderlich machen kann.