Fehlerbehandlung in Go

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 werden

Die 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


Fehlerschnittstelle

Unter 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 Quelle

Es 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 Quellcode

Sie 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 es

github.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 Syntax

Zusä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 / Fehler

Die 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.

Source: https://habr.com/ru/post/de453806/


All Articles