Hallo Habrowsk-Bürger!
Der Golang Developer- Kurs beginnt bereits heute bei OTUS und wir betrachten dies als eine großartige Gelegenheit, um einen weiteren nützlichen Beitrag zu diesem Thema zu veröffentlichen. Lassen Sie uns heute über Go's Herangehensweise an Fehler sprechen. Fangen wir an!

Beherrschen Sie die pragmatische Fehlerbehandlung in Ihrem Go-Code
Dieser Beitrag ist Teil der Before Getting Started- Reihe , in der wir die Welt von Golang erkunden und Tipps und Ideen austauschen, die Sie beim Schreiben von Code in Go kennen sollten, damit Sie Ihre eigenen Probleme nicht ausfüllen müssen.Ich gehe davon aus, dass Sie bereits über grundlegende Erfahrungen mit Go verfügen. Wenn Sie jedoch das Gefühl haben, irgendwann auf ein unbekanntes Diskussionsmaterial gestoßen zu sein, zögern Sie nicht, eine Pause einzulegen, das Thema zu erkunden und zurückzukehren.
Jetzt, wo wir unseren Weg frei gemacht haben, lass uns gehen!
Der Ansatz von Go zur Fehlerbehandlung ist eine der umstrittensten und am meisten missbrauchten Funktionen. In diesem Artikel lernen Sie Go's Herangehensweise an Fehler kennen und verstehen, wie sie „unter der Haube“ funktionieren. Sie lernen einige verschiedene Ansätze kennen, überprüfen den Go-Quellcode und die Standardbibliothek, um herauszufinden, wie Fehler behandelt werden und wie mit ihnen gearbeitet wird. Sie erfahren, warum Typzusicherungen eine wichtige Rolle bei der Behandlung spielen, und Sie werden bevorstehende Änderungen an der Fehlerbehandlung sehen, die Sie in Go 2 einführen möchten.

Eintrag
Das Wichtigste zuerst: Fehler in Go sind keine Ausnahme.
Dave Cheney hat einen
epischen Blog-Beitrag darüber geschrieben, daher verweise ich Sie darauf und fasse zusammen: In anderen Sprachen können Sie nicht sicher sein, ob eine Funktion eine Ausnahme auslösen kann oder nicht. Anstatt Ausnahmen auszulösen, unterstützen Go-Funktionen
mehrere Rückgabewerte. Konventionell wird diese Funktion normalerweise verwendet, um das Ergebnis einer Funktion zusammen mit einer Fehlervariablen zurückzugeben.

