Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Упаковка ошибок (Error Wrapping) в Go
Упаковка ошибок — это идиоматический подход в Go, позволяющий создавать цепочки ошибок, где каждая последующая ошибка "оборачивает" предыдущую, добавляя контекстную информацию. Этот механизм появился в Go 1.13 и стал стандартным способом обработки ошибок, сохраняя при этом полную историю их возникновения.
Как работает упаковка ошибок
Основная идея заключается в том, что вместо простого возврата новой ошибки мы "оборачиваем" исходную ошибку, добавляя к ней дополнительный контекст. Это реализуется с помощью функции fmt.Errorf() с директивой %w:
package main
import (
"errors"
"fmt"
)
func processFile(filename string) error {
data, err := readFile(filename)
if err != nil {
// Упаковываем ошибку, добавляя контекст
return fmt.Errorf("не удалось обработать файл %s: %w", filename, err)
}
result, err := parseData(data)
if err != nil {
// Дополнительная упаковка в другой функции
return fmt.Errorf("ошибка парсинга: %w", err)
}
return saveResult(result)
}
func readFile(filename string) ([]byte, error) {
if filename == "" {
return nil, errors.New("имя файла не может быть пустым")
}
// ... чтение файла
return []byte{}, nil
}
Преимущества упаковки ошибок
- Сохранение полного стека ошибок — мы не теряем оригинальную ошибку, а только дополняем её контекстом
- Возможность проверки конкретных ошибок — можно проверить, содержится ли в цепочке определённая ошибка
- Улучшенная диагностика — при логировании видна вся цепочка возникновения проблемы
- Совместимость с существующим кодом — стандартный интерфейс
errorостаётся неизменным
Функции для работы с упакованными ошибками
Go 1.13 представил три ключевые функции в пакете errors:
package main
import (
"errors"
"fmt"
)
var ErrFileNotFound = errors.New("файл не найден")
func main() {
err := processFile("test.txt")
// Проверка на конкретную ошибку в цепочке
if errors.Is(err, ErrFileNotFound) {
fmt.Println("Файл действительно не найден")
}
// Извлечение конкретной ошибки из цепочки
var fileErr *FileError
if errors.As(err, &fileErr) {
fmt.Printf("Ошибка файла: %v\n", fileErr)
}
// Разворачивание ошибки для получения оригинальной
originalErr := errors.Unwrap(err)
if originalErr != nil {
fmt.Printf("Оригинальная ошибка: %v\n", originalErr)
}
}
Практические рекомендации по упаковке ошибок
- Добавляйте контекст на каждом уровне — каждая функция, которая получает ошибку и передаёт её выше, должна добавлять релевантный контекст:
func processUserData(userID string) error {
data, err := fetchUserData(userID)
if err != nil {
return fmt.Errorf("processUserData: получение данных для пользователя %s: %w", userID, err)
}
// ... обработка данных
return nil
}
- Используйте sentinel-ошибки с умом — создавайте публичные переменные ошибок для важных состояний:
var (
ErrUserNotFound = errors.New("пользователь не найден")
ErrInvalidToken = errors.New("невалидный токен")
ErrDBConnection = errors.New("ошибка соединения с БД")
)
- Не упаковывайте ошибки без необходимости — если вы не добавляете полезный контекст, просто возвращайте ошибку как есть:
func validate(input string) error {
if input == "" {
// Просто возвращаем ошибку без упаковки
return ErrEmptyInput
}
return nil
}
Пример полного сценария использования
func processOrder(orderID string) error {
order, err := db.GetOrder(orderID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("заказ %s не найден: %w", orderID, err)
}
return fmt.Errorf("ошибка БД при получении заказа: %w", err)
}
if err := validateOrder(order); err != nil {
return fmt.Errorf("невалидный заказ %s: %w", orderID, err)
}
if err := processPayment(order); err != nil {
var paymentErr *PaymentError
if errors.As(err, &paymentErr) {
return fmt.Errorf("платёжная ошибка для заказа %s: %w", orderID, paymentErr)
}
return fmt.Errorf("неизвестная ошибка платежа: %w", err)
}
return nil
}
Упаковка ошибок в Go предоставляет мощный, но элегантный способ обработки ошибок, который сочетает в себе простоту интерфейса error с возможностями полноценной трассировки ошибок, характерными для исключений в других языках. Это делает код более надежным и удобным для отладки, сохраняя при этом ясность и простоту, за которые ценят Go.