Найти в Дзене
Nuances of programming

Golang — изящная обработка ошибок

Оглавление

Источник: Nuances of Programming

Несмотря на то, что в go предусмотрена простая модель ошибок, на деле все не так уж и просто. В данной статье я хочу рассказать вам об эффективном способе обработки ошибок и решения сопутствующих проблем.

Для начала, необходимо понять, что именно считается ошибкой в go.

Затем рассмотрим весь процесс, от создания ошибки до ее обработки и проанализируем возможные изъяны.

И, наконец, изучим решение, позволяющее устранить недочеты без вреда для дизайна приложения.

Что считается ошибкой в go

Глядя на встроенный тип ошибки, можно прийти к некоторым выводам:

-2

Мы видим, что ошибкой является интерфейс, который реализует простой метод Error, возвращающий строку.

Из этого определения следует, что для создания ошибки достаточно простой строки. Поэтому если я создам следующую структуру:

-3

То получу наипростейшее определение ошибки.

Внимание: Это всего лишь пример. Создать ошибку можно при использовании стандартных пакетов go (fmt и errors):

-4

Достаточно ли простого сообщения для изящной обработки ошибок? Давайте ответим на этот вопрос в конце статьи, проанализировав предложенное решение.

Поток ошибок

Поняв, что такое ошибка, необходимо будет визуализировать поток в его жизненном цикле.

Дабы не повторяться и не усложнять, лучшее реагировать на ошибки однократно и в одном месте.

Ответ на вопрос “почему” дан в примере ниже:

-5

Что же не так с этим кодом?

Во-первых, ошибка обрабатывалась дважды. Сначала через логирование, а затем через возвращение ее оператору вызова функции.

Возможно, кто-то из ваших коллег тоже воспользуется этой функцией. И при повторном появлении ошибки заново ее зарегистрирует. И тогда в системных логах начнется сплошной хаос из ошибок.

Представьте себе, что приложение состоит из трех разных уровней: репозитория, интерактора и веб-сервера:

-6

По принципу “не усложнять и не повторяться” (см. выше) это окажется наиболее правильным способом обработки ошибки — возвращение ее на слой выше. Далее она регистрируется, получается правильный ответ от веб-сервера — все это делается в одном месте.

Но с предыдущей частью кода все не так радужно. К сожалению, во встроенных ошибках go не предусмотрена трассировка стека. Кроме того, ошибка генерируется на внешней зависимости, а искать место ее возникновения нужно будет во внутреннем коде проекта.

github.com/pkg/errors нам в помощь.

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

-7

Эта функция оборачивает ошибку, исходящую из ORM, и создает трассировку стека без затрагивания начальной ошибки.

Посмотрим, как эта ошибка обрабатывается в других слоях. Сначала интерактор:

-8

Как вы видите, ошибка обрабатывается на верхнем слое. Идеально? Нет. Вы, должно быть, уже заметили, кодом HTTP-ответа всегда возвращается 500. Кроме того, мы всегда логируем ошибки. Ряд ошибок (“не найден результат” и т.д.) просто захламляют логи.

Решение

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

Все мы знаем, что если мы вносим что-то новое в ошибку, мы каким-то образом будем вызывать зависимость в тех точках, где создается и обрабатывается ошибка.

Давайте обозначим 3 главные цели идеального решения:

  • Обеспечивает хорошую трассировку стека ошибок.
  • Регистрирует ошибку (напр., уровень сетевой инфраструктуры).
  • При необходимости может предоставить пользователю контекстные данные об ошибке (Пример: адрес почты указан в неправильном формате).

Для начала создадим тип ошибки:

-9

Атрибуты public присваиваются только для ErrorType и типов ошибок. Можно создавать новые ошибки или оборачивать существующие.

Но возникает сразу два вопроса.

Как проверить тип ошибки без экспорта customError?

Как добавить/получить контекстные данные по ошибках (даже уже существующим) из внешних зависимостей?

Позаимствуем стратегию отсюда github.com/pkg/errors. Первым делом обернем библиотечные методы.

-10

Теперь создадим собственные методы для обработки контекста и типа универсальных ошибок:

-11

Вернемся к нашему примеру и воспользуемся новым пакетом ошибок:

-12

Теперь интерактор:

-13

И, наконец, веб-сервер:

-14

Как вы видите, экспортированный тип и некоторые значения существенно упрощают нашу жизнь при работе с ошибками. Самое приятное здесь то, что по шаблону, при создании ошибки, мы явно указываем ее тип.

Читайте нас в телеграмме и vk

Перевод статьи Stupid Gopher: Golang — handling errors gracefully