Menu
Coddy logo textTech

errors.Is() i errors.As()

Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 56 z 107.

Gdy błędy są opakowywane, proste sprawdzenia równości, takie jak err == ErrNotFound, przestają działać. Pakiet errors udostępnia dwie funkcje do sprawdzania łańcuchów opakowanych błędów: errors.Is() i errors.As().

errors.Is() sprawdza, czy którykolwiek błąd w łańcuchu pasuje do konkretnego błędu wartowniczego:

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")
}

Mimo że zwrócony błąd jest opakowany, errors.Is() rozwija łańcuch i znajduje w nim ErrNotFound.

errors.As() sprawdza, czy którykolwiek błąd w łańcuchu pasuje do określonego typu, i wyodrębnia go:

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)
}

Używaj errors.Is() do sprawdzania błędów wartowniczych, a errors.As(), gdy potrzebujesz uzyskać dostęp do pól niestandardowego typu błędu. Obie funkcje przechodzą przez cały łańcuch błędów, dzięki czemu są niezbędne do prawidłowej obsługi opakowanych błędów.

challenge icon

Wyzwanie

Łatwy

Zbudujemy system przetwarzania plików, który pokaże, jak prawidłowo sprawdzać opakowane łańcuchy błędów za pomocą errors.Is() i errors.As(). Utworzysz błędy, które będą opakowywane podczas przechodzenia przez różne warstwy, a następnie użyjesz tych funkcji, aby je zidentyfikować i wyodrębnić z nich informacje.

Rozmieścisz kod w dwóch plikach:

  • errors.go: Zdefiniuj infrastrukturę błędów dla systemu plików.

    Utwórz błąd wartowniczy:

    • ErrPermissionDenied z komunikatem "permission denied"

    Utwórz własny typ błędu:

    • strukturę FileError z polami Filename (string) i Operation (string)
    • jej metoda Error() powinna zwracać: [Operation] failed on file: [Filename]

    Zaimplementuj dwie funkcje, które symulują operacje na plikach i opakowują błędy:

    • ReadFile(filename string) error - Jeśli filename ma wartość "secret.txt", zwróć ErrPermissionDenied. Jeśli filename ma wartość "missing.txt", zwróć *FileError z Operation o wartości "read" i przekazanym Filename. W przeciwnym razie zwróć nil.
    • ProcessFile(filename string) error - Wywołuje ReadFile. Jeśli zostanie zwrócony błąd, opakuj go za pomocą fmt.Errorf z formatem "processing failed: %w". W przeciwnym razie zwróć nil.
  • main.go: Odczytaj nazwę pliku z wejścia i wywołaj ProcessFile. Użyj errors.Is() i errors.As(), aby sprawdzić opakowany łańcuch błędów i wypisać odpowiednie komunikaty na podstawie tego, co znajdziesz w środku.

Otrzymasz następujące dane wejściowe:

  • Wiersz 1: nazwa pliku do przetworzenia

Obsłuż wynik, sprawdzając łańcuch błędów:

  • Jeśli errors.Is() znajdzie ErrPermissionDenied w łańcuchu: wypisz pełny komunikat błędu, a następnie w nowym wierszu wypisz Access denied - check file permissions
  • Jeśli errors.As() znajdzie w łańcuchu *FileError: wypisz pełny komunikat błędu, a następnie w nowym wierszu wypisz File issue: [Filename] during [Operation]
  • Jeśli nie wystąpi błąd: wypisz File '[filename]' processed successfully

Na przykład dla secret.txt wynik powinien wyglądać następująco:

processing failed: permission denied
Access denied - check file permissions

Dla missing.txt wynik powinien wyglądać następująco:

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

A dla data.txt wynik powinien wyglądać następująco:

File 'data.txt' processed successfully

Zwróć uwagę, że mimo iż błędy są opakowywane przez ProcessFile, nadal możesz wykryć pierwotny błąd wartowniczy za pomocą errors.Is() i wyodrębnić własny typ błędu za pomocą errors.As(). Te funkcje przechodzą przez cały łańcuch błędów, aby znaleźć to, czego szukasz.

Spróbuj swoich sił

package main

import (
	"errors"
	"fmt"
)

func main() {
	// Odczytaj nazwę pliku z wejścia
	var filename string
	fmt.Scanln(&filename)

	// Wywołaj ProcessFile z nazwą pliku
	err := ProcessFile(filename)

	// TODO: Obsłuż wynik, analizując łańcuch błędów
	// 
	// Użyj errors.Is(), aby sprawdzić, czy ErrPermissionDenied znajduje się w łańcuchu:
	// - Jeśli znaleziono: wypisz pełny błąd, a następnie "Access denied - check file permissions"
	//
	// Użyj errors.As(), aby sprawdzić, czy *FileError znajduje się w łańcuchu:
	// - Jeśli znaleziono: wypisz pełny błąd, a następnie "File issue: [Filename] during [Operation]"
	//
	// Jeśli nie ma błędu: wypisz "File '[filename]' processed successfully"

	_ = errors.Is // wskazówka: użyj errors.Is()
	_ = errors.As // wskazówka: użyj errors.As()
	_ = err
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online