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 Fehlerkette 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 umschlossen ist, entpackt errors.Is() die Kette und findet darin ErrNotFound.
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)
}Verwende errors.Is() beim Prüfen auf Sentinel-Fehler und errors.As(), wenn du auf Felder eines benutzerdefinierten Fehlertyps zugreifen musst. Beide Funktionen durchlaufen die gesamte Fehlerkette, wodurch sie für die korrekte Behandlung umschlossener Fehler unerlässlich sind.
Aufgabe
EinfachErstellen wir ein Dateiverarbeitungssystem, das demonstriert, wie man eingeschlossene Fehlerketten mit errors.Is() und errors.As() ordnungsgemäß untersucht. Du wirst Fehler erstellen, die beim Durchlaufen verschiedener Schichten eingeschlossen werden, und anschließend diese Funktionen verwenden, um Informationen aus ihnen zu identifizieren und zu extrahieren.
Du wirst deinen Code auf zwei Dateien verteilen:
errors.go: Definiere die Fehlerinfrastruktur für dein Dateisystem.Erstelle einen Sentinel-Fehler:
ErrPermissionDeniedmit der Meldung"permission denied"
Erstelle einen benutzerdefinierten Fehlertyp:
- Eine
FileError-Struktur mit den FeldernFilename(string) undOperation(string) - Ihre
Error()-Methode soll Folgendes zurückgeben:[Operation] failed on file: [Filename]
Implementiere zwei Funktionen, die Dateioperationen simulieren und Fehler einschließen:
ReadFile(filename string) error– Wenn filename"secret.txt"ist, gibErrPermissionDeniedzurück. Wenn filename"missing.txt"ist, gib einen*FileErrormit der Operation"read"und dem angegebenen Filename zurück. Andernfalls gibnilzurück.ProcessFile(filename string) error– RuftReadFileauf. Wenn ein Fehler zurückgegeben wird, schließe ihn mitfmt.Errorfund dem Format"processing failed: %w"ein. Andernfalls gibnilzurück.
main.go: Lies einen Dateinamen aus der Eingabe und rufeProcessFileauf. Verwendeerrors.Is()underrors.As(), um die eingeschlossene Fehlerkette zu untersuchen und abhängig davon, was du darin findest, passende Meldungen auszugeben.
Die folgende Eingabe wird bereitgestellt:
- Zeile 1: Zu verarbeitender Dateiname
Verarbeite das Ergebnis, indem du die Fehlerkette untersuchst:
- Wenn
errors.Is()ErrPermissionDeniedin der Kette findet: Gib die vollständige Fehlermeldung aus und in einer neuen Zeile anschließendAccess denied - check file permissions - Wenn
errors.As()einen*FileErrorin der Kette findet: Gib die vollständige Fehlermeldung aus und in einer neuen Zeile anschließendFile issue: [Filename] during [Operation] - Wenn kein Fehler auftritt: Gib
File '[filename]' processed successfullyaus
Wenn beispielsweise secret.txt angegeben wird, sollte deine Ausgabe wie folgt aussehen:
processing failed: permission denied
Access denied - check file permissionsUnd wenn missing.txt angegeben wird, sollte deine Ausgabe wie folgt aussehen:
processing failed: read failed on file: missing.txt
File issue: missing.txt during readUnd wenn data.txt angegeben wird, sollte deine Ausgabe wie folgt aussehen:
File 'data.txt' processed successfullyBeachte, dass du den ursprünglichen Sentinel-Fehler mit ProcessFile weiterhin erkennen und den benutzerdefinierten Fehlertyp mit errors.Is() extrahieren kannst, obwohl die Fehler von errors.As() eingeschlossen werden. Diese Funktionen durchlaufen die gesamte Fehlerkette, um das zu finden, wonach du suchst.
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
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP in Go
Externe DateienGo-Workspace & ModulePackages & ImportsExportierte vs. nicht exportierte NamenEinführung in OOP mit GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktorfunktionenRückblick – Einfacher Taschenrechner4Schnittstellen
Einführung in SchnittstellenImplizite ImplementierungSchnittstelle als VertragLeere Schnittstelle (any)TypzusicherungTypwechselZusammensetzung von SchnittstellenStringer- und Error-SchnittstellenRückblick – Formenrechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf PaketebeneGetter- und Setter-MethodenInformationsverbergung in GoRückblick – Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterTypbeschränkungenGenerische StrukturenWorkaround für generische MethodenZusammenfassung – Generische Sammlung2Typen & Structs im Detail
Grundlegende & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct-TagsAnonyme StructsVerschachtelte StructsNullwerte & StandardwerteRückblick – Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen der Struct-EinbettungMethoden-PromotionMehrere Structs einbettenEinbettung vs. AggregationVerbergen eingebetteter MethodenRückblick – Mitarbeiterhierarchie8Fehlerbehandlung & OOP
Das Error-InterfaceBenutzerdefinierte FehlertypenError-Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverRückblick – Dateiparser3Zeiger & Speicher
Grundlagen von Zeigern in GoZeiger auf StructsÜbergabe per Wert vs. ReferenzDie Funktion new()Garbage Collection in GoRückblick – Verkettete Liste erstellen6Polymorphismus in Go
Polymorphismus über InterfacesDuck-Typing in GoRegeln zur Interface-ErfüllungPolymorphe SammlungenDependency InjectionZusammenfassung – Zahlungsprozessor9Konkurrenz & OOP
Grundlagen der GoroutinesChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignRückblick – Worker PoolÜbe selbstständig: Online-Go-Compiler