Przejdź do głównej treści
eLearner.app
Moduł 10 · Lekcja 2 z 547/50 w kursie~14 min
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

Go
data, err := os.ReadFile(path)
if err != nil {
    return fmt.Errorf("config %s: %w", path, err)
}

Cztery zasady:

  1. error to ostatni powrót.
  2. Sprawdź natychmiast za pomocą if err != nil.
  3. Dodaj kontekst przed propagacją (za pomocą fmt.Errorf + %w).
  4. Nigdy nie ignoruj błędu: zajmij się nim, zarejestruj go lub zwróć.
Go
_ = 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ć:

Go
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:

Go
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:

Go
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:

Go
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:

Go
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 wymaga errors.Join).
  • Argument MUSI być error, a nie ciągiem znaków.
  • Dodaj kontekst, nie powielaj oryginalnej wiadomości.
Go
// 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:

Go
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:

  1. Błąd nie do naprawienia: naruszony niezmiennik w miejscu, które „nie może się zdarzyć” (panic("unreachable")).
  2. Nieudana inicjalizacja w init()/main() samodzielnego programu (brak krytycznej konfiguracji → szybka awaria).
  3. 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

Ćwiczenie#go.m10.l2.e1
Próby: 0Ładowanie...

Zapisz błąd os.ReadFile z fmt.Errorf za pomocą verbo %w i dołącz do konkursu „config:” (ścieżka do pliku).

Ładowanie edytora...

Rozwiązanie dostępne po 3 próbach

Ćwiczenie#go.m10.l2.e2
Próby: 0Ładowanie...

Usa error.Is per verificare se err nella catena di wrap corrisponde alla sentinella ErrNotFound e stampa 'manca!' w sumie.

Ładowanie edytora...

Rozwiązanie dostępne po 3 próbach

Quiz#go.m10.l2.e3
Gotowe

Quando jest idiomatycznym użyciem paniki w bibliotece Go?

Go
func F(x int) {
  // ??? panic(...)
}
Opcje odpowiedzi