Расскажите про обработку ошибок в Go. Что представляет собой тип `error`?

Ответ

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

Тип error — это встроенный интерфейс, который требует реализации всего одного метода:

// Определение интерфейса error
type error interface {
    Error() string
}

Любой тип, реализующий этот метод, может быть использован как ошибка. Этот подход, в отличие от механизма исключений (try/catch) в других языках, заставляет разработчика явно обрабатывать возможные сбои.

Основные идиомы и инструменты для работы с ошибками:

  1. Явная обработка: Самый распространенный паттерн — возврат ошибки как последнего значения из функции и немедленная проверка на nil.

    val, err := someFunction()
    if err != nil {
        // Обрабатываем ошибку
        return err
    }
    // Работаем с val
  2. Создание ошибок:

    • errors.New("сообщение об ошибке"): Создает простую ошибку с текстовым сообщением.
    • fmt.Errorf("ошибка с форматированием: %v", value): Создает ошибку с форматированным сообщением.
  3. Оборачивание ошибок (Wrapping): С Go 1.13 появилась возможность оборачивать ошибки, сохраняя исходную ошибку в цепочке. Это делается с помощью директивы %w в fmt.Errorf.

    if err != nil {
        return fmt.Errorf("ошибка на верхнем уровне: %w", err)
    }
  4. Проверка и извлечение ошибок:

    • errors.Is(err, target): Проверяет, есть ли в цепочке ошибок err конкретная ошибка target (например, io.EOF).
    • errors.As(err, &target): Проверяет, есть ли в цепочке err ошибка определенного типа, и если есть, присваивает ее переменной target.

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

type MyError struct {
    Code int
    Msg  string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("код: %d, сообщение: %s", e.Code, e.Msg)
}

func doSomething() error {
    return &MyError{Code: 500, Msg: "что-то пошло не так"}
}