ПрограммированиеGo разработчик

Какие особенности работы с ошибками (errors) в Go, как правильно создавать и обрабатывать ошибки, и как реализовать собственные типы ошибок?

Проходите собеседования с ИИ помощником Hintsage

Ответ

Go с самого начала строился вокруг явного возврата ошибок вместо исключений. Это позволяет разрабатывать предсказуемый код и избегать скрытых ловушек, свойственных try/catch конструкциям других языков (например, Java или C++). Ошибка в Go — это интерфейс, реализующий метод Error(), что позволяет создавать как простые, так и составные/обёрнутые ошибки с контекстом.

Проблемы возникают при неправильной обработке ошибок (например, если их игнорировать через _ или не оборачивать дополнительной информацией для дебага) или создании "магических" ошибок, не соответствующих интерфейсу error. Важно также уметь возвращать ошибки из публичных функций и проверять их в каждом вызове.

Решение — использовать стандартные подходы:

  • Всегда возвращать ошибку как последнее значение функции.
  • Использовать errors.New, fmt.Errorf для создания ошибок.
  • Для пользовательских ошибок создавать свои типы, реализующие Error().
  • Для сложных кейсов применять ошибки-обёртки (errors.Wrap), чтобы не терять контекст.

Пример кода:

import ( "errors" "fmt" ) type NotFoundError struct { Resource string } func (e *NotFoundError) Error() string { return fmt.Sprintf("%s not found", e.Resource) } func GetUser(id int) (string, error) { if id != 1 { return "", &NotFoundError{"User"} } return "Steve", nil } func main() { user, err := GetUser(2) if err != nil { if nfe, ok := err.(*NotFoundError); ok { fmt.Println(nfe.Resource, "problem") } else { fmt.Println("error:", err) } } fmt.Println("user:", user) }

Ключевые особенности:

  • Ошибки всегда возвращаются явно, обычно последним аргументом функции.
  • Для сложных ошибок можно определять свои типы (struct), реализующие interface error.
  • Для обёртки ошибок и сохранения stack trace можно использовать пакет "errors" (Go 1.13+ поддерживает errors.Is и errors.As для работы с цепочкой ошибок).

Вопросы с подвохом.

Можно ли при обработке error делать сравнение через ==, или нужно использовать специальные методы?

Лучше всегда использовать errors.Is() для сравнения с ошибками-обёртками, иначе сравнение через == может не сработать при наличии обёртывания ошибки.

if errors.Is(err, os.ErrNotExist) { // обработка отсутствия файла }

Обязательно ли реализовывать тип-структуру для кастомной ошибки, или достаточно использовать errors.New("...")?

Если нужен только текст ошибки, достаточно errors.New(). Если важно сохранить контекст (например, имя ресурса), лучше определить struct с методом Error().

Как правильно возвращать ошибку в случае успешного выполнения функции?

В случае успеха всегда возвращайте nil-ошибку.

return result, nil

Типовые ошибки и анти-паттерны

  • Игнорирование возвращаемых ошибок (через _ или пропуск обработки)
  • Сравнение ошибок только по ==, несмотря на вложенные ошибки
  • Захардкоженные строки вместо ошибок
  • Отсутствие дополнительного контекста в ошибках

Пример из жизни

Негативный кейс

Разработчик пишет функцию, возвращающую ошибку без пояснений (просто errors.New("fail")). При анализе логов установить причину не получается.

Плюсы:

  • Быстрота реализации

Минусы:

  • Неинформативные ошибки
  • Сложность дебага

Позитивный кейс

Разработчик определяет кастомный тип ошибки NotFoundError и возвращает его с подробным описанием.

Плюсы:

  • Удобство фильтрации логов
  • Лёгкость поиска и обработки ошибок

Минусы:

  • Потребность в описании дополнительных структур