error-InterfaceFehlerbehandlung erfolgt in Go nicht über Exceptions.
Stattdessen sind Fehler normale Werte von Typen, die das eingebaute error-Interface implementieren.
Das error-Interface ist sehr minimalistisch.
Es enthält nur eine Methode Error(), die die Fehlermeldung als String zurückgibt.
type error interface {
Error() string
}
Jedes Mal, wenn du eine Funktion definierst, in der während der Ausführung ein Fehler auftreten kann, der den Aufrufer erreichen muss, musst du error als einen der Rückgabetypen angeben.
Wenn die Funktion mehrere Rückgabewerte hat, ist error per Konvention immer der letzte.
func DoSomething() (int, error) {
// ...
}
Du musst das error-Interface nicht immer selbst implementieren.
Um einen einfachen Fehler zu erzeugen, kannst du die Funktion errors.New() verwenden, die Teil des Pakets errors der Standardbibliothek ist.
Das Einzige, was du übergeben musst, ist die Fehlermeldung als String, und errors.New() kümmert sich darum, einen Wert zu erzeugen, der deine Nachricht enthält und das error-Interface implementiert.
Wenn die Funktion einen Fehler zurückgibt, ist es gute Praxis, für alle anderen Rückgabeparameter den Nullwert zurückzugeben:
func DoSomething() (SomeStruct, int, error) {
// ...
return SomeStruct{}, 0, errors.New("failed to calculate result")
}
Du solltest nicht davon ausgehen, dass alle Funktionen Nullwerte für die anderen Rückgabewerte liefern, wenn ein Fehler vorliegt. Es ist gute Praxis, davon auszugehen, dass keiner der anderen Rückgabewerte sicher verwendet werden kann, wenn ein Fehler aufgetreten ist. Die einzigen Ausnahmen sind Fälle, in denen die Dokumentation eindeutig angibt, dass andere Rückgabewerte im Fehlerfall aussagekräftig sind.
Wenn du einen solchen einfachen Fehler an mehreren Stellen verwenden möchtest, solltest du eine Variable für den Fehler deklarieren, anstatt errors.New inline zu verwenden.
Per Konvention sollte der Name der Variable mit Err oder err beginnen (je nachdem, ob sie exportiert ist oder nicht).
Diese Fehlervariablen werden oft Sentinel-Fehler genannt.
import "errors"
var ErrNotFound = errors.New("resource was not found")
func DoSomething() error {
// ...
return ErrNotFound
}
Gib nil für den Fehler zurück, um zu signalisieren, dass während der Funktionsausführung keine Fehler aufgetreten sind:
func Foo() (int, error) {
return 10, nil
}
Wenn du eine Funktion aufrufst, die einen Fehler zurückgibt, ist es üblich, den Fehlerwert in einer Variable namens err zu speichern.
Bevor du das tatsächliche Ergebnis der Funktion verwendest, musst du prüfen, dass kein Fehler aufgetreten ist.
Um zu vermeiden, dass du den „Happy Path“ deines Codes verschachtelst, sollten Fehlerfälle zuerst behandelt werden.
Wir können == und != verwenden, um den Fehler mit nil zu vergleichen, und wir wissen, dass ein Fehler aufgetreten ist, wenn err nicht nil ist.
func processUserFile() error {
file, err := os.Open("./users.csv")
if err != nil {
return err
}
// do something with file
}
Meistens wird der Fehler, wie im obigen Beispiel gezeigt, im Funktionsstapel nach oben zurückgegeben. Eine andere Möglichkeit, mit dem Fehler umzugehen, wäre, ihn zu loggen und mit einer anderen Operation fortzufahren. Es ist gute Praxis, den Fehler entweder zurückzugeben oder zu loggen, niemals beides.
Da die meisten Funktionen in Go einen Fehler als einen der Rückgabewerte enthalten, wirst du das Muster if err != nil überall im Go-Code sehen und verwenden.
Wenn du möchtest, dass dein Fehler mehr Informationen als nur den Fehlermeldungs-String enthält, kannst du einen eigenen Fehlertyp erstellen.
Wie bereits erwähnt, kann alles, was das error-Interface implementiert (d. h. eine Methode Error() string hat), in Go als Fehler dienen.
Üblicherweise wird ein Struct verwendet, um einen eigenen Fehlertyp zu erstellen.
Per Konvention sollten die Namen eigener Fehlertypen mit Error enden.
Außerdem ist es am besten, die Methode Error() string mit einem Pointer-Receiver einzurichten; sieh dir diesen Stackoverflow-Kommentar an, um die Begründung zu erfahren.
Beachte, dass das bedeutet, dass du einen Pointer auf deinen eigenen Fehler zurückgeben musst; andernfalls zählt er nicht als error, weil der Nicht-Pointer-Wert die Methode Error() string nicht bereitstellt.
type MyCustomError struct {
message string
details string
}
func (e *MyCustomError) Error() string {
return fmt.Sprintf("%s, details: %s", e.message, e.details)
}
func someFunction() error {
// ...
return &MyCustomError{
message: "...",
details: "...",
}
}
Der Tag, auf den du so lange gewartet hast, ist endlich gekommen, und du bist jetzt der stolze Besitzer eines wunderschönen Bauernhofs in den Alpen.
Du magst es immer noch nicht, früh am Morgen aufzuwachen, um deine Kühe zu füttern.
Weil du ein ausgezeichneter Ingenieur bist, baust du einen Futterspender, den FEED-M-ALL.
Das Letzte, was noch fehlt, um dein Projekt abzuschließen, ist ein Stück Code, das die Futtermenge berechnet, die jede Kuh bekommen soll. Es ist wichtig, dass jede Kuh die gleiche Menge bekommt. Du musst Konflikte vermeiden. Kühe sind sehr empfindlich.
Zum Glück musst du nicht alle Formeln zur Berechnung der Futtermenge selbst herausfinden. Du verwendest eine geheimnisvolle externe Bibliothek, die du im Internet gefunden hast. Sie soll zu den glücklichsten Kühen führen. Die Bibliothek stellt einen Typ bereit, der das folgende Interface erfüllt. Darauf wirst du dich in deinem eigenen Code verlassen.
type FodderCalculator interface {
FodderAmount(int) (float64, error)
FatteningFactor() (float64, error)
}
Während du an deinem Code arbeitest, wirst du die Fehlerbehandlung verbessern, um sie robuster und später in deinem täglichen Leben auf dem Bauernhof einfacher zu debuggen zu machen.
Zuerst konzentrierst du dich darauf, den Code zu schreiben, der benötigt wird, um die Futtermenge pro Kuh zu berechnen.
Implementiere eine Funktion DivideFood, die einen FodderCalculator und eine Anzahl Kühe als Ganzzahl als Argumente entgegennimmt.
Für diese Aufgabe gehst du davon aus, dass die übergebene Anzahl der Kühe immer größer als null ist.
Die Funktion soll die Futtermenge pro Kuh als float64 oder einen Fehler zurückgeben, falls einer auftritt.
Um die Berechnung durchzuführen, musst du zuerst die Gesamtfuttermenge für alle Kühe abrufen.
Das machst du, indem du die Methode FodderAmount aufrufst und die Anzahl der Kühe übergibst.
Zusätzlich brauchst du einen Faktor, mit dem diese Menge multipliziert werden muss.
Diesen Faktor erhältst du, indem du die Methode FatteningFactor aufrufst.
Mit diesen beiden Werten und der Anzahl der Kühe kannst du nun die Futtermenge pro Kuh berechnen (als float64).
Das ist es, was die Funktion DivideFood zurückgeben soll.
Wenn eine der Methoden, die du aufrufst, einen Fehler zurückgibt, soll die Ausführung stoppen und dieser Fehler soll (unverändert) von der Funktion DivideFood zurückgegeben werden.
// For this example, we assume FodderAmount returns 50
// and FatteningFactor returns 1.5.
DivideFood(fodderCalculator, 5)
// => 15 <nil>
// Now assuming FodderAmount returns an error with message "something went wrong".
DivideFood(fodderCalculator, 5)
// => 0 "something went wrong"
Während du an der ersten Aufgabe oben gearbeitet hast, hast du gemerkt, dass die externe Bibliothek, die du verwendest, nicht so hochwertig ist, wie du dachtest. Zum Beispiel kann sie ungültige Eingaben nicht richtig verarbeiten. Du möchtest diese Einschränkung umgehen, indem du in deinem eigenen Code eine Überprüfung des Eingabewerts hinzufügst.
Schreibe eine Funktion ValidateInputAndDivideFood, die dieselbe Signatur wie DivideFood oben hat.
DivideFood aufrufen und die Ergebnisse dieses Aufrufs zurückgeben."invalid number of cows" zurückgeben.ValidateInputAndDivideFood(fodderCalculator, 5)
// => 15 <nil>
ValidateInputAndDivideFood(fodderCalculator, -2)
// => 0 "invalid number of cows"
Die Anzahl der Kühe zu überprüfen, bevor du sie weitergibst, war ein guter Zug, aber mit der unspezifischen Fehlermeldung bist du noch nicht ganz zufrieden.
Du beschließt, es besser zu machen, indem du einen benutzerdefinierten Fehlertyp namens InvalidCowsError erstellst.
Der benutzerdefinierte Fehler soll die Anzahl der Kühe (int) und eine benutzerdefinierte Nachricht (string) enthalten, und die Methode Error soll die Daten im folgenden Format serialisieren:
{number of cows} cows are invalid: {custom message}
Mit deinem benutzerdefinierten Fehler ausgestattet, implementiere eine Funktion ValidateNumberOfCows, die die Anzahl der Kühe als Ganzzahl akzeptiert und einen Fehler (oder nil) zurückgibt.
InvalidCowsError mit der benutzerdefinierten Nachricht "there are no negative cows" zurück.InvalidCowsError mit der benutzerdefinierten Nachricht "no cows don't need food" zurück.nil zurück, um anzuzeigen, dass die Validierung erfolgreich war.err := ValidateNumberOfCows(-5)
err.Error()
// => "-5 cows are invalid: there are no negative cows"
Nach der harten Arbeit, diese Validierungsfunktion einzurichten, merkst du, dass es bereits Abend ist, und verlässt deinen Schreibtisch, um den Sonnenuntergang über den Bergen zu genießen. Die Aufgabe, die neue Validierungsfunktion tatsächlich in deinen Code einzubauen, verschiebst du auf einen anderen Tag.
Melde dich bei Exercism an, um Go mit 34 Konzepte166 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.