Fehlerbehandlung in Go: Philosophie und Praxis
Im Gegensatz zu vielen modernen Sprachen verwendet Go (Golang) nicht das klassische try/catch-Konstrukt. Stattdessen folgen Go-Entwickler dem Ansatz „Fehler als Werte“ (errors as values). Dies macht den Code expliziter, vorhersagbarer und sicherer. In diesem Artikel analysieren wir alle Aspekte der Fehlerbehandlung in Go: von grundlegenden Mustern bis hin zu fortgeschrittenen Techniken mit defer, panic und recover.
1. Grundlagen: Fehler sind einfach Werte
In Go ist ein Fehler das Interface error mit der einzigen Methode Error() string. Jede Funktion, die fehlschlagen kann, gibt einen Fehler als letzten Rückgabewert zurück.
package main
import ( "errors" "fmt")
// Divisionsfunktion mit Fehlerrückgabefunc divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("Division durch Null nicht möglich") } return a / b, nil}
func main() { result, err := divide(10, 0) if err != nil { fmt.Println("Fehler:", err) return } fmt.Println("Ergebnis:", result)}Der entscheidende Punkt: Die Überprüfung if err != nil ist die Standardmethode zur Fehlerbehandlung in Go. Es gibt keine versteckten Ausnahmen, die unerwartet „auftauchen“ könnten.
2. Erstellung und Anpassung von Fehlern
Go bietet mehrere Möglichkeiten zur Fehlererstellung:
errors.New()— einfacher textueller Fehler.fmt.Errorf()— formatierter Fehler (unterstützt%wfür Wrapping).- Benutzerdefinierte Typen — zur Übergabe zusätzlichen Kontexts.
Benutzerdefinierter Fehler mit zusätzlichen Daten
type DivisionError struct { Dividend float64 Divisor float64 Message string}
func (e *DivisionError) Error() string { return fmt.Sprintf("%s: %f / %f", e.Message, e.Dividend, e.Divisor)}
func safeDivide(a, b float64) (float64, error) { if b == 0 { return 0, &DivisionError{ Dividend: a, Divisor: b, Message: "Division durch Null", } } return a / b, nil}
func main() { _, err := safeDivide(10, 0) if err != nil { // Type Assertion zum Abrufen zusätzlicher Felder var divErr *DivisionError if errors.As(err, &divErr) { fmt.Printf("Fehler mit %f durch %f: %s", divErr.Dividend, divErr.Divisor, divErr.Message) } }}Die Verwendung von errors.As() ermöglicht es zu prüfen, ob ein Fehler einem bestimmten Typ entspricht, und Daten daraus zu extrahieren. Dies ist das Äquivalent zu instanceof in anderen Sprachen.
3. Fehlerbehandlungsmuster: Vom Einfachen zum Komplexen
In Go gibt es mehrere etablierte Muster für die Arbeit mit Fehlern:
3.1. Sentinel-Fehler (Markierungsfehler)
Dies sind vordefinierte Fehler, die mit == verglichen werden können. Beispiel: io.EOF.
import ( "errors" "fmt")
var ErrNotFound = errors.New("Element nicht gefunden")
func findUser(id int) (string, error) { if id != 1 { return "", ErrNotFound } return "Alice", nil}
func main() { _, err := findUser(2) if errors.Is(err, ErrNotFound) { fmt.Println("Benutzer nicht gefunden, erstelle neuen...") }}3.2. Fehler-Wrapping (Umhüllen)
Seit Go 1.13 können Fehler mit %w in fmt.Errorf umhüllt werden. Dies bewahrt die Fehlerkette.
func readConfig(path string) error { data, err := os.ReadFile(path) if err != nil { return fmt.Errorf("Lesen der Konfiguration %s: %w", path, err) } // ... Verarbeitung von data