Предупреждение: этот пост может вас сильно разозлить. С другой стороны, это может сделать вас невероятно счастливым. 😉

Обработка ошибок на основе исключений и кода возврата

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

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

  1. Если ошибка обычно распространяется не более чем на один кадр стека (т. Е. Непосредственно вызывающему функцию), следует использовать обработку ошибок кода возврата.
  2. Если ошибка обычно распространяется более чем на один кадр стека (т. Е. Через несколько вызывающих функций), следует использовать обработку ошибок на основе исключений.

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

Как язык, Go не поддерживает исключения, утверждая, что идиома try / catch приводит к запутанному коду. Хотя есть некоторые вещи, которые мне нравятся в Go (и многие вещи, которые я ненавижу), такие догматичные черно-белые утверждения, как эти, к их числу не относятся. Инженерное дело - это постоянный набор зависимых от ситуации компромиссов.

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

Обработка ошибок на основе исключений в Go

Позвольте мне начать с быстрых переводов:

Догматизм, не подкрепленный реальностью, самый лучший, правда? (Я считаю, что исключения - это здорово 😉). В следующем наборе коротких фрагментов кода я продемонстрирую, как добавить обработку ошибок на основе исключений в ваш код Go.

Определение типа исключения

Рисунок 1 демонстрирует определение очень простого типа «исключения» RateLimitConfigError. В этом примере тип - это просто псевдоним для строки, но он также может быть структурой, если требуется переносить более сложную информацию об ошибке. Мы также определяем функцию Error() для использования при ведении журнала.

Выбрасывание исключения

Рисунок 2 демонстрирует «выброс» исключения в коде Go. Поскольку panic() вызывает исключение, все, что нам нужно сделать, это panic() с нашим типом исключения.

Ловля исключения

Рисунок 3 демонстрирует, как перехватить исключение в Go. Этот пример немного более интересен, так как нам нужно немного согнуть Go, чтобы сделать то, что нужно.

  1. reloadConfig() - это функция, которая в конечном итоге становится сильно вложенной, очень хорошо поддающейся обработке ошибок на основе исключений, основанной на двух правилах стиля обработки ошибок, которые я изложил выше.
  2. В строке 2 мы устанавливаем оператор defer с финализатором.
  3. В строке 3 мы пытаемся recover (перехватывать) любые возникшие исключения.
  4. В строке 4 мы проверяем тип обнаруженных исключений. Если тип не соответствует ожидаемому, мы повторно вызываем исключение в строке 6. Это предотвращает обнаружение любых ошибок, которые мы не планировали.
  5. Начиная с строки 9, мы принимаем меры к данным исключения.

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

(Кстати, рисунок 1 также демонстрирует один из моих любимых стилей программирования Go, нацеленных на то, чтобы раздражать догматических фанатиков: я называю все объектные переменные как this, как показано в строке 1 . Знаю, я плохой человек. 😃)

Заключение

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

Реальный пример panic и восстановления см. В пакете json из стандартной библиотеки Go. Он декодирует данные в формате JSON с помощью набора рекурсивных функций. При обнаружении искаженного JSON парсер вызывает панику, чтобы развернуть стек до вызова функции верхнего уровня, который восстанавливается после паники и возвращает соответствующее значение ошибки (см. Методы error и unmarshal типа decodeState в decode.go ).

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

Чтобы получить более полный пример этого стиля программирования, взгляните на Службу ограничения скорости Lyft, в которой я нарушаю многие догматические правила Go способами, которые, на мой взгляд, приводят к более чистому и читаемому коду (и, конечно, раздражать фанатиков).

Обладая этим новообретенным знанием, идите вперед, распространяйте Евангелие и напишите еще больше «Исключительный, вперед!»