Перейти к основному содержимому

Обработка ошибок в Go (Часть 1: Философия и if err != nil)

В Go нет блока try-catch. Ошибка — это просто результат выполнения функции, который вы возвращаете вместе с полезным значением.

1. Интерфейс error

Все ошибки в Go реализуют один простой интерфейс из стандартной библиотеки:

type error interface {
Error() string
}

Это означает, что любая структура или тип, у которого есть метод Error() string, автоматически становится ошибкой.

2. Идиома if err != nil

Поскольку любая функция, которая может привести к ошибке, возвращает её вторым (или последним) параметром, проверка на ошибку стала «визитной карточкой» Go.

func divide(a, b float64) (float64, error) {
if b == 0 {
// Создаем ошибку с помощью стандартного пакета errors
return 0, errors.New("деление на ноль невозможно")
}
return a / b, nil
}

func main() {
result, err := divide(10, 0)

// ВСЕГДА проверяем err на nil
if err != nil {
fmt.Println("Произошла ошибка:", err)
return // Прерываем выполнение, если работать дальше нельзя
}

fmt.Println("Результат:", result)
}

Почему это кажется многословным? Да, писать if err != nil приходится часто. Но это заставляет вас явно решать, что делать при ошибке: логировать, пропустить или остановить программу. Нет "невидимых" исключений, которые вылетают из ниоткуда.

3. Как правильно создавать ошибки

Существует три основных способа:

  1. errors.New: Используется для простых статических сообщений.
    return nil, errors.New("не удалось подключиться к БД")
  2. fmt.Errorf: Используется, если нужно добавить в сообщение об ошибке динамические данные (контекст).
    return nil, fmt.Errorf("пользователь с ID %d не найден", userID)
  3. Пользовательские структуры: Если вам нужно передать вызывающей функции дополнительные данные об ошибке (например, код HTTP-ответа).
    type MyError struct {
    Code int
    Msg string
    }
    func (e *MyError) Error() string { return e.Msg }

4. Оборачивание ошибок (Error Wrapping) — это важно!

Часто ошибка возникает на нижнем уровне (например, "файл не найден"), но вы хотите добавить к ней контекст верхнего уровня ("не удалось прочитать конфиг"). В Go 1.13+ для этого используют fmt.Errorf с глаголом %w.

func readConfig() error {
err := os.ReadFile("config.json")
if err != nil {
// Мы "оборачиваем" исходную ошибку, добавляя свой контекст
return fmt.Errorf("ошибка чтения файла: %w", err)
}
return nil
}

Зачем оборачивать? Чтобы вы могли добавить контекст, но при этом сохранить исходную ошибку, чтобы потом проверить её тип (например, "это ошибка прав доступа или ошибка чтения?").