Что такое оборачивание (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)
    }

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

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