Menu
Coddy logo textTech

errors.Is() und errors.As()

Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 56 von 107.

Wenn Fehler verpackt werden, funktionieren einfache Gleichheitsprüfungen wie err == ErrNotFound nicht mehr. Das errors-Paket bietet zwei Funktionen zur Untersuchung von verpackten Fehlerketten an: errors.Is() und errors.As().

errors.Is() prüft, ob ein Fehler in der Kette mit einem bestimmten Sentinel-Fehler übereinstimmt:

import "errors"

var ErrNotFound = errors.New("not found")

func FindUser(id int) error {
    return fmt.Errorf("database lookup failed: %w", ErrNotFound)
}

err := FindUser(42)
if errors.Is(err, ErrNotFound) {
    fmt.Println("User doesn't exist")
}

Obwohl der zurückgegebene Fehler eingepackt (wrapped) ist, entpackt errors.Is() die Kette und findet ErrNotFound darin.

errors.As() prüft, ob ein Fehler in der Kette mit einem bestimmten Typ übereinstimmt und extrahiert diesen:

type ValidationError struct {
    Field string
}

func (e *ValidationError) Error() string {
    return "validation failed: " + e.Field
}

err := fmt.Errorf("request failed: %w", &ValidationError{Field: "email"})

var ve *ValidationError
if errors.As(err, &ve) {
    fmt.Println("Invalid field:", ve.Field)
}

Verwenden Sie errors.Is() beim Prüfen auf Sentinel-Fehler und errors.As(), wenn Sie auf Felder eines benutzerdefinierten Fehlertyps zugreifen müssen. Beide Funktionen durchlaufen die gesamte Fehlerkette, was sie für die korrekte Behandlung von gewrappten Fehlern (wrapped errors) unerlässlich macht.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Dateiverarbeitungssystem erstellen, das demonstriert, wie man verschachtelte Fehlerketten (wrapped error chains) mit errors.Is() und errors.As() korrekt inspiziert. Sie werden Fehler erstellen, die beim Durchlaufen verschiedener Ebenen eingepackt (wrapped) werden, und dann diese Funktionen verwenden, um sie zu identifizieren und Informationen daraus zu extrahieren.

Sie werden Ihren Code in zwei Dateien organisieren:

  • errors.go: Definieren Sie die Fehler-Infrastruktur für Ihr Dateisystem.

    Erstellen Sie einen Sentinel-Fehler:

    • ErrPermissionDenied mit der Nachricht "permission denied"

    Erstellen Sie einen benutzerdefinierten Fehlertyp:

    • FileError Struct mit den Feldern Filename (string) und Operation (string)
    • Die Methode Error() sollte Folgendes zurückgeben: [Operation] failed on file: [Filename]

    Implementieren Sie zwei Funktionen, die Dateioperationen simulieren und Fehler einpacken:

    • ReadFile(filename string) error - Wenn der Dateiname "secret.txt" ist, geben Sie ErrPermissionDenied zurück. Wenn der Dateiname "missing.txt" ist, geben Sie einen *FileError mit der Operation "read" und dem angegebenen Dateinamen zurück. Andernfalls geben Sie nil zurück.
    • ProcessFile(filename string) error - Ruft ReadFile auf. Wenn ein Fehler zurückgegeben wird, packen Sie ihn mit fmt.Errorf und dem Format "processing failed: %w" ein. Andernfalls geben Sie nil zurück.
  • main.go: Lesen Sie einen Dateinamen von der Eingabe ein und rufen Sie ProcessFile auf. Verwenden Sie errors.Is() und errors.As(), um die verschachtelte Fehlerkette zu inspizieren und basierend auf dem Inhalt entsprechende Nachrichten auszugeben.

Die folgende Eingabe wird bereitgestellt:

  • Zeile 1: Zu verarbeitender Dateiname

Behandeln Sie das Ergebnis, indem Sie die Fehlerkette inspizieren:

  • Wenn errors.Is() den Fehler ErrPermissionDenied in der Kette findet: Geben Sie die vollständige Fehlermeldung aus und in einer neuen Zeile Access denied - check file permissions
  • Wenn errors.As() einen *FileError in der Kette findet: Geben Sie die vollständige Fehlermeldung aus und in einer neuen Zeile File issue: [Filename] during [Operation]
  • Wenn kein Fehler auftritt: Geben Sie File '[filename]' processed successfully aus

Zum Beispiel sollte Ihre Ausgabe bei secret.txt wie folgt aussehen:

processing failed: permission denied
Access denied - check file permissions

Und bei missing.txt sollte Ihre Ausgabe wie folgt aussehen:

processing failed: read failed on file: missing.txt
File issue: missing.txt during read

Und bei data.txt sollte Ihre Ausgabe wie folgt aussehen:

File 'data.txt' processed successfully

Beachten Sie, dass Sie, obwohl die Fehler von ProcessFile eingepackt wurden, den ursprünglichen Sentinel-Fehler mit errors.Is() erkennen und den benutzerdefinierten Fehlertyp mit errors.As() extrahieren können. Diese Funktionen durchlaufen die gesamte Fehlerkette, um das Gesuchte zu finden.

Spickzettel

Wenn Fehler verschachtelt (wrapped) sind, verwenden Sie errors.Is() und errors.As() aus dem errors-Paket, um die Fehlerkette zu untersuchen.

errors.Is() prüft, ob ein Fehler in der Kette mit einem Sentinel-Fehler übereinstimmt:

import "errors"

var ErrNotFound = errors.New("not found")

err := fmt.Errorf("database lookup failed: %w", ErrNotFound)
if errors.Is(err, ErrNotFound) {
    // ErrNotFound wurde in der Kette gefunden
}

errors.As() prüft, ob ein Fehler in der Kette einem bestimmten Typ entspricht, und extrahiert diesen:

type ValidationError struct {
    Field string
}

func (e *ValidationError) Error() string {
    return "validation failed: " + e.Field
}

err := fmt.Errorf("request failed: %w", &ValidationError{Field: "email"})

var ve *ValidationError
if errors.As(err, &ve) {
    // ve enthält nun den ValidationError aus der Kette
    fmt.Println(ve.Field)
}

Verwenden Sie errors.Is() für Sentinel-Fehler und errors.As(), wenn Sie auf Felder von benutzerdefinierten Fehlertypen zugreifen müssen. Beide Funktionen durchlaufen die gesamte Fehlerkette.

Probier es selbst

package main

import (
	"errors"
	"fmt"
)

func main() {
	// Dateinamen von der Eingabe lesen
	var filename string
	fmt.Scanln(&filename)

	// ProcessFile mit dem Dateinamen aufrufen
	err := ProcessFile(filename)

	// TODO: Das Ergebnis durch Überprüfung der Fehlerkette verarbeiten
	// 
	// Verwende errors.Is(), um zu prüfen, ob ErrPermissionDenied in der Kette enthalten ist:
	// - Wenn gefunden: Den vollständigen Fehler ausgeben, dann "Access denied - check file permissions"
	//
	// Verwende errors.As(), um zu prüfen, ob ein *FileError in der Kette enthalten ist:
	// - Wenn gefunden: Den vollständigen Fehler ausgeben, dann "File issue: [Filename] during [Operation]"
	//
	// Wenn kein Fehler: "File '[filename]' processed successfully" ausgeben

	_ = errors.Is // Hinweis: errors.Is() verwenden
	_ = errors.As // Hinweis: errors.As() verwenden
	_ = err
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung