programowanieProgramista Backend

Opowiedz, jak działa obsługa błędów za pomocą pakietu error wrapping w Go: czym jest obwrapping błędów, dlaczego to ważne i jak poprawnie implementować zagnieżdżanie błędów?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

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:

  • Używanie %w do zagnieżdżania błędów za pomocą fmt.Errorf.
  • Identyfikacja zagnieżdżonych przyczyn błędów za pomocą errors.Is i errors.As.
  • Kompatybilność z niestandardowymi typami błędów przy zagnieżdżaniu i rozwijaniu.

Pytania z haczykiem.

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.

Typowe błędy i antywzorce

  • Nie używać %w, a tylko %v — traci się możliwość analizy łańcucha.
  • Nie implementować Unwrap() dla własnych struktur błędów.
  • Nie robić sprawdzania przez errors.Is/As, a tylko porównywać błąd bezpośrednio.
  • Tworzenie nowych błędów bez przyczyny (utrata kontekstu).

Przykład z życia

Negatywny przypadek

W aplikacji po prostu zwracano nowy błąd z tekstem na każdym poziomie:

return errors.New("błąd zapisu do bazy danych")

Zalety:

  • Prosto i szybko.

Wady:

  • Niemożliwe jest odróżnienie, że błąd to tak naprawdę "nie znaleziono" z głębi, co może złamać logikę biznesową na górze.
  • Utrata informacji o stosie i źródle błędu.

Pozytywny przypadek

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:

  • Poprawnie określa przyczynę na każdym poziomie.
  • Łatwiejsze debugowanie, zawsze widoczny jest oryginalny kontekst.

Wady:

  • Wymaga zrozumienia, jak działa owijanie i odpowiedniego pisania błędów.