Добавить в корзинуПозвонить
Найти в Дзене
Артем Антонов

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

Ошибка в Go указывает на то, что конкретная задача не может быть успешно завершена. Это ожидаемая и обрабатываемая ситуация. В отличие от таких языков, как Python, Java или Ruby, которые используют модель исключений (exception) с блоками try/catch, Go использует возможность функций возвращать несколько значений. Стандартной идиомой является возвращение ошибки в качестве последнего возвращаемого значения. Рассмотрим базовый пример функции Concat, которая объединяет строки: Обработка такой ошибки в вызывающем коде выглядит следующим образом: Ключевой момент здесь - использование оператора if, который позволяет выполнить присваивание result, err := Concat(...) перед проверкой условия err != nil. Область видимости переменных result и err ограничена этим блоком if/else. Возврат полезного значения вместе с ошибкой - это хорошая практика. В примере с Concat даже в случае ошибки возвращается пустая строка, что является логичным результатом. Это позволяет упростить код для тех, кто хочет проигн
Оглавление

1. Обработка ошибок (Errors)

Ошибка в Go указывает на то, что конкретная задача не может быть успешно завершена. Это ожидаемая и обрабатываемая ситуация.

Идиомы обработки ошибок

В отличие от таких языков, как Python, Java или Ruby, которые используют модель исключений (exception) с блоками try/catch, Go использует возможность функций возвращать несколько значений. Стандартной идиомой является возвращение ошибки в качестве последнего возвращаемого значения. Рассмотрим базовый пример функции Concat, которая объединяет строки:

Обработка такой ошибки в вызывающем коде выглядит следующим образом:

-2

Ключевой момент здесь - использование оператора if, который позволяет выполнить присваивание result, err := Concat(...) перед проверкой условия err != nil. Область видимости переменных result и err ограничена этим блоком if/else.

Минимизация использования nil

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

-3

Совет: Для создания простых ошибок используйте errors.New("message"). Для создания форматированной строки ошибки используйте fmt.Errorf("Error: %s", reason).

Пользовательские типы ошибок

Интерфейс error в Go чрезвычайно прост:

-4

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

Например, ошибка парсера, содержащая информацию о строке и символе:

-5

Такой тип позволяет не только вывести сообщение, но и программно проанализировать место ошибки.

Переменные ошибок

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

Рассмотрим пример функции, имитирующей отправку запроса, которая может вернуть одну из двух ошибок:

-6
gist:8baffb8ae5018d03ad24fc489323bf6f

Этот подход прост и эффективен, так как ошибки создаются только один раз (при инициализации пакета), а их проверка сводится к простому сравнению err == ErrTimeout.

2. Система аварий (Panics)

Авария (panic) сигнализирует о критической, неожиданной ситуации, которая угрожает целостности программы (например, деление на ноль, доступ к элементу массива за его пределами). Необработанная авария приводит к аварийному завершению программы.

Отличия аварий от ошибок

  • Ошибка - это ожидаемая ситуация, которую можно и нужно обработать. Она является частью обычного потока выполнения.
  • Авария - это неожиданная ситуация, часто вызванная ошибкой программиста. Она нарушает нормальный ход выполнения программы.
-7

Возбуждение аварий

Для возбуждения аварии используется встроенная функция panic(interface{}). В нее можно передать что угодно, но идиоматически правильным является передача значения, реализующего интерфейс error (например, созданного через errors.New). Это упрощает последующую обработку аварии.

-8

Восстановление после аварий

Go предоставляет механизм для перехвата аварии и восстановления нормального выполнения программы. Он основан на связке ключевых слов defer и recover.

  • defer - откладывает выполнение функции до момента выхода из текущей функции. Отложенные функции выполняются даже при аварии.
  • recover - функция, которую можно вызвать внутри отложенной, чтобы остановить раскрутку стека и получить значение, переданное в panic. Если аварии нет, recover возвращает nil.

Базовый пример восстановления:

-9

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

-10

Важно: Объявляйте отложенные функции как можно ближе к началу вмещающей функции, чтобы было понятно, какая логика очистки будет выполнена.

Аварии и горутины

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

Рассмотрим простой эхо-сервер:

-11

Авария в response "убьет" всю программу. Чтобы этого избежать, необходимо обрабатывать аварии внутри каждой сопрограммы, где они могут возникнуть.

-12

Для упрощения этой задачи можно создать специальную функцию-обертку для запуска сопрограмм:

-13
gist:7a1dcd2f35c33e9db49a2b5eeecc7c48

Использование:

-14

Эта обертка перехватит любую аварию в сопрограмме и запишет ее в лог, не позволяя ей "уронить" всю программу.

Итоги

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

  1. Ошибки - это часть нормального потока выполнения. Используйте множественный возврат, возвращайте осмысленные значения вместе с ошибкой, создавайте переменные ошибок для частых сценариев и пользовательские типы для передачи дополнительного контекста.
  2. Аварии - это чрезвычайные ситуации. Используйте их для уведомлении о неожиданных ошибках. Всегда передавайте в panic значение типа error.
  3. Восстанавливайтесь от аварий с помощью defer и recover, чтобы грациозно закрывать ресурсы и, по возможности, преобразовывать аварию в обычную ошибку.
  4. Помните о горутинах. Авария, не перехваченная внутри горутины, приведет к краху всей программы. Обеспечьте обработку аварий на верхнем уровне каждой горутины, которая в этом нуждается.

Этот подход в итоге приводит к более ясному, контролируемому и отказоустойчивому коду.

Жду ваших комментариев! Поставьте лайк, если хотите видеть больше интересного контента.

До скорой встречи!