0Pricing
Go Academy · Урок

Обёртывание и распаковка ошибок

Использование %w, errors.Is и errors.As

«Обёртывание и распаковка ошибок» — бесплатный урок Go Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Go Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Go Academy содержит 4 уроков всего.

Что такое обёртывание ошибок?

Обёртывание ошибки помещает исходную ошибку внутрь новой, сохраняя исходную для программной проверки и одновременно добавляя контекст. Полноценная поддержка появилась в 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
}

Спецификатор %w

Используйте %w (а не %v) в fmt.Errorf, чтобы обернуть ошибку. Обёрнутую ошибку можно получить с помощью 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 возвращает следующую ошибку в цепочке — обёрнутую ошибку — или nil, если следующей ошибки нет:

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 — обход цепочки

errors.Is(err, target) рекурсивно снимает обёртки в цепочке ошибок, пока не найдёт совпадение или не достигнет 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 — обход цепочки для типов

errors.As(err, &target) проходит по цепочке в поисках ошибки, присваиваемой типу target:

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
    }
}

Пользовательский Unwrap для цепочек

Реализуйте Unwrap() error в своём пользовательском типе ошибки, чтобы поддержать errors.Is и 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 и %w в fmt.Errorf

Разница между %v и %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
}

Обёртывание на практике: обработчик HTTP

Последовательное обёртывание ошибок упрощает отладку в рабочих системах:

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 — несколько обёрток (Go 1.20+)

errors.Join создаёт ошибку, которая оборачивает несколько ошибок. errors.Is проверяет их все:

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
}

Рекомендации по снятию обёрток с ошибок

Рекомендации по обёртыванию ошибок:

  • Оборачивайте с помощью %w, чтобы сохранить ошибку для программной проверки
  • Добавляйте имя операции для контекста: fmt.Errorf("funcName: %w", err)
  • Не оборачивайте ошибки-маркеры, если вызывающий код не будет использовать errors.Is
  • Избегайте повторного добавления одного и того же контекста на каждом уровне
  • Реализуйте Unwrap() в пользовательских типах, чтобы участвовать в цепочках

Быстрая проверка

В чём разница между использованием %v и %w в fmt.Errorf?

Повторение: обёртывание и снятие обёрток с ошибок

Краткое содержание:

  • fmt.Errorf("ctx: %w", err) оборачивает ошибку, добавляя контекст
  • errors.Unwrap извлекает следующую ошибку в цепочке
  • errors.Is проходит по цепочке в поисках совпадения идентичности
  • errors.As проходит по цепочке в поисках совпадения типа
  • Пользовательские типы реализуют Unwrap() error
  • errors.Join оборачивает несколько ошибок (Go 1.20+)

Часто задаваемые вопросы

Урок «Обёртывание и распаковка ошибок» бесплатный?

Да — полный текст урока «Обёртывание и распаковка ошибок» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Go Academy, подпишись на CoddyKit PRO. Курс Go Academy содержит 4 уроков всего.

Чему я научусь в уроке «Обёртывание и распаковка ошибок»?

Использование %w, errors.Is и errors.As Ты практикуешь Go Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Go Academy?

Предыдущий опыт не требуется. Go Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Обёртывание и распаковка ошибок»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Go Academy?

Да. Каждый урок Go Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Интерфейс error
  2. Создание пользовательских ошибок
  3. Обёртывание и распаковка ошибок
  4. panic, recover и defer
← Назад к Go Academy