Lektionen des Moduls (2/5)
Idiomatische Fehlerbehandlung
Die Fehlerbehandlung in Go ist explizit: keine Ausnahmen, kein Try/Catch. Jede Funktion, die fehlschlagen kann, gibt error als letzten Wert zurück, und der Aufrufer entscheidet. In diesem Kapitel werden die idiomatischen Muster gesammelt, um Fehler auf robuste Weise zu erzeugen, zu verbreiten und zu prüfen.
Die goldene Regel
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("config %s: %w", path, err)
}Vier Prinzipien:
errorist die letzte Rückgabe.- Sofort prüfen mit
if err != nil. - Kontext hinzufügen vor der Weitergabe (mit
fmt.Errorf+%w). - Ignorieren Sie niemals einen Fehler: Behandeln Sie ihn entweder, protokollieren Sie ihn oder geben Sie ihn zurück.
_ = file.Close() // EXPLICITLY ignored (you know what you're doing)Sentinel-Fehler
Ein Sentinel-Fehler ist eine exportierte globale Variable var ErrXxx = errors.New("..."), mit der der Aufrufer vergleichen kann:
package store
var ErrNotFound = errors.New("not found")
func Get(key string) (string, error) {
if !exists(key) {
return "", ErrNotFound
}
// ...
}Der Aufrufer verwendet errors.Is (NICHT ==), der die Wrap-Kette durchläuft:
v, err := store.Get(k)
if errors.Is(err, store.ErrNotFound) {
// handle 404
}Standardbeispiele: io.EOF, sql.ErrNoRows, os.ErrNotExist.
Benutzerdefinierte eingegebene Fehler
Um strukturierte Daten (HTTP-Code, ungültiges Feld usw.) zu übertragen, definieren Sie einen Typ:
type ValidationError struct {
Field string
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}Der Aufrufer verwendet errors.As, um einen sicheren Downcast entlang der Kette durchzuführen:
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Println("invalid field:", ve.Field)
}errors.As durchläuft die Unwrap()-Kette, bis es einen Fehler findet, der dem übergebenen Zeiger zuordenbar ist.
Umbruch mit %w
fmt.Errorf mit dem Verb %w erzeugt einen Fehler, der das Original umschließt und es für errors.Is/errors.As behält:
if err := db.Query(); err != nil {
return fmt.Errorf("load users: %w", err)
}Ohne %w (mit %v oder %s) erhalten Sie einen umfangreicheren String, verlieren aber die Kette: Der Aufrufer kann bestimmte Fehler nicht mehr erkennen.
Regeln für %w:
- Nur einer pro
fmt.Errorf-Aufruf (Multi-Wrap erforderterrors.Join). - Das Argument MUSS ein
errorsein, kein String. - Fügen Sie Kontext hinzu, duplizieren Sie nicht die ursprüngliche Nachricht.
// bad: duplicates
return fmt.Errorf("error: %w", err) // "error: file not found"
// good: useful context
return fmt.Errorf("load config %s: %w", path, err)errors.Join für mehrere Fehler
Seit Go 1.20 kombiniert errors.Join(errs...) mehrere Fehler zu einem, sodass sie alle von errors.Is/As erkennbar bleiben:
var errs []error
for _, item := range items {
if err := process(item); err != nil {
errs = append(errs, fmt.Errorf("item %s: %w", item.ID, err))
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}Wann sollte panic verwendet werden?
Niemals wegen erwarteter Fehler. Nur in drei Fällen:
- Nicht behebbarer Fehler: Eine Invariante wurde an einer Stelle verletzt, die „nicht passieren kann“ (
panic("unreachable")). - Initialisierung fehlgeschlagen in
init()/main()eines eigenständigen Programms (kritische Konfiguration fehlt → schlägt schnell fehl). - Bibliotheks-API, die es explizit dokumentiert: z. B.
regexp.MustCompile(gerät in Panik, wenn das Muster ungültig ist – akzeptabel, da das Muster eine Build-Zeit-Konstante ist).
Alles andere ist error. Verwenden Sie panic nicht als Verknüpfung, um sich nach oben auszubreiten: Es unterbricht die Komposition, überspringt defer Close() und überfordert den Aufrufer.
Übungen
Schließen Sie den Fehler von os.ReadFile mit fmt.Errorf ab, verwenden Sie das Wort %w und fügen Sie den Wettbewerb „config:“ hinzu (mit dem Pfad).
Lösung nach 3 Versuchen verfügbar
Usa-Fehler.Wird überprüft, ob in der nächsten Zeile ein Fehler aufgetreten ist, der mit dem ErrNotFound-Hinweis korrespondiert und mit „manca!“ versehen ist. Im Großen und Ganzen.
Lösung nach 3 Versuchen verfügbar
Was ist die idiomatische Verwendung von Panik in einer Bibliothek?
func F(x int) {
// ??? panic(...)
}