Historia pytania:
Do Go 1.13 błąd był prostym interfejsem. Aby dodać dodatkowy kontekst błędu, często tworzono własne typy, ale nie było zorganizowanego mechanizmu zagnieżdżania, jak w nowoczesnym Javie czy C#. Wraz z wprowadzeniem Go 1.13 wprowadzono standardowy sposób owijania (wrapping) błędów za pomocą funkcji fmt.Errorf i identyfikacji przyczyny ( errors.Is/errors.As).
Problem:
W złożonych aplikacjach, w których błąd może wystąpić na różnych poziomach stosu, ważne jest, aby nie tracić kontekstu przy zwracaniu błędu z głębi kodu. W przeciwnym razie debugowanie staje się trudne i nie można zrozumieć, gdzie w łańcuchu wystąpił błąd.
Rozwiązanie:
Go proponuje owijanie błędów w nowe obiekty błędów, które zawierają przyczynę. Do tego używa się %w w fmt.Errorf, a do sprawdzania głębszych przyczyn — errors.Is lub errors.As z pakietu errors.
Przykład kodu:
import ( "errors" "fmt" ) var ErrNotFound = errors.New("nie znaleziono") func getData() error { return fmt.Errorf("baza danych usługi: %w", ErrNotFound) } func main() { err := getData() if errors.Is(err, ErrNotFound) { fmt.Println("Wykryto przyczynę: nie znaleziono") } else { fmt.Println("Inny błąd:", err) } }
Kluczowe cechy:
%w do zagnieżdżania błędów za pomocą fmt.Errorf.errors.Is i errors.As.Jaki operator formatowania jest potrzebny do owijania błędów za pomocą fmt.Errorf?
Słusznie używać %w, a nie %v — tylko %w zapewnia wsparcie dla rozwijania.
Przykład kodu:
fmt.Errorf("błąd: %w", err)
Czy można ręcznie tworzyć łańcuchy błędów bez fmt.Errorf i nadal je identyfikować za pomocą errors.Is?
Nie, trzeba zaimplementować interfejs Unwrap, w przeciwnym razie standardowe funkcje nie rozwinią łańcucha.
Przykład kodu interfejsu:
type wrappedError struct { msg string err error } func (w wrappedError) Error() string { return w.msg + ": " + w.err.Error() } func (w wrappedError) Unwrap() error { return w.err }
Czy błędy zwracają wartości nil po owinięciu, jeśli zagnieżdżony błąd był nil?
Nie, jeśli zagnieżdżony błąd jest nil, to owinięty błąd też jest nil tylko przy poprawnym użyciu. Ale jeśli stworzyć strukturę wrappera ręcznie, w której pole błędu jest nil, będzie to nie nil. To często prowadzi do zamieszania przy sprawdzaniu.
%w, a tylko %v — traci się możliwość analizy łańcucha.Unwrap() dla własnych struktur błędów.errors.Is/As, a tylko porównywać błąd bezpośrednio.W aplikacji po prostu zwracano nowy błąd z tekstem na każdym poziomie:
return errors.New("błąd zapisu do bazy danych")
Zalety:
Wady:
Zastosowano owijanie błędów z %w i analizę przez errors.Is:
if errors.Is(err, ErrNotFound) { return fmt.Errorf("błąd na poziomie usługi: %w", err) }
Zalety:
Wady: