Неудача против рейза в Ruby: Должны ли мы по-настоящему верить руководству по стилю?

Ruby предлагает две возможности программно вызывать исключение: raise и fail, оба являются методами Kernel. Согласно документам, они абсолютно эквивалентны.

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

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

Может ли кто-нибудь увидеть недостатки в моих аргументах? Или есть другие критерии, которые могли бы мне хотеть использовать fail вместо raise?

Ответ 1

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

Это не то, что говорится в официальном руководстве по стилю или предоставленной вами ссылке.

Здесь подразумевается использование raise только в блоках rescue. Aka использовать fail когда вы хотите сказать, что что-то не работает, и использовать raise при повторном исключении.

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

  1. Руководство по стилю вашего проекта
  2. Руководство вашей компании
  3. Руководство сообщества

В идеале три должны быть одинаковыми.


Обновление: Начиная с этого PR (декабрь 2015 г.), конвенция всегда должна использовать raise.

Ответ 2

Однажды у меня был разговор с Джимом Weirich об этих самых вещах, я так всегда fail, когда мой метод явно неудачи по какой - то причине и raise повторно брошенные исключения.

Вот сообщение с сообщением от Джима (почти дословно, что он сказал мне лично): http://www.virtuouscode.com/2014/05/21/jim-weirich-on-exceptions/

Вот соответствующий текст с поста, цитата, приписываемая Джим:

Вот моя основная философия (и другие случайные мысли) об исключениях.

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

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

Вот некоторые конкретные примеры. Метод save модели Rails:

model.save!
-- post-condition: The model object is saved.

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

model.save
-- post-condition: (the model is saved && result == true) ||
                   (the model is not saved && result == false)

Если save фактически не сохраняет, то возвращаемый результат будет false, но пост-условие все еще выполняется, следовательно, исключение не будет.

Мне интересно, что save! метод имеет гораздо более простое пост-условие.

На тему спасения исключений, я думаю, что приложение должно иметь стратегические точки, где исключены исключения. По большей части мало необходимости в спасении/повторении. Единственный раз, когда вы хотели бы спасти и реконструировать, - это когда у вас есть работа на полпути, и вы хотите отменить что-то, чтобы избежать частично полного состояния. Ваши стратегические точки спасения следует выбирать осторожно, чтобы программа могла продолжить работу, даже если текущая операция не удалась. Программы обработки транзакций должны просто перейти к следующей транзакции. Приложение Rails должно восстановиться и быть готовым к обработке следующего HTTP-запроса.

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

Исключения не должны использоваться для управления потоком, для этого используйте throw/catch. Это резервирует исключения для истинных условий отказа.

(В стороне, потому что я использую исключения для указания сбоев, я почти всегда использую ключевое слово fail а не ключевое слово raise в Ruby. Fail and raise - синонимы, поэтому нет никакой разницы, кроме того, что fail более четко указывает, что метод не прошел. Единственное время, когда я использую raise - это когда я улавливаю исключение и повторно его поднимаю, потому что здесь я не сработал, но явно и целенаправленно создаю исключение. Это стилистическая проблема, за которой я следую, но я сомневаюсь, что это делают многие другие люди).

Там у вас это есть, довольно бессвязные воспоминания о моих мыслях об исключениях.

Я знаю, что есть много руководств по стилю, которые не согласны (например, руководство по стилю, используемое RoboCop). Мне все равно. Джим убедил меня.