Fehlerbehandlung in Go

Robuster Code muss auf unerwartete Umstände wie falsche Benutzereingaben, fehlerhafte Netzwerkverbindungen und fehlerhafte Datenträger korrekt reagieren. Error handling ist der Vorgang, bei dem festgestellt wird, wann sich Ihr Programm in einem unerwarteten Zustand befindet, und Schritte unternommen werden, um Diagnoseinformationen für das spätere Debuggen aufzuzeichnen.

Im Gegensatz zu anderen Sprachen, in denen Entwickler Fehler mit spezieller Syntax behandeln müssen, sind Fehler in Go Werte mit dem Typerror, die von Funktionen wie jedem anderen Wert zurückgegeben werden. Um Fehler in Go zu behandeln, müssen wir diese Fehler untersuchen, die von Funktionen zurückgegeben werden könnten, entscheiden, ob ein Fehler aufgetreten ist, und geeignete Maßnahmen ergreifen, um Daten zu schützen und Benutzern oder Operatoren mitzuteilen, dass der Fehler aufgetreten ist.

Fehler erzeugen

Bevor wir Fehler behandeln können, müssen wir zuerst einige erstellen. Die Standardbibliothek bietet zwei integrierte Funktionen zum Erstellen von Fehlern:errors.New undfmt.Errorf. Mit beiden Funktionen können Sie eine benutzerdefinierte Fehlermeldung angeben, die Sie später Ihren Benutzern präsentieren können.

errors.New verwendet ein einzelnes Argument - eine Fehlermeldung als Zeichenfolge, die Sie anpassen können, um Ihre Benutzer auf Fehler aufmerksam zu machen.

Führen Sie das folgende Beispiel aus, um einen Fehler zu sehen, der durcherrors.New verursacht wurde, die auf die Standardausgabe gedruckt wurden:

package main

import (
    "errors"
    "fmt"
)

func main() {
    err := errors.New("barnacles")
    fmt.Println("Sammy says:", err)
}
OutputSammy says: barnacles

Wir haben die Funktionerrors.New aus der Standardbibliothek verwendet, um eine neue Fehlermeldung mit der Zeichenfolge"barnacles" als Fehlermeldung zu erstellen. Wir haben hier die Konvention befolgt, indem wir für die Fehlermeldung Kleinbuchstaben verwendet haben, wie inGo Programming Language Style Guideangegeben.

Schließlich haben wir die Funktionfmt.Println verwendet, um unsere Fehlermeldung mit"Sammy says:" zu kombinieren.

Mit der Funktionfmt.Errorf können Sie dynamisch eine Fehlermeldung erstellen. Das erste Argument ist eine Zeichenfolge, die Ihre Fehlermeldung mit Platzhalterwerten wie%s für eine Zeichenfolge und%d für eine Ganzzahl enthält. fmt.Errorf interpoliert die Argumente, die dieser Formatierungszeichenfolge folgen, in der folgenden Reihenfolge in diese Platzhalter:

package main

import (
    "fmt"
    "time"
)

func main() {
    err := fmt.Errorf("error occurred at: %v", time.Now())
    fmt.Println("An error happened:", err)
}
OutputAn error happened: Error occurred at: 2019-07-11 16:52:42.532621 -0400 EDT m=+0.000137103

Wir haben die Funktionfmt.Errorf verwendet, um eine Fehlermeldung zu erstellen, die die aktuelle Zeit enthält. Die Formatierungszeichenfolge, die wirfmt.Errorf zur Verfügung gestellt haben, enthält die Formatierungsanweisung%v, diefmt.Errorf anweist, die Standardformatierung für das erste Argument nach der Formatierungszeichenfolge zu verwenden. Dieses Argument ist die aktuelle Zeit, die von dertime.Now-Funktion aus der Standardbibliothek bereitgestellt wird. Ähnlich wie im vorherigen Beispiel kombinieren wir unsere Fehlermeldung mit einem kurzen Präfix und drucken das Ergebnis mit der Funktionfmt.Println in die Standardausgabe.

Fehler behandeln

In der Regel wird ein derart erstellter Fehler nicht sofort für einen anderen Zweck verwendet, wie im vorherigen Beispiel. In der Praxis ist es weitaus üblicher, einen Fehler zu erstellen und ihn von einer Funktion zurückzugeben, wenn ein Fehler auftritt. Aufrufer dieser Funktion verwenden dann eineif-Anweisung, um festzustellen, ob der Fehler vorliegt, odernil - ein nicht initialisierter Wert.

