Fe

Fehler in Go

2 Übungen

Über Fehler

Das Interface error

In Go erfolgt die Fehlerbehandlung nicht über Exceptions. Stattdessen sind Fehler normale Werte von Typen, die das eingebaute Interface error implementieren. Das Interface error ist sehr minimal. 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, steht error per Konvention immer an letzter Stelle.

func DoSomething() (int, error) {
  // ...
}

Einen einfachen Fehler erzeugen und zurückgeben

Du musst das Interface error nicht immer selbst implementieren. Um einen einfachen Fehler zu erzeugen, kannst du die Funktion errors.New() verwenden, die zum Paket errors der Standardbibliothek gehört. Du musst nur die Fehlermeldung als String übergeben, und errors.New() kümmert sich darum, einen Wert zu erzeugen, der deine Meldung enthält und das Interface error 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")
}
Caution

Du solltest nicht davon ausgehen, dass alle Funktionen für die übrigen Rückgabewerte Nullwerte zurückgeben, wenn ein Fehler vorliegt. Es ist gute Praxis, anzunehmen, dass keiner der anderen Rückgabewerte sicher verwendet werden kann, wenn ein Fehler zurückgegeben wird.

Angenommen, eine Funktion versucht, aus einer Datei zu lesen, und gibt ein halb gefülltes Byte-Slice und einen Fehler zurück. Wenn du dieses zurückgegebene Byte-Slice in deinem Code weiterverwendest und annimmst, dass es immer leer ist, weil ein Fehler aufgetreten ist, kannst du auf subtile Bugs stoßen.

Die einzigen Ausnahmen sind Fälle, in denen die Dokumentation der Funktion klar angibt, dass die anderen Rückgabewerte im Fehlerfall von Bedeutung sind. Ein Beispiel findest du in der [Dokumentation für Write auf einem Buffer][buffer-write].

Achte darauf, dass die Fehlermeldung, die du angibst, so spezifisch wie möglich ist, denn Fehler enthalten standardmäßig keine Stacktraces. Per Konvention sollte die Fehlermeldung mit einem Kleinbuchstaben beginnen und nicht mit einem Punkt enden.

Wenn du einen solchen Fehler an mehreren Stellen verwenden möchtest (oder den Fehler für die Nutzer deines Pakets verfügbar machen willst), solltest du eine Variable für den Fehler deklarieren, anstatt errors.New direkt im Code aufzurufen. 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 es bei der Ausführung der Funktion keine Fehler gab:

func Foo() (int, error) {
  return 10, nil
}

Variablen in die Fehlermeldung aufnehmen

Wenn du aus einigen Variablen eine komplexere Fehlermeldung aufbauen musst, kannst du String-Formatierung nutzen. Go hat für diesen Zweck eine spezielle Funktion fmt.Errorf. Sie baut den String der Fehlermeldung zusammen und gibt einen Fehler zurück.

input := 123
action := "UPDATE"

err := fmt.Errorf("invalid input %d for action %s", input, action)
err.Error()
// => "invalid input 123 for action UPDATE"

Fehler prüfen

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 eigentliche Ergebnis der Funktion verwendest, musst du prüfen, dass kein Fehler aufgetreten ist. Mit == und != können wir den Fehler mit nil vergleichen, und wir wissen, dass ein Fehler aufgetreten ist, wenn err nicht nil ist.

Um den „Happy Path“ deines Codes nicht zu verschachteln, solltest du Fehlerfälle zuerst behandeln, meist durch einen frühen Return. Weitere Details findest du in diesem berühmten Artikel von Mat Ryer.

// Do this:
func myFunc() error {
  file, err := os.Open("./users.csv")
  if err != nil {
    return err
  }
  // do something with file
}

// Not this:
func myFunc() error {
  file, err := os.Open("./users.csv")
  if err != nil {
    // handle err
  } else {
    // do something with file
  }
}

// Also not this:
func myFunc() error {
  file, err := os.Open("./users.csv")
  if err == nil {
    // do something with file
  }
  // handle err
}

Meistens wird der Fehler den Funktionsstack hinauf zurückgegeben, wie im ersten Beispiel oben gezeigt. Eine andere Möglichkeit, mit dem Fehler umzugehen, wäre, ihn zu protokollieren und mit einer anderen Operation fortzufahren. Es ist gute Praxis, den Fehler entweder zurückzugeben oder zu protokollieren, nie beides.

Da die meisten Funktionen in Go einen Fehler als einen der Rückgabewerte haben, wirst du das Muster if err != nil überall im Go-Code sehen und verwenden.

Eigene Fehlertypen

Wenn dein Fehler mehr Informationen als nur den String der Fehlermeldung enthalten soll, kannst du einen eigenen Fehlertyp erstellen. Wie bereits erwähnt, kann in Go alles, was das Interface error implementiert (d. h. eine Methode Error() string hat), als Fehler dienen.

Üblicherweise verwendet man ein Struct, um einen eigenen Fehlertyp zu erstellen. Per Konvention sollten die Namen eigener Fehlertypen mit Error enden. Außerdem solltest du die Methode Error() string am besten mit einem Pointer-Receiver definieren; die Begründung dazu findest du in diesem Stackoverflow-Kommentar. Beachte, dass du dazu einen Pointer auf deinen eigenen Fehler zurückgeben musst, sonst 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: "...",
  }
}
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab

Lerne Fehler

Das Üben ist gesperrt

Schalte 4 weitere Übungen frei, um Fehler zu üben