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.