0Pricing
Go Academy · Lekcja

Opakowywanie i rozpakowywanie błędów

Używanie %w, errors.Is i errors.As

Opakowywanie i rozpakowywanie błędów to bezpłatna lekcja Go Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Go Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Go Academy zawiera 4 lekcji w sumie.

Czym jest opakowywanie błędów?

Opakowywanie błędów osadza oryginalny błąd w nowym błędzie, zachowując oryginał na potrzeby programowej analizy i jednocześnie dodając kontekst. Mechanizm został właściwie wprowadzony w Go 1.13:

package main
import ("fmt"; "errors")

var ErrDB = errors.New("database unreachable")

func queryUsers() error {
    return fmt.Errorf("queryUsers: %w", ErrDB)
}

func loadProfile(id int) error {
    return fmt.Errorf("loadProfile(%d): %w", id, queryUsers())
}

func main() {
    err := loadProfile(42)
    fmt.Println(err) // loadProfile(42): queryUsers: database unreachable
}

Specyfikator %w

Używaj %w (a nie %v) w fmt.Errorf, aby opakować błąd. Do opakowanego błędu można uzyskać dostęp za pomocą errors.Unwrap:

package main
import ("fmt"; "errors")

func main() {
    inner := errors.New("inner error")
    outer := fmt.Errorf("outer: %w", inner)

    fmt.Println(outer)              // outer: inner error
    fmt.Println(errors.Unwrap(outer)) // inner error
    fmt.Println(errors.Is(outer, inner)) // true
}

errors.Unwrap

errors.Unwrap zwraca następny błąd w łańcuchu (opakowany błąd) albo nil, jeśli go nie ma:

package main
import ("fmt"; "errors")

type Layer struct{ msg string; err error }
func (l *Layer) Error() string { return l.msg }
func (l *Layer) Unwrap() error { return l.err }

func main() {
    root := errors.New("root")
    mid  := &Layer{"mid", root}
    top  := &Layer{"top", mid}

    fmt.Println(errors.Unwrap(top))  // mid
    fmt.Println(errors.Unwrap(mid))  // root
    fmt.Println(errors.Unwrap(root)) // <nil>
}

errors.Is — przechodzenie po łańcuchu

errors.Is(err, target) rekurencyjnie rozpakowuje łańcuch błędów, aż znajdzie dopasowanie albo dotrze do nil:

package main
import ("fmt"; "errors")

var ErrNotFound = errors.New("not found")

func findItem(id int) error {
    return fmt.Errorf("findItem(%d): %w",
        id, fmt.Errorf("repo: %w", ErrNotFound))
}

func main() {
    err := findItem(7)
    fmt.Println(errors.Is(err, ErrNotFound)) // true — deep in chain
    fmt.Println(err)                         // full message chain
}

errors.As — przechodzenie po łańcuchu w poszukiwaniu typów

errors.As(err, &target) przechodzi po łańcuchu w poszukiwaniu błędu, który można przypisać do typu wartości docelowej:

package main
import ("fmt"; "errors")

type StatusError struct{ Code int }
func (e *StatusError) Error() string { return fmt.Sprintf("status %d", e.Code) }

func doRequest() error {
    return fmt.Errorf("request: %w", &StatusError{503})
}

func main() {
    err := doRequest()
    var se *StatusError
    if errors.As(err, &se) {
        fmt.Println("status code:", se.Code) // 503
    }
}

Własna metoda Unwrap dla łańcuchów

Zaimplementuj Unwrap() error we własnym typie błędu, aby obsługiwać errors.Is i errors.As:

package main
import ("fmt"; "errors")

type AppError struct {
    Op  string
    Err error
}
func (e *AppError) Error() string  { return fmt.Sprintf("%s: %v", e.Op, e.Err) }
func (e *AppError) Unwrap() error  { return e.Err }

var ErrAuth = errors.New("unauthorized")

func main() {
    err := &AppError{"login", ErrAuth}
    fmt.Println(errors.Is(err, ErrAuth)) // true
}

%v a %w w fmt.Errorf

Różnica między %v a %w:

package main
import ("fmt"; "errors")

func main() {
    inner := errors.New("inner")

    withV := fmt.Errorf("outer: %v", inner) // string only, no wrap
    withW := fmt.Errorf("outer: %w", inner) // wraps inner

    fmt.Println(errors.Is(withV, inner)) // false — not wrapped
    fmt.Println(errors.Is(withW, inner)) // true  — wrapped
}

Opakowywanie w praktyce: moduł obsługi HTTP

Konsekwentne opakowywanie błędów ułatwia debugowanie systemów produkcyjnych:

package main
import "fmt"

func dbQuery(sql string) error {
    return fmt.Errorf("db.Query(%q): connection refused", sql)
}

func getUserByID(id int) (string, error) {
    if err := dbQuery("SELECT * FROM users WHERE id=?"); err != nil {
        return "", fmt.Errorf("getUserByID(%d): %w", id, err)
    }
    return "Alice", nil
}

func handler() error {
    _, err := getUserByID(1)
    if err != nil {
        return fmt.Errorf("handler: %w", err)
    }
    return nil
}

func main() { fmt.Println(handler()) }

errors.Join — wiele opakowań (Go 1.20+)

errors.Join tworzy błąd, który opakowuje wiele błędów. errors.Is sprawdza wszystkie z nich:

package main
import ("fmt"; "errors")

var ErrA = errors.New("err A")
var ErrB = errors.New("err B")

func main() {
    joined := errors.Join(ErrA, ErrB)
    fmt.Println(joined)
    fmt.Println(errors.Is(joined, ErrA)) // true
    fmt.Println(errors.Is(joined, ErrB)) // true
}

Dobre praktyki rozpakowywania błędów

Wskazówki dotyczące opakowywania błędów:

  • Opakowuj za pomocą %w, aby zachować błąd na potrzeby programowej analizy
  • Dodawaj nazwę operacji jako kontekst: fmt.Errorf("funcName: %w", err)
  • Nie opakowuj błędów sentinelowych, jeśli kod wywołujący nie będzie używać errors.Is
  • Unikaj wielokrotnego opakowywania tego samego kontekstu na każdym poziomie
  • Implementuj Unwrap() we własnych typach, aby uczestniczyły w łańcuchach

Szybkie sprawdzenie

Jaka jest różnica między użyciem %v i %w w fmt.Errorf?

Podsumowanie: opakowywanie i rozpakowywanie błędów

Podsumowanie:

  • fmt.Errorf("ctx: %w", err) opakowuje błąd wraz z kontekstem
  • errors.Unwrap pobiera następny błąd w łańcuchu
  • errors.Is przechodzi po łańcuchu w poszukiwaniu zgodności tożsamości
  • errors.As przechodzi po łańcuchu w poszukiwaniu zgodności typu
  • Typy własne implementują Unwrap() error
  • errors.Join opakowuje wiele błędów (Go 1.20+)

Często zadawane pytania

Czy lekcja „Opakowywanie i rozpakowywanie błędów” jest bezpłatna?

Tak — pełny tekst „Opakowywanie i rozpakowywanie błędów” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Go Academy, przejdź na CoddyKit PRO. Kurs Go Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Opakowywanie i rozpakowywanie błędów”?

Używanie %w, errors.Is i errors.As Ćwiczysz Go Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Go Academy?

Nie wymagamy żadnego doświadczenia. Go Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Opakowywanie i rozpakowywanie błędów”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Go Academy?

Tak. Każda lekcja Go Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Interfejs error
  2. Tworzenie własnych błędów
  3. Opakowywanie i rozpakowywanie błędów
  4. panic, recover i defer
← Powrót do Go Academy