Lekcje modułu (2/5)
Idiomatyczna obsługa błędów
Obsługa błędów w Go jest wyraźna: bez wyjątków, bez prób/catchów. Każda funkcja, która może zawieść, zwraca error jako ostatnią wartość, a osoba wywołująca decyduje. W tym rozdziale zebrano wzorce idiomatyczne służące do tworzenia, propagowania i sprawdzania błędów w niezawodny sposób.
Złota zasada
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("config %s: %w", path, err)
}Cztery zasady:
errorto ostatni powrót.- Sprawdź natychmiast za pomocą
if err != nil. - Dodaj kontekst przed propagacją (za pomocą
fmt.Errorf+%w). - Nigdy nie ignoruj błędu: zajmij się nim, zarejestruj go lub zwróć.
_ = file.Close() // EXPLICITLY ignored (you know what you're doing)Błędy Sentinela
Błąd wartowniczy to wyeksportowana zmienna globalna var ErrXxx = errors.New("..."), z którą osoba wywołująca może porównać:
package store
var ErrNotFound = errors.New("not found")
func Get(key string) (string, error) {
if !exists(key) {
return "", ErrNotFound
}
// ...
}Osoba wywołująca używa errors.Is (NIE ==), który przechodzi przez łańcuch zawijania:
v, err := store.Get(k)
if errors.Is(err, store.ErrNotFound) {
// handle 404
}Standardowe przykłady: io.EOF, sql.ErrNoRows, os.ErrNotExist.
Niestandardowe błędy wpisane
Aby przenosić dane strukturalne (kod HTTP, nieprawidłowe pole itp.), zdefiniuj typ:
type ValidationError struct {
Field string
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}Osoba wywołująca używa errors.As do bezpiecznego przesyłania w dół łańcucha:
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Println("invalid field:", ve.Field)
}errors.As przechodzi przez łańcuch Unwrap(), aż znajdzie błąd, który można przypisać do przekazanego wskaźnika.
Zawijanie za pomocą %w
fmt.Errorf z czasownikiem %w powoduje błąd, który zawija oryginalny, zachowując go dla errors.Is/errors.As:
if err := db.Query(); err != nil {
return fmt.Errorf("load users: %w", err)
}Bez %w (przy użyciu %v lub %s) otrzymasz bogatszy ciąg, ale stracisz łańcuch: osoba wywołująca nie może już rozpoznać określonych błędów.
Zasady dla %w:
- Tylko jedno na wywołanie
fmt.Errorf(wielokrotne zawijanie wymagaerrors.Join). - Argument MUSI być
error, a nie ciągiem znaków. - Dodaj kontekst, nie powielaj oryginalnej wiadomości.
// bad: duplicates
return fmt.Errorf("error: %w", err) // "error: file not found"
// good: useful context
return fmt.Errorf("load config %s: %w", path, err)errors.Join dla wielu błędów
Od wersji 1.20 errors.Join(errs...) łączy wiele błędów w jeden, dzięki czemu są one wykrywalne przez errors.Is/As:
var errs []error
for _, item := range items {
if err := process(item); err != nil {
errs = append(errs, fmt.Errorf("item %s: %w", item.ID, err))
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}Kiedy używać panic
Nigdy w przypadku oczekiwanych błędów. Tylko w trzech przypadkach:
- Błąd nie do naprawienia: naruszony niezmiennik w miejscu, które „nie może się zdarzyć” (
panic("unreachable")). - Nieudana inicjalizacja w
init()/main()samodzielnego programu (brak krytycznej konfiguracji → szybka awaria). - Bibliotekowy interfejs API, który wyraźnie to dokumentuje: np.
regexp.MustCompile(wpada w panikę, jeśli wzorzec jest nieprawidłowy — akceptowalny, ponieważ wzorzec jest stałą czasową kompilacji).
Wszystko inne to error. Nie używaj panic jako skrótu do propagacji: psuje to kompozycję, pomija defer Close() i przytłacza rozmówcę.
Ćwiczenia
Zapisz błąd os.ReadFile z fmt.Errorf za pomocą verbo %w i dołącz do konkursu „config:” (ścieżka do pliku).
Rozwiązanie dostępne po 3 próbach
Usa error.Is per verificare se err nella catena di wrap corrisponde alla sentinella ErrNotFound e stampa 'manca!' w sumie.
Rozwiązanie dostępne po 3 próbach
Quando jest idiomatycznym użyciem paniki w bibliotece Go?
func F(x int) {
// ??? panic(...)
}