In den letzten zehn Jahren haben wir erfolgreich die Tatsache ausgenutzt, dass Go 
Fehler als Werte behandelt . Obwohl die Standardbibliothek nur eine minimale Unterstützung für Fehler hatte: nur die Funktionen 
errors.New und 
fmt.Errorf , die einen Fehler erzeugen, der nur eine Nachricht enthält - die integrierte Schnittstelle ermöglicht es Go-Programmierern, Informationen hinzuzufügen. Sie benötigen lediglich einen Typ, der die 
Error Methode implementiert:
 type QueryError struct { Query string Err error } func (e *QueryError) Error() string { return e.Query + ": " + e.Err.Error() } 
Diese Arten von Fehlern treten in allen Sprachen auf und speichern eine Vielzahl von Informationen, von Zeitstempeln über Dateinamen bis hin zu Serveradressen. Fehler auf niedriger Ebene, die zusätzlichen Kontext bieten, werden häufig erwähnt.
Ein Muster, bei dem ein Fehler einen anderen enthält, ist in Go so häufig, dass nach einer 
heftigen Diskussion in Go 1.13 seine explizite Unterstützung hinzugefügt wurde. In diesem Artikel werden Ergänzungen zur Standardbibliothek behandelt, die die erwähnte Unterstützung bieten: drei neue Funktionen im 
fmt.Errorf und ein neuer Formatierungsbefehl für 
fmt.Errorf .
Bevor wir die Änderungen im Detail besprechen, wollen wir uns damit befassen, wie Fehler in früheren Versionen der Sprache untersucht und konstruiert wurden.
Fehler vor dem Start 1.13
Fehlerforschung
Fehler in Go sind Bedeutungen. Programme treffen Entscheidungen basierend auf diesen Werten auf unterschiedliche Weise. Meistens wird der Fehler mit nil verglichen, um festzustellen, ob der Vorgang fehlgeschlagen ist.
 if err != nil {  
Manchmal vergleichen wir den Fehler, um den 
Kontrollwert herauszufinden und festzustellen, ob ein bestimmter Fehler aufgetreten ist.
 var ErrNotFound = errors.New("not found") if err == ErrNotFound {  
Der Fehlerwert kann von einem beliebigen Typ sein, der die in der Sprache definierte Fehlerschnittstelle erfüllt. Ein Programm kann eine Typanweisung oder einen Typschalter verwenden, um den Fehlerwert eines spezifischeren Typs anzuzeigen.
 type NotFoundError struct { Name string } func (e *NotFoundError) Error() string { return e.Name + ": not found" } if e, ok := err.(*NotFoundError); ok {  
Informationen hinzufügen
Oft leitet eine Funktion einen Fehler an den Aufrufstapel weiter und fügt ihm Informationen hinzu, z. B. eine kurze Beschreibung dessen, was passiert ist, als der Fehler aufgetreten ist. Dies ist einfach zu tun. Erstellen Sie einfach einen neuen Fehler, der den Text des vorherigen Fehlers enthält:
 if err != nil { return fmt.Errorf("decompress %v: %v", name, err) } 
Wenn Sie mit 
fmt.Errorf einen neuen Fehler 
fmt.Errorf verwerfen wir alles außer dem Text des ursprünglichen Fehlers. Wie wir im 
QueryError Beispiel gesehen haben, müssen Sie manchmal einen neuen Fehlertyp definieren, der den ursprünglichen Fehler enthält, um ihn zur Analyse mithilfe von Code zu speichern:
 type QueryError struct { Query string Err error } 
Programme können in den 
*QueryError und eine Entscheidung basierend auf dem ursprünglichen Fehler treffen. Dies wird manchmal als Auspacken eines Fehlers bezeichnet.
 if e, ok := err.(*QueryError); ok && e.Err == ErrPermission {  
Der Typ 
os.PathError aus der Standardbibliothek ist ein weiteres Beispiel dafür, wie ein Fehler einen anderen enthält.
Fehler in Go 1.13
Methode auspacken
In Go 1.13 vereinfachten die Standardbibliothekspakete 
errors und 
fmt die 
fmt Fehlern, die andere Fehler enthalten. Das wichtigste ist die Konvention, nicht die Änderung: Ein Fehler, der einen anderen Fehler enthält, kann die 
Unwrap Methode implementieren, die den ursprünglichen Fehler zurückgibt. Wenn 
e1.Unwrap() e2 zurückgibt, sagen wir, dass 
e1 e2 packt und Sie 
e1 entpacken können, um 
e2 zu erhalten.
Gemäß dieser Konvention können Sie der 
QueryError beschriebenen 
QueryError Methode den 
QueryError beschriebenen 
QueryError Typ 
QueryError , der den darin enthaltenen Fehler zurückgibt:
 func (e *QueryError) Unwrap() error { return e.Err } 
Das Ergebnis des Entpackens des Fehlers kann auch die 
Unwrap Methode enthalten. Die Folge von Fehlern, die durch wiederholtes Auspacken erhalten werden, nennen wir die 
Fehlerkette .
Fehleruntersuchung mit Is und As
In Go 1.13 enthält das 
errors zwei neue Funktionen zur Fehleruntersuchung: 
Is und 
As .
Die Funktion 
errors.Is vergleicht einen Fehler mit einem Wert.
 
Die 
As Funktion prüft, ob der Fehler von einem bestimmten Typ ist.
 
Im einfachsten Fall verhält sich die Funktion 
errors.Is wie ein Vergleich mit einem Steuerfehler und die Funktion 
errors.As wie eine 
errors.As . Bei der Arbeit mit gepackten Fehlern bewerten diese Funktionen jedoch alle Fehler in der Kette. Schauen wir uns das 
QueryError Beispiel oben an, um den ursprünglichen Fehler zu untersuchen:
 if e, ok := err.(*QueryError); ok && e.Err == ErrPermission {  
Mit der Funktion 
errors.Is können 
errors.Is schreiben:
 if errors.Is(err, ErrPermission) {  
Das 
Unwrap enthält auch eine neue 
Unwrap Funktion, die das Ergebnis des Aufrufs der 
Unwrap Methode des Fehlers zurückgibt oder nil zurückgibt, wenn der Fehler nicht über die 
Unwrap Methode verfügt. Es ist normalerweise besser, 
errors.Is zu verwenden. 
errors.Is oder 
errors.As , da Sie damit die gesamte Kette mit einem einzigen Aufruf untersuchen können.
Fehler beim Verpacken mit% w
Wie bereits erwähnt, ist es üblich, die Funktion 
fmt.Errorf zu verwenden, um dem Fehler zusätzliche Informationen hinzuzufügen.
 if err != nil { return fmt.Errorf("decompress %v: %v", name, err) } 
In Go 1.13 unterstützt die Funktion 
fmt.Errorf den neuen Befehl 
%w . Wenn dies der 
fmt.Errorf ist, enthält der von 
fmt.Errorf Fehler die 
Unwrap Methode, die das 
%w Argument zurückgibt, das ein Fehler sein sollte. In allen anderen Fällen ist 
%w identisch mit 
%v .
 if err != nil {  
Durch das Packen des Fehlers mit 
%w wird er für 
errors.Is verfügbar. 
errors.Is und 
errors.As :
 err := fmt.Errorf("access denied: %w", ErrPermission) ... if errors.Is(err, ErrPermission) ... 
Wann packen?
Wenn Sie dem Fehler mit 
fmt.Errorf oder einer benutzerdefinierten 
fmt.Errorf einen zusätzlichen Kontext hinzufügen, müssen Sie entscheiden, ob der neue Fehler das Original enthält. Es gibt keine einzige Antwort darauf, alles hängt vom Kontext ab, in dem der neue Fehler erstellt wird. Packen Sie, um ihren Anrufer zu zeigen. Packen Sie den Fehler nicht ein, wenn dies zur Offenlegung von Implementierungsdetails führt.
Stellen Sie sich beispielsweise eine 
Parse , die eine komplexe Datenstruktur aus 
io.Reader liest. Wenn ein Fehler auftritt, möchten wir die Nummer der Zeile und Spalte herausfinden, in der er aufgetreten ist. Wenn beim Lesen von 
io.Reader ein Fehler aufgetreten ist, müssen wir ihn packen, um den Grund herauszufinden. Da dem Aufrufer die Funktion 
io.Reader zur 
io.Reader , ist es sinnvoll, den von ihm erzeugten Fehler 
io.Reader .
Ein anderer Fall: Eine Funktion, die mehrere Datenbankaufrufe ausführt, sollte wahrscheinlich keinen Fehler zurückgeben, bei dem das Ergebnis eines dieser Aufrufe gepackt ist. Wenn die von dieser Funktion verwendete Datenbank Teil der Implementierung ist, verstößt das Offenlegen dieser Fehler gegen die Abstraktion. Wenn die 
LookupUser Funktion aus dem Paket 
pkg Paket Go 
database/sql , tritt möglicherweise der Fehler 
sql.ErrNoRows . Wenn Sie mit 
fmt.Errorf("accessing DB: %v", err) einen Fehler 
fmt.Errorf("accessing DB: %v", err) , kann der Aufrufer nicht nach innen schauen und 
sql.ErrNoRows finden. Wenn die Funktion jedoch 
fmt.Errorf("accessing DB: %w", err) , kann der Aufrufer 
fmt.Errorf("accessing DB: %w", err) schreiben:
 err := pkg.LookupUser(...) if errors.Is(err, sql.ErrNoRows) … 
In diesem Fall sollte die Funktion immer 
sql.ErrNoRows wenn Sie Clients nicht 
sql.ErrNoRows möchten, auch wenn Sie zu einem Paket mit einer anderen Datenbank wechseln. Mit anderen Worten, das Packen macht einen Fehler zu einem Teil Ihrer API. Wenn Sie diesen Fehler in Zukunft nicht mehr als Teil der API unterstützen möchten, packen Sie ihn nicht.
Es ist wichtig zu beachten, dass der Fehler unabhängig davon, ob Sie ihn einpacken oder nicht, unverändert bleibt. 
Eine Person, die es verstehen wird, wird die gleichen Informationen haben. Entscheidungen über Verpackungen zu treffen, hängt davon ab, ob zusätzliche Informationen für 
Programme benötigt 
werden, damit sie fundiertere Entscheidungen treffen können. oder wenn Sie diese Informationen ausblenden möchten, um den Abstraktionsgrad beizubehalten.
Einrichten von Fehlertests mit Is- und As-Methoden
Die Funktion 
errors.Is jeden Fehler in der Kette mit dem Zielwert. Standardmäßig entspricht ein Fehler diesem Wert, wenn sie gleichwertig sind. Darüber hinaus kann ein Fehler in der Kette mithilfe der Implementierung 
der Is Methode die Übereinstimmung mit dem Zielwert erklären.
Betrachten Sie den Fehler, der 
durch das Upspin-Paket verursacht 
wird , das den Fehler mit der Vorlage vergleicht und nur Felder ungleich Null auswertet:
 type Error struct { Path string User string } func (e *Error) Is(target error) bool { t, ok := target.(*Error) if !ok { return false } return (e.Path == t.Path || t.Path == "") && (e.User == t.User || t.User == "") } if errors.Is(err, &Error{User: "someuser"}) {  
Die Funktion 
errors.As empfiehlt auch die 
As Methode, falls vorhanden.
Fehler und Paket-APIs
Ein Paket, das Fehler zurückgibt (und die meisten Pakete tun dies), sollte die Eigenschaften dieser Fehler beschreiben, auf die sich ein Programmierer verlassen kann. Ein gut gestaltetes Paket vermeidet auch die Rückgabe von Fehlern mit Eigenschaften, auf die man sich nicht verlassen kann.
Am einfachsten ist es zu sagen, ob die Operation erfolgreich war, und den Wert null bzw. nicht null zurückzugeben. In vielen Fällen sind keine weiteren Informationen erforderlich.
Wenn Sie die Funktion benötigen, um einen identifizierbaren Fehlerzustand zurückzugeben, z. B. "Element nicht gefunden", können Sie einen Fehler zurückgeben, in den der Signalwert gepackt ist.
 var ErrNotFound = errors.New("not found")  
Es gibt andere Muster zum Bereitstellen von Fehlern, die der Aufrufer semantisch untersuchen kann. Geben Sie beispielsweise direkt einen Steuerwert, einen bestimmten Typ oder einen Wert zurück, der mithilfe einer Prädikativfunktion analysiert werden kann.
Geben Sie die internen Details in keinem Fall an den Benutzer weiter. Wie im Kapitel „Wann lohnt es sich zu verpacken?“ Erwähnt: Wenn Sie einen Fehler von einem anderen Paket zurückgeben, konvertieren Sie ihn so, dass der ursprüngliche Fehler nicht angezeigt wird, es sei denn, Sie beabsichtigen, diesen bestimmten Fehler in Zukunft zurückzugeben.
 f, err := os.Open(filename) if err != nil {  
Wenn eine Funktion einen Fehler mit einem gepackten Signalwert oder -typ zurückgibt, wird der ursprüngliche Fehler nicht direkt zurückgegeben.
 var ErrPermission = errors.New("permission denied")  
Fazit
Obwohl wir nur drei Funktionen und einen Formatierungsbefehl besprochen haben, hoffen wir, dass sie dazu beitragen, die Fehlerbehandlung in Go-Programmen erheblich zu verbessern. Wir hoffen, dass das Verpacken, um zusätzlichen Kontext bereitzustellen, zur normalen Praxis wird und Programmierern hilft, bessere Entscheidungen zu treffen und Fehler schneller zu finden.
Wie Russ Cox in seiner 
Rede auf der GopherCon 2019 sagte, experimentieren, vereinfachen und 
versenden wir auf dem Weg zu Go 2. Und jetzt, nachdem wir diese Änderungen ausgeliefert haben, machen wir uns an neue Experimente.