Что такое оборачивание (wrapping) ошибок в Go и для чего оно нужно?

Ответ

Оборачивание ошибок (error wrapping) — это механизм, который позволяет создавать новую ошибку, сохраняя при этом исходную (оригинальную) ошибку. Это делается для добавления контекста к ошибке по мере её продвижения вверх по стеку вызовов.

Проблема, которую это решает:

Без оборачивания, при возврате ошибки из функции, мы часто теряем исходную информацию. Например:

// Плохой пример: теряется исходная ошибка os.ErrNotExist
if err != nil {
    return fmt.Errorf("не удалось прочитать конфиг: %v", err)
}

В этом случае мы больше не можем программно проверить, была ли исходная проблема в том, что файл не найден (os.ErrNotExist).

Решение с помощью %w:

Начиная с Go 1.13, для оборачивания используется директива %w в функции fmt.Errorf.

// Хороший пример: исходная ошибка сохраняется
if err != nil {
    return fmt.Errorf("не удалось прочитать конфиг: %w", err)
}

Как работать с обёрнутыми ошибками:

Пакет errors предоставляет две функции для инспекции цепочки ошибок:

  1. errors.Is(err, target): Проверяет, есть ли в цепочке ошибок err ошибка, эквивалентная target. Идеально для проверки на конкретные значения ошибок (например, io.EOF, sql.ErrNoRows).

    if errors.Is(err, os.ErrNotExist) {
        fmt.Println("Файл конфигурации не найден, используем значения по умолчанию.")
    }
  2. errors.As(err, target): Проверяет, есть ли в цепочке ошибка, которую можно присвоить переменной target (которая должна быть указателем на тип ошибки). Используется для получения доступа к полям конкретного типа ошибки.

    var pathError *fs.PathError
    if errors.As(err, &pathError) {
        fmt.Printf("Ошибка связана с путем: %sn", pathError.Path)
    }

Основные преимущества:

  • Сохранение контекста: Понятно, где и почему произошла ошибка на каждом уровне.
  • Упрощение отладки: Полная цепочка ошибок помогает быстро найти корень проблемы.
  • Гибкая проверка ошибок: Позволяет надежно реагировать на конкретные типы сбоев.

Ответ 18+ 🔞

А, ну ты смотри, опять про эти ваши обёртки для ошибок. Слушай, это ж как с бутербродом, блядь. Представь: у тебя кусок хлеба — это основная ошибка, os.ErrNotExist, например. А сверху ты намазываешь контекст, как масло с икрой: "не удалось прочитать конфиг". Так вот, раньше-то, сука, мы этот хлеб просто закидывали в блендер, получалась каша-малаша, и понять, что там внутри, было нихуя невозможно.

Проблема-то в чём, ёпта?

Раньше писали так, и всё накрывалось медным тазом:

// Пиздец, а не пример: где моя исходная ошибка? Съела её собака!
if err != nil {
    return fmt.Errorf("не удалось прочитать конфиг: %v", err)
}

Вот ты получил такую ошибку, а проверить, был ли это "файл не найден" или "нет прав" — хуй тебе, а не проверка. Всё, пиши пропало. Информация похерена.

А щас, блядь, красота!

Go 1.13 подъехал и говорит: "На, мудила, директиву %w, заворачивай на здоровье".

// О, вот это уже дело! Оригинал ошибки при нас, как в термосе.
if err != nil {
    return fmt.Errorf("не удалось прочитать конфиг: %w", err)
}

Теперь ошибка — это как матрёшка, сука. Раскрываешь одну, а внутри ещё сидит, хитрая жопа.

И как с этим чудом работать?

Пакет errors выдал нам два волшебных инструмента, прям как отвёртка и плоскогубцы.

  1. errors.Is(err, target): Это чтоб спросить: "Слушай, а в этой цепочке случайно не затесался os.ErrNotExist?" Идеально, когда тебе важно значение ошибки.

    if errors.Is(err, os.ErrNotExist) {
        fmt.Println("Файлика-то нет, ёбта! Давай по дефолту.")
    }
  2. errors.As(err, target): А это уже поинтереснее. Тут ты спрашиваешь: "А нет ли тут ошибки типа *fs.PathError? Если есть — дай-ка я на неё посмотрю поближе, полюбопытствую, что у неё в полях записано". target должен быть указателем, не забудь, а то опозоришься.

    var pathError *fs.PathError
    if errors.As(err, &pathError) {
        fmt.Printf("Вот же ж блядь! Проблема с путём: %sn", pathError.Path)
    }

И что в сухом остатке, спросишь ты?

  • Контекст не теряется: Каждый уровень добавляет свои "пять копеек", и в логе видна вся история, как на ладони. Не надо гадать на кофейной гуще.
  • Отладка — одно удовольствие: Цепочка ведёт прямиком к корню зла. Никаких "ой, а откуда это вообще взялось?".
  • Гибкая реакция: Можешь чётко ловить конкретные сценарии и обрабатывать их как надо, а не одной общей молитвой на все случаи жизни.

В общем, вещь, блядь, полезная. Не игнорируй, а то будешь потом как Герасим — "Муму-муму" говорить, а помочь ничем не сможешь.