← Назад к вопросам

Как обрабатывать ошибки в Go? Что такое error wrapping?

2.2 Middle🔥 181 комментариев
#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Обработка ошибок в Go

В Go обработка ошибок является явной и составной частью сигнатур функций. В отличие от исключений в других языках, ошибки в Go — это обычные значения, которые возвращаются как последнее значение из функций.

Основные подходы к обработке

1. Проверка ошибок после каждого вызова функции

file, err := os.Open("file.txt")
if err != nil {
    // Обработка ошибки
    log.Printf("Не удалось открыть файл: %v", err)
    return err
}
defer file.Close()

2. Типизированные ошибки с errors.New() и fmt.Errorf()

var ErrNotFound = errors.New("ресурс не найден")

func FindUser(id int) (*User, error) {
    if id <= 0 {
        return nil, fmt.Errorf("некорректный ID: %d", id)
    }
    // ... логика поиска
}

3. Сравнение ошибок с errors.Is() и errors.As()

// Проверка на конкретную ошибку
if errors.Is(err, os.ErrNotExist) {
    // Обработка случая "файл не существует"
}

// Приведение к конкретному типу ошибки
var pathErr *os.PathError
if errors.As(err, &pathErr) {
    fmt.Printf("Ошибка пути: %s\n", pathErr.Path)
}

Error Wrapping (Обёртывание ошибок)

Error wrapping — это механизм добавления контекста к ошибке при её передаче вверх по стеку вызовов, сохраняя при этом оригинальную ошибку для последующего анализа.

Реализация через fmt.Errorf() с %w

func processFile(filename string) error {
    data, err := readFile(filename)
    if err != nil {
        // Оборачиваем ошибку, добавляя контекст
        return fmt.Errorf("не удалось обработать файл %s: %w", filename, err)
    }
    // ... обработка данных
    return nil
}

func readFile(filename string) ([]byte, error) {
    content, err := os.ReadFile(filename)
    if err != nil {
        return nil, fmt.Errorf("ошибка чтения файла: %w", err)
    }
    return content, nil
}

Практические аспекты wrapping

1. Сохранение цепочки ошибок

// Исходная ошибка
err := io.EOF

// Первый уровень обёртки
err = fmt.Errorf("чтение данных: %w", err)

// Второй уровень обёртки
err = fmt.Errorf("обработка запроса: %w", err)

// errors.Is() пройдёт по всей цепочке
fmt.Println(errors.Is(err, io.EOF)) // true

2. Кастомные обёрнутые ошибки

type ServiceError struct {
    Operation string
    Err       error
}

func (e *ServiceError) Error() string {
    return fmt.Sprintf("ошибка в %s: %v", e.Operation, e.Err)
}

func (e *ServiceError) Unwrap() error {
    return e.Err
}

Best Practices

  • Всегда проверяйте ошибки сразу после вызова функции
  • Добавляйте контекст при передаче ошибки на уровень выше
  • Используйте errors.Is() вместо == для проверки ошибок
  • Избегайте избыточного обёртывания на одном уровне абстракции
  • Создавайте sentinel-ошибки как переменные уровня пакета
  • Логируйте ошибки только один раз на самом верхнем уровне

Пример полной обработки

func main() {
    if err := runApp(); err != nil {
        // Логирование на верхнем уровне
        log.Printf("Сбой приложения: %v", err)
        
        // Проверка конкретных ошибок для пользователя
        if errors.Is(err, ErrConfigNotFound) {
            fmt.Println("Конфигурационный файл не найден")
        }
        os.Exit(1)
    }
}

Error wrapping стал стандартом начиная с Go 1.13, обеспечивая баланс между добавлением полезного контекста и сохранением возможности анализа исходных ошибок. Этот подход делает отладку более простой, так как цепочка ошибок показывает полный путь возникновения проблемы через все уровни абстракции приложения.