Wenn Ihre Funktion aus irgendeinem Grund fehlschlägt, sollten Sie wahrscheinlich den zuvor deklarierten
error
. Konventionell signalisiert die Rückgabe eines Fehlers dem Anrufer das Problem, und die Rückgabe von Null wird nicht als Fehler angesehen. Auf diese Weise wird dem Anrufer klar, dass ein Problem aufgetreten ist, und er muss sich damit befassen: Wer auch immer Ihre Funktion aufruft, weiß, dass er sich nicht auf das Ergebnis verlassen sollte, bevor er nach einem Fehler sucht. Wenn der Fehler nicht gleich Null ist, muss er ihn überprüfen und verarbeiten (protokollieren, zurückgeben, warten, einen Wiederholungs- / Bereinigungsmechanismus aufrufen usw.).
(3 // Fehlerbehandlung
5 // Fortsetzung)Diese Snippets sind in Go sehr verbreitet, und einige betrachten sie als Boilerplate-Code. Der Compiler behandelt nicht verwendete Variablen als Kompilierungsfehler. Wenn Sie also nicht nach Fehlern suchen, müssen Sie sie
einem leeren Bezeichner zuweisen. Egal wie bequem es auch sein mag, Fehler sollten nicht ignoriert werden.
(4 // Das Ignorieren von Fehlern ist unsicher, und Sie sollten sich nicht auf das Ergebnis verlassen, bevor Sie nach Fehlern suchen.)Das Ergebnis kann erst nach Überprüfung auf Fehler als vertrauenswürdig eingestuft werdenDie Fehlerrückgabe zusammen mit den Ergebnissen zusammen mit dem strengen Go-Typ-System erschwert das Schreiben von markiertem Code erheblich. Sie sollten immer davon ausgehen, dass der Wert einer Funktion beschädigt ist, es sei denn, Sie haben den zurückgegebenen Fehler überprüft. Wenn Sie den Fehler einem leeren Bezeichner zuweisen, ignorieren Sie ausdrücklich, dass der Wert Ihrer Funktion möglicherweise beschädigt ist.
Der leere Ausweis ist dunkel und voller Schrecken.Go hat
panic
und
recover
, die auch in
einem anderen detaillierten Go-Blog-Beitrag beschrieben werden . Sie sollen jedoch keine Ausnahmen simulieren. Laut Dave
"Wenn Sie in Go in Panik geraten, geraten Sie wirklich in Panik: Dies ist nicht das Problem eines anderen, dies ist bereits ein Spieler." Sie sind tödlich und führen zu einem Absturz in Ihrem Programm. Rob Pike prägte das Sprichwort „Keine Panik“, das für sich selbst spricht: Sie sollten diese Mechanismen wahrscheinlich vermeiden und stattdessen Fehler zurückgeben.
"Fehler sind die Bedeutungen."
"Suchen Sie nicht nur nach Fehlern, sondern gehen Sie elegant damit um."
"Keine Panik"
alle Sprüche von Rob Pike
Unter der Haube
FehlerschnittstelleUnter der Haube ist der Fehlertyp eine
einfache Schnittstelle mit einer Methode . Wenn Sie nicht damit vertraut sind, empfehle ich dringend,
diesen Beitrag im offiziellen Go-Blog anzuzeigen.
Fehlerschnittstelle von der QuelleEs ist nicht schwierig, eigene Fehler zu machen. Es gibt verschiedene Ansätze für Benutzerstrukturen, die die
string
Methode
Error()
implementieren. Jede Struktur, die diese einzelne Methode implementiert, wird als gültiger Fehlerwert betrachtet und kann als solche zurückgegeben werden.
Schauen wir uns einige dieser Ansätze an.
Integrierte errorString-Struktur
Die am häufigsten verwendete und am weitesten verbreitete Implementierung der Fehlerschnittstelle ist die integrierte
errorString
Struktur. Dies ist die einfachste Implementierung, die Sie sich vorstellen können.

Quelle:
Go QuellcodeSie können die vereinfachte Implementierung
hier sehen . Es enthält lediglich eine
string
, die von der
Error
Methode zurückgegeben wird. Dieser Zeichenfolgenfehler kann von uns anhand einiger Daten formatiert werden,
fmt.Sprintf
. B. mit
fmt.Sprintf
. Abgesehen davon enthält es jedoch keine weiteren Funktionen. Wenn Sie
error.New oder
fmt.Errorf angewendet
haben , haben Sie es bereits
verwendet .
(13 // Ausgabe :)versuche esgithub.com/pkg/errors
Ein weiteres einfaches Beispiel ist das Paket
pkg / Errors . Nicht zu verwechseln mit dem integrierten
errors
, das Sie zuvor kennengelernt haben. Dieses Paket bietet zusätzliche wichtige Funktionen wie Fehlerumbruch, Erweiterung, Formatierung und Stapelverfolgungsaufzeichnung. Sie können das Paket installieren, indem Sie
go get github.com/pkg/errors
.

In Fällen, in denen Sie den Stack-Trace oder die erforderlichen Debugging-Informationen an Ihre Fehler anhängen müssen, führt die Verwendung der
Errorf
New
oder
Errorf
dieses Pakets zu Fehlern, die bereits in Ihren Stack-Trace geschrieben wurden, und Sie können damit auch einfache Metadaten anhängen Formatierungsfunktionen.
Errorf
implementiert die Schnittstelle
fmt.Formatter , d. H. Sie können sie mithilfe der Runen des Pakets
fmt
(
%s
,
%v
,
%+v
usw.) formatieren.
(// 6 oder Alternative)Dieses Paket führt auch die
errors.Wrapf
errors.Wrap
und
errors.Wrapf
. Diese Funktionen fügen dem Fehler mithilfe einer Nachricht und einer Stapelverfolgung an der Stelle, an der sie aufgerufen wurden, Kontext hinzu. Anstatt den Fehler einfach zurückzugeben, können Sie ihn daher mit Kontext und wichtigen Debugging-Daten umschließen.

Fehler-Wrapper durch andere Fehler unterstützen die
Cause() error
, die ihren internen Fehler zurückgibt. Darüber hinaus können sie mit
errors.Cause(err error) error
verwendet werden.
errors.Cause(err error) error
, die den internen Hauptfehler im
errors.Cause(err error) error
extrahiert.
Fehlerbehandlung
Typgenehmigung
Typzusicherungen spielen beim Umgang mit Fehlern eine wichtige Rolle. Sie werden sie verwenden, um Informationen aus dem Schnittstellenwert zu extrahieren. Da die Fehlerbehandlung mit Benutzerimplementierungen der
error
, ist die Implementierung von
error
ein sehr praktisches Werkzeug.
Die Syntax ist für alle Zwecke gleich -
x.(T)
wenn
x
einen Schnittstellentyp hat.
x.(T)
besagt, dass
x
nicht
nil
und dass der in
x
gespeicherte Wert vom Typ
T
In den nächsten Abschnitten werden zwei Möglichkeiten zur Verwendung von Typanweisungen untersucht - mit einem bestimmten Typ
T
und mit einer Schnittstelle vom Typ
T
(2 // Kurzsyntax überspringt die boolesche Variable ok
3 // Panik: Schnittstellenkonvertierung: Schnittstelle {} ist Null, keine Zeichenfolge
6 // erweiterte Syntax mit boolean ok
8 // gerät nicht in Panik, sondern setzt ok false, wenn die Anweisung false ist
9 // jetzt können wir s sicher als String verwenden)Sandbox: Panik mit verkürzter Syntax , sichere erweiterte SyntaxZusätzlicher Syntaxhinweis: Eine Typzusicherung kann entweder mit einer verkürzten Syntax (die in Panik gerät, wenn eine Anweisung fehlschlägt) oder einer erweiterten Syntax (die den logischen Wert OK verwendet, um Erfolg oder Misserfolg anzuzeigen) verwendet werden. Ich empfehle immer, länglich statt verkürzt einzunehmen, da ich lieber die Variable OK überprüfe und mich nicht mit Panik befasse.
Typ T-Zulassung
Eine Anweisung vom Typ
x.(T)
mit einer Schnittstelle vom Typ
T
bestätigt, dass
x
die Schnittstelle von
T
implementiert
T
Auf diese Weise können Sie sicherstellen, dass der Schnittstellenwert die Schnittstelle implementiert, und nur dann können Sie ihre Methoden verwenden.
(5 ... // behaupten, dass x die Resolver-Schnittstelle implementiert
6 ... // hier können wir diese Methode bereits sicher anwenden)Um zu verstehen, wie dies verwendet werden kann, werfen wir noch einmal einen Blick auf
pkg/errors
. Sie kennen dieses
errors.Cause(err error) error
bereits, also lassen Sie uns die
errors.Cause(err error) error
.
errors.Cause(err error) error
.
Diese Funktion empfängt einen Fehler und extrahiert den innersten Fehler, den sie erleidet (derjenige, der nicht mehr als Wrapper für einen anderen Fehler dient). Dies mag primitiv erscheinen, aber es gibt viele großartige Dinge, die Sie aus dieser Implementierung lernen können:

Quelle:
pkg / FehlerDie Funktion empfängt den Fehlerwert und kann nicht davon ausgehen, dass das empfangene
err
ein Wrapperfehler ist (unterstützt von der
Cause
Methode). Daher müssen Sie vor dem Aufrufen der
Cause
Methode sicherstellen, dass es sich um einen Fehler handelt, der diese Methode implementiert. Durch Ausführen einer type-Anweisung in jeder Iteration der for-Schleife können Sie sicherstellen, dass die
cause
Variable die
Cause
Methode unterstützt, und weiterhin interne Fehler daraus extrahieren, bis Sie einen Fehler finden, der keine
Cause
.
Indem Sie eine einfache lokale Schnittstelle erstellen, die nur die von Ihnen benötigten Methoden enthält, und eine Bestätigung darauf anwenden, wird Ihr Code von anderen Abhängigkeiten getrennt. Das Argument, das Sie erhalten haben, muss keine bekannte Struktur sein, es muss nur ein Fehler sein. Jeder Typ, der die Methoden
Error
und
Cause
implementiert, ist
Error
. Wenn Sie also die
Cause
Methode in Ihrem Fehlertyp implementieren, können Sie diese Funktion ohne Verlangsamung verwenden.
Es gibt jedoch einen kleinen Nachteil, den Sie beachten sollten: Schnittstellen können sich ändern. Daher sollten Sie den Code sorgfältig pflegen, damit Ihre Anweisungen nicht verletzt werden. Vergessen Sie nicht, Ihre Schnittstellen dort zu definieren, wo Sie sie verwenden, um sie schlank und ordentlich zu halten, und es wird Ihnen gut gehen.
Wenn Sie nur eine Methode benötigen, ist es manchmal bequemer, eine Anweisung auf einer anonymen Schnittstelle abzugeben, die nur die Methode enthält, auf die Sie sich verlassen, d. H.
v, ok := x.(interface{ F() (int, error) })
. Die Verwendung anonymer Schnittstellen kann dazu beitragen, Ihren Code von möglichen Abhängigkeiten zu trennen und ihn vor möglichen Änderungen an den Schnittstellen zu schützen.
Typ T- und Typschalterzulassung
Ich stelle diesen Abschnitt vor, indem ich zwei ähnliche Fehlerbehandlungsmuster vorstelle, die unter mehreren Fehlern und Fallen leiden. Dies bedeutet nicht, dass sie nicht häufig sind. Beide können praktische Werkzeuge in kleinen Projekten sein, lassen sich jedoch nicht gut skalieren.
Die erste ist die zweite Version der Typzusicherung: Es wird eine Zusicherung vom Typ
x.(T)
mit einem bestimmten Typ
T
durchgeführt. Er behauptet, dass der Wert von
x
vom Typ
T
ist oder in Typ
T
umgewandelt werden kann
T
(2 // wir können v als mypkg.SomeErrorType verwenden)Ein anderes ist das Muster des
Typschalters . Type Switch kombiniert eine switch-Anweisung mit einer type-Anweisung unter Verwendung des reservierten
type
Schlüsselworts. Sie treten besonders häufig bei der Fehlerbehandlung auf, bei der die Kenntnis des Grundtyps eines variablen Fehlers sehr nützlich sein kann.
(3 // Verarbeitung ...
5 // Verarbeitung ...)Der große Nachteil beider Ansätze besteht darin, dass beide mit ihren Abhängigkeiten zu einer Codebindung führen. Beide Beispiele sollten mit der
SomeErrorType
Struktur vertraut sein (die offensichtlich exportiert werden sollte) und das
mypkg
Paket importieren.
In beiden Ansätzen sollten Sie bei der Behandlung Ihrer Fehler mit dem Typ vertraut sein und dessen Paket importieren. Die Situation verschärft sich, wenn Sie sich mit Fehlern in Wrappern befassen, bei denen die Fehlerursache möglicherweise ein Fehler ist, der sich aus einer internen Abhängigkeit ergibt, die Sie nicht kennen und über die Sie nichts wissen sollten.
(7 // Verarbeitung ...
9 // Verarbeitung ...)Type Switch unterscheidet zwischen
*MyStruct
und
MyStruct
. Wenn Sie nicht sicher sind, ob es sich um einen Zeiger oder eine tatsächliche Instanz einer Struktur handelt, müssen Sie daher beide Optionen angeben. Darüber hinaus
fallthrough
die Fälle in Type Switch wie bei normalen Switches nicht fehl, aber im Gegensatz zu den üblichen Type Switch ist die Verwendung von
fallthrough
in Type Switch verboten. Sie müssen daher ein Komma verwenden und beide Optionen
fallthrough
, was leicht zu vergessen ist.

Zusammenfassend
Das ist alles! Sie sind jetzt mit den Fehlern vertraut und sollten bereit sein, alle Fehler zu beheben, die Ihre Go-Anwendung möglicherweise auf Ihren Pfad wirft (oder tatsächlich zurückgibt)!
Beide Fehlerpakete bieten einfache, aber wichtige Ansätze für Fehler in Go. Wenn sie Ihren Anforderungen entsprechen, sind sie eine gute Wahl. Sie können ganz einfach Ihre eigenen Fehlerstrukturen implementieren und die Go-Fehlerbehandlung nutzen, indem Sie sie mit
pkg/errors
error kombinieren.
Wenn Sie einfache Fehler skalieren, kann die korrekte Verwendung von Typanweisungen ein hervorragendes Werkzeug für die Behandlung verschiedener Fehler sein. Entweder mit Type Switch oder durch Überprüfen des Fehlerverhaltens und Überprüfen der implementierten Schnittstellen.
Was weiter?
Die Fehlerbehandlung in Go ist jetzt sehr relevant. Jetzt, da Sie die Grundlagen haben, fragen Sie sich vielleicht, was vor uns liegt, um mit Go-Fehlern umzugehen!
Die nächste Version von Go 2 widmet dem viel Aufmerksamkeit, und Sie können sich bereits die
Entwurfsversion ansehen. Darüber hinaus hatte Marcel van Lojuizen während der
dotGo 2019 ein hervorragendes Gespräch zu einem Thema, das ich nur empfehlen kann -
„GO 2-Fehlerwerte heute“ .
Natürlich gibt es noch viele weitere Ansätze, Tipps und Tricks, und ich kann sie nicht alle in einem Beitrag zusammenfassen! Trotzdem hoffe ich, dass es Ihnen gefallen hat und wir sehen uns in der nächsten Folge von
Before You Go !
Und jetzt traditionell auf Ihre Kommentare warten.