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

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

В Go обёртывание ошибок выполняется с помощью оператора %w в функции fmt.Errorf или с использованием функции errors.Join. Обёрнутая ошибка сохраняет связь с исходной, что позволяет программе проверять, является ли ошибка конкретным типом, или соответствует ли она определённому значению. Основное преимущество обёртывания — возможность добавления контекста (например, имени функции, идентификатора запроса или параметров) без изменения исходной ошибки.

Синтаксис

goerr := fmt.Errorf("context: %w", originalErr)

В данном примере %w — это специальный форматный вербайзер, который оборачивает ошибку originalErr в новую ошибку с добавленным текстом "context:".

Пример создания обёрнутой ошибки

Рассмотрим пример, где мы оборачиваем ошибку, полученную при попытке открыть файл, добавляя информацию о том, что это произошло при загрузке конфигурации:

gopackage main

import (
  "fmt"
  "os"
)

func loadConfig() error {
  _, err := os.Open("config.json")
  if err != nil {
    return fmt.Errorf("load config failed: %w", err)
  }
  return nil
}

func main() {
  err := loadConfig()
  if err != nil {
    fmt.Println(err)
  }
}

Результат выполнения кода будет содержать как добавленный контекст, так и исходную ошибку:

go"load config failed: open config.json: no such file or directory"

Проверка обёрнутой ошибки с помощью errors.Is

Обёрнутые ошибки можно проверять на соответствие определённому типу или значению с помощью функции errors.Is. Это позволяет обрабатывать ошибки на разных уровнях, не теряя информации о её происхождении:

gopackage main

import (
  "errors"
  "fmt"
  "os"
)

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

func loadConfig() error {
  _, err := os.Open("config.json")
  if errors.Is(err, os.ErrNotExist) {
    return fmt.Errorf("load config failed: %w", ErrNotFound)
  }
  return err
}

func main() {
  err := loadConfig()
  if errors.Is(err, ErrNotFound) {
    fmt.Println("Configuration file is missing")
  } else if err != nil {
    fmt.Println("Other error:", err)
  }
}

Результат выполнения кода:

go"Configuration file is missing"

Обёртывание с помощью errors.Join

Начиная с Go 1.20, появилась функция errors.Join, которая позволяет объединять несколько ошибок в одну, оборачивая их все одновременно. Это удобно, когда требуется собрать несколько ошибок в одну цепочку:

gopackage main

import (
  "errors"
  "fmt"
)

func main() {
  err1 := errors.New("database connection failed")
  err2 := errors.New("cache read error")
  wrapped := errors.Join(err1, err2)
  fmt.Println(wrapped)
}

Результат выполнения кода:

go"database connection failed\ncache read error"

В этом примере обе ошибки объединяются в одну, и их можно обрабатывать совместно. Однако, в отличие от fmt.Errorf с %w, errors.Join не добавляет текстовый контекст, а просто объединяет несколько ошибок в одну структуру.

Смотрите также

  • функцию errors.Is,
    которая проверяет, содержит ли цепочка ошибок конкретную ошибку
  • функцию errors.As,
    которая извлекает ошибку определённого типа из цепочки
  • функцию errors.Unwrap,
    которая возвращает предыдущую ошибку в цепочке
  • функцию fmt.Errorf,
    которая форматирует строку и может обёртывать ошибки через %w