Dieses nächste Beispiel enthält eine Funktion, die immer einen Fehler zurückgibt. Beachten Sie beim Ausführen des Programms, dass es die gleiche Ausgabe wie im vorherigen Beispiel erzeugt, obwohl diesmal eine Funktion den Fehler zurückgibt. Das Deklarieren eines Fehlers an einem anderen Ort ändert nichts an der Fehlermeldung.

package main

import (
    "errors"
    "fmt"
)

func boom() error {
    return errors.New("barnacles")
}

func main() {
    err := boom()

    if err != nil {
        fmt.Println("An error occurred:", err)
        return
    }
    fmt.Println("Anchors away!")
}
OutputAn error occurred: barnacles

Hier definieren wir eine Funktion namensboom(), die ein einzelneserror zurückgibt, das wir miterrors.New konstruieren. Wir rufen dann diese Funktion auf und erfassen den Fehler mit der Zeileerr := boom().
. Sobald wir diesen Fehler zugewiesen haben, prüfen wir, ob er mit der Bedingungif err != nil vorhanden war. Hier wird die Bedingung immer zutrue ausgewertet, da wir immererror vonboom() zurückgeben.

Dies ist nicht immer der Fall. Daher empfiehlt es sich, Fälle mit logischer Behandlung zu verwenden, in denen kein Fehler vorliegt (nil), und Fälle, in denen der Fehler vorliegt. Wenn der Fehler vorliegt, verwenden wirfmt.Println, um unseren Fehler zusammen mit einem Präfix zu drucken, wie wir es in früheren Beispielen getan haben. Schließlich verwenden wir einereturn-Anweisung, um die Ausführung vonfmt.Println("Anchors away!") zu überspringen, da diese nur ausgeführt werden sollte, wenn kein Fehler aufgetreten ist.

[.note] #Note: Die im letzten Beispiel gezeigte Konstruktion vonif err != nil ist das Arbeitspferd der Fehlerbehandlung in der Programmiersprache Go. Wo immer eine Funktion einen Fehler erzeugen könnte, ist es wichtig, eineif-Anweisung zu verwenden, um zu überprüfen, ob eine aufgetreten ist. Auf diese Weise hat der idiomatische Go-Code natürlich seine“happy path”-Logik auf der ersten Einrückungsstufe und die gesamte "traurige Pfad" -Logik auf der zweiten Einrückungsstufe.
#

If-Anweisungen verfügen über eine optionale Zuweisungsklausel, mit deren Hilfe das Aufrufen einer Funktion und die Behandlung ihrer Fehler vereinfacht werden können.

Führen Sie das nächste Programm aus, um die gleiche Ausgabe wie in unserem vorherigen Beispiel zu sehen, diesmal jedoch mit der Anweisungifder Verbindung, um einige Boilerplates zu reduzieren:

package main

import (
    "errors"
    "fmt"
)

func boom() error {
    return errors.New("barnacles")
}

func main() {
    if err := boom(); err != nil {
        fmt.Println("An error occurred:", err)
        return
    }
    fmt.Println("Anchors away!")
}
OutputAn error occurred: barnacles

Nach wie vor haben wir eine Funktion,boom(), die immer einen Fehler zurückgibt. Wir weisen den vonboom() zurückgegebenen Fehlererr als ersten Teil der Anweisungif zu. Im zweiten Teil der Anweisungif steht nach dem Semikolon die Variableerr zur Verfügung. Wir prüfen, ob der Fehler aufgetreten ist, und geben den Fehler wie zuvor mit einem kurzen Präfix aus.

In diesem Abschnitt haben wir gelernt, wie Sie mit Funktionen umgehen, die nur einen Fehler zurückgeben. Diese Funktionen sind häufig, aber es ist auch wichtig, Fehler von Funktionen zu behandeln, die mehrere Werte zurückgeben können.

Fehler neben Werten zurückgeben

Bei Funktionen, die einen einzelnen Fehlerwert zurückgeben, handelt es sich häufig um Funktionen, die eine statusbezogene Änderung bewirken, z. B. das Einfügen von Zeilen in eine Datenbank. Es ist auch üblich, Funktionen zu schreiben, die einen Wert zurückgeben, wenn sie erfolgreich abgeschlossen wurden, zusammen mit einem potenziellen Fehler, wenn diese Funktion fehlgeschlagen ist. Mit Go können Funktionen mehr als ein Ergebnis zurückgeben, mit dem gleichzeitig ein Wert und ein Fehlertyp zurückgegeben werden können.

Um eine Funktion zu erstellen, die mehr als einen Wert zurückgibt, listen wir die Typen jedes zurückgegebenen Werts in Klammern in der Signatur für die Funktion auf. Beispielsweise würde einecapitalize-Funktion, die einstring und einerror zurückgibt, mitfunc capitalize(name string) (string, error) {} deklariert. Der Teil(string, error) teilt dem Go-Compiler mit, dass diese Funktion einstring und einerror in dieser Reihenfolge zurückgibt.

Führen Sie das folgende Programm aus, um die Ausgabe einer Funktion anzuzeigen, die sowohl einstring als auch einerror zurückgibt:

package main

import (
    "errors"
    "fmt"
    "strings"
)

func capitalize(name string) (string, error) {
    if name == "" {
        return "", errors.New("no name provided")
    }
    return strings.ToTitle(name), nil
}

func main() {
    name, err := capitalize("sammy")
    if err != nil {
        fmt.Println("Could not capitalize:", err)
        return
    }

    fmt.Println("Capitalized name:", name)
}
OutputCapitalized name: SAMMY

Wir definierencapitalize() als eine Funktion, die eine Zeichenfolge (den Namen, der großgeschrieben werden soll) verwendet und eine Zeichenfolge und einen Fehlerwert zurückgibt. Inmain() rufen wircapitalize() auf und weisen die beiden von der Funktion zurückgegebenen Werte den Variablenname underr zu, indem wir sie durch Kommas auf der linken Seite von trennen := Operator. Danach führen wir unsereif err != nil-Prüfung wie in früheren Beispielen durch und drucken den Fehler mitfmt.Println in die Standardausgabe, wenn der Fehler vorhanden war. Wenn kein Fehler vorhanden war, drucken wirCapitalized name: SAMMY.

Versuchen Sie, die Zeichenfolge"sammy" inname, err := capitalize("sammy") in die leere Zeichenfolge("") zu ändern, und Sie erhalten stattdessen den FehlerCould not capitalize: no name provided.

Die Funktioncapitalize gibt einen Fehler zurück, wenn Aufrufer der Funktion eine leere Zeichenfolge für den Parametername angeben. Wenn der Parametername nicht die leere Zeichenfolge ist, verwendetcapitalize()strings.ToTitle, um den Parametername groß zu schreiben, und gibtnil für den Fehlerwert zurück.

In diesem Beispiel werden einige subtile Konventionen befolgt, die für Go-Code typisch sind, jedoch vom Go-Compiler nicht erzwungen werden. Wenn eine Funktion mehrere Werte zurückgibt, einschließlich eines Fehlers, fordert die Konvention an, dass wirerror als letztes Element zurückgeben. Wenn Sie einerror von einer Funktion mit mehreren Rückgabewerten zurückgeben, setzt der idiomatische Go-Code auch jeden fehlerfreien Wert aufzero value. Nullwerte sind beispielsweise eine leere Zeichenfolge für Zeichenfolgen,0 für Ganzzahlen, eine leere Struktur für Strukturtypen undnil für Schnittstellen- und Zeigertypen, um nur einige zu nennen. Wir behandeln Nullwerte detaillierter in unserentutorial on variables and constants.

Reduzierplatte

Das Einhalten dieser Konventionen kann in Situationen mühsam werden, in denen es viele Werte gibt, die von einer Funktion zurückgegeben werden müssen. Wir können einanonymous function verwenden, um die Kesselplatte zu reduzieren. Anonyme Funktionen sind Prozeduren, die Variablen zugewiesen werden. Im Gegensatz zu den Funktionen, die wir in früheren Beispielen definiert haben, sind sie nur in den Funktionen verfügbar, in denen Sie sie deklarieren. Dadurch eignen sie sich perfekt als kurze Teile der wiederverwendbaren Hilfslogik.

Das folgende Programm ändert das letzte Beispiel so, dass es die Länge des Namens enthält, den wir großschreiben. Da es drei zurückzugebende Werte gibt, kann die Behandlung von Fehlern ohne eine anonyme Funktion, die uns hilft, umständlich werden:

package main

import (
    "errors"
    "fmt"
    "strings"
)

func capitalize(name string) (string, int, error) {
    handle := func(err error) (string, int, error) {
        return "", 0, err
    }

    if name == "" {
        return handle(errors.New("no name provided"))
    }

    return strings.ToTitle(name), len(name), nil
}

func main() {
    name, size, err := capitalize("sammy")
    if err != nil {
        fmt.Println("An error occurred:", err)
    }

    fmt.Printf("Capitalized name: %s, length: %d", name, size)
}
OutputCapitalized name: SAMMY, length: 5

Innerhalb vonmain() erfassen wir nun die drei zurückgegebenen Argumente voncapitalize alsname,size bzw.err. Wir prüfen dann, obcapitalize einerror zurückgegeben hat, indem wir prüfen, ob die Variableerr nicht gleichnil war. Dies ist wichtig, bevor Sie versuchen, einen der anderen voncapitalize zurückgegebenen Werte zu verwenden, da die anonyme Funktionhandle diese Werte auf Null setzen kann. Da kein Fehler aufgetreten ist, weil wir die Zeichenfolge"sammy" angegeben haben, drucken wir den großgeschriebenen Namen und seine Länge aus.

Sie können erneut versuchen,"sammy" in die leere Zeichenfolge("") zu ändern, um den gedruckten Fehlerfall (An error occurred: no name provided) anzuzeigen.

Innerhalb voncapitalize definieren wir die Variablehandle als anonyme Funktion. Es nimmt einen einzelnen Fehler und gibt identische Werte in derselben Reihenfolge zurück wie die Rückgabewerte voncapitalize. handle setzt diese Werte auf Null und leitet die als Argument übergebenenerror als endgültigen Rückgabewert weiter. Auf diese Weise können wir alle incapitalize aufgetretenen Fehler zurückgeben, indem wir die Anweisungreturn vor dem Aufruf anhandle miterror als Parameter verwenden.

Denken Sie daran, dasscapitalize immer drei Werte zurückgeben muss, da wir die Funktion so definiert haben. Manchmal möchten wir nicht mit allen Werten umgehen, die eine Funktion zurückgeben könnte. Glücklicherweise haben wir eine gewisse Flexibilität, wie wir diese Werte auf der Zuweisungsseite verwenden können.

Fehlerbehandlung bei Multi-Return-Funktionen

Wenn eine Funktion viele Werte zurückgibt, müssen wir diese in Go einer Variablen zuweisen. Im letzten Beispiel geben wir dazu Namen für die beiden Werte an, die von der Funktioncapitalizezurückgegeben werden. Diese Namen sollten durch Kommas getrennt sein und auf der linken Seite des Operators:=angezeigt werden. Der erste voncapitalize zurückgegebene Wert wird der Variablenname zugewiesen, und der zweite Wert (error) wird der Variablenerr zugewiesen. Gelegentlich interessiert uns nur der Fehlerwert. Sie können unerwünschte Werte, die Funktionen zurückgeben, mit dem Variablennamen des speziellen_verwerfen.

Im folgenden Programm haben wir unser erstes Beispiel mit der Funktioncapitalize so geändert, dass durch Übergabe der leeren Zeichenfolge("") ein Fehler auftritt. Versuchen Sie, dieses Programm auszuführen, um festzustellen, wie wir nur den Fehler untersuchen können, indem Sie den ersten zurückgegebenen Wert mit der Variablen_verwerfen:

package main

import (
    "errors"
    "fmt"
    "strings"
)

func capitalize(name string) (string, error) {
    if name == "" {
        return "", errors.New("no name provided")
    }
    return strings.ToTitle(name), nil
}

func main() {
    _, err := capitalize("")
    if err != nil {
        fmt.Println("Could not capitalize:", err)
        return
    }
    fmt.Println("Success!")
}
OutputCould not capitalize: no name provided

Innerhalb dermain()-Funktion weisen wir diesmal der Unterstrichvariablen (_) den großgeschriebenen Namen (die zuerst zurückgegebenenstring) zu. Gleichzeitig weisen wir dieerror, die voncapitalize zurückgegeben werden, der Variablenerr zu. Wir prüfen dann, ob der Fehler in der Bedingungif err != nil vorhanden war. Da wir eine leere Zeichenfolge als Argument fürcapitalize in der Zeile_, err := capitalize("") fest codiert haben, wird diese Bedingung immer zutrue ausgewertet. Dies erzeugt die Ausgabe"Could not capitalize: no name provided", die durch den Aufruf der Funktionfmt.Println im Hauptteil der Anweisungif gedruckt wird. Diereturn danach überspringen diefmt.Println("Success!").

Fazit

Wir haben viele Möglichkeiten gesehen, Fehler mithilfe der Standardbibliothek zu erstellen und Funktionen zu erstellen, die Fehler auf idiomatische Weise zurückgeben. In diesem Tutorial ist es uns gelungen, mit den Standardfunktionenerrors.New undfmt.Errorf der Standardbibliothek erfolgreich verschiedene Fehler zu erstellen. In zukünftigen Tutorials werden wir uns damit beschäftigen, wie wir unsere eigenen benutzerdefinierten Fehlertypen erstellen, um den Benutzern umfassendere Informationen zu vermitteln.