Rückgabe vom Typ error

Die zweite Möglichkeit der Fehlerbehandlung besteht in der Rückgabe des speziellen Fehlertyps error. Sie wird dann verwendet, wenn der Anwender einen Fehler erwartet, also beispielsweise wenn eine Datei geöffnet werden soll, die eventuell nicht existiert. Dann wird in der Regel eine Funktion aufgerufen und anschließend geprüft, ob ein Fehler aufgetreten ist.

Die meisten Funktionen in Go, in denen ein Fehler auftreten kann, geben zwei Werte zurück: das eigentliche Ergebnis und den Fehlerwert. Ist dieser nil, also ohne Inhalt, ist kein Fehler aufgetreten und das eigentliche Ergebnis kann verwendet werden. Typisch wird das wie folgt benutzt:

package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("/datei/die/nicht/exitiert")
    if err != nil {
        // Fehlerbehandlung
        fmt.Println("Fehler aufgetreten:", err.Error())
        return
    }
    // OK, alles gut!
    // ...
    // ...
    err = file.Close()
    if err != nil {
        // Fehlerbehandlung
        fmt.Println("Fehler aufgetreten:", err.Error())
        return
    }
}

Die Funktion os.Open() gibt zwei Werte zurück. Hier der Ausschnitt aus der Dokumentation:

func Open(name string) (file *File, err error)

Was bedeutet, dass die Funktion einen Wert als Parameter erwartet (eine Zeichenkette, der Bezeichner name ist nur ein Hinweis auf die Verwendung und hat keine tiefere Bedeutung) und zwei Parameter zurück liefert. Der erste ist vom Typ Zeiger auf os.File, der zweite vom Typ error. Auch hier sind die Namen vor den Typbezeichnungen nur Hinweise auf deren Bedeutung.

Zurück zum Beispiel von oben. In der ersten Zeile der Funktion main() haben wir die beiden Rückgabewerte von os.Open() zwei Variablen zugeordnet. Die Konvention ist, dass wenn der zweite Wert (also der Typ error) nil ist, dann ist kein Fehler aufgetreten. Wenn der zweite Wert aber einen Wert hat, ist das der Fehlercode, den die Funktion os.Open() zurückgegeben hat. Das wird in der zweiten Zeile überprüft. Im Falle eines Fehler ist in der Funktion Error() eine textuelle Beschreibung des Fehlers (Zeichenkette).

Der Typ error ist in Go eingebaut und hat erst einmal keine spezielle Bedeutung, außer, dass er per Konvention für Fehlerzustände benutzt wird. Seine Definition ist folgende:

type error interface {
    Error() string
}

Das heißt, alle Typen, die eine Funktion Error() mit dem Rückgabetyp string definieren, sind auch vom Typ error und können als Fehlercode benutzt werden. Um einen eigenen Fehlercode zu erzeugen, kann man sich entweder einen eingenen Typ definieren und eben diese Funktion definieren, oder man benutzt die praktischerweise im Paket errors enthaltene Funktion new(), um einen neuen Fehler zu erzeugen.

package main

import (
    "errors"
    "fmt"
)

func ohoh() error {
    var neuerFehler error
    neuerFehler = errors.New("Ganz schlimmer Fehler!")
    return neuerFehler
}

// oder einfacher:
// func ohoh() error {
//  return errors.New("Ganz schlimmer Fehler!")
// }

func main() {
    err := ohoh()
    if err != nil {
        fmt.Println(err)
        return
    }
}

Ein Kritikpunkt an der expliziten Übergabe des Fehlercodes (anstelle von Exceptions wie bei anderen Programmiersprachen) ist, dass der Code schnell unübersichtlich wird. Manchmal lässt es sich nicht vermeiden, Code in dieser Art zu schreiben:

func beispiel() {
    wert, err := EineFunktion()
    if err != nil {
        // Fehlerbehandlung
    }
    wert2, err := EineZweiteFunktion(wert)
    if err != nil {
        // Fehlerbehandlung
    }
    wert3, err := EineDrittteFunktion(wert2)
    if err != nil {
        // Fehlerbehandlung
    }
    // mache etwas mit wert3
}

Dass dass nicht immer auf den ersten Blick zu verstehen ist, verwundert nicht. Übersichtlicher wäre dieses hier:

func beispiel() {
    // Geht nicht:
    versuche {
        wert := EineFunktion()
        wert2 := EineZweiteFunktion(wert)
        wert3 := EineDrittteFunktion(wert2)
        // mache etwas mit wert3
    }
    ansonsten {
        // Fehlerbehandlung
    }
}

Die Befürworter der ersten Variante halten dagegen, dass man sich über die auftretenden Fehler mehr Gedanken macht und bewusster Code schreibt.