Почему это плохой стиль для "rescue Exception => e" в Ruby?
Райана Дэвиса Ruby QuickRef говорит (без объяснения причин):
не спасать исключение. КОГДА-ЛИБО. или я заколю тебя.
Почему бы и нет? Как правильно поступить?
5 ответов
TL; DR используйте StandardError
вместо этого для ловли общего исключения. Когда исходное исключение повторно вызывается (например, при спасении только для регистрации исключения), rescuing Exception
- это, наверное, хорошо.
Exception
корень иерархия исключений Ruby, поэтому, когда вы rescue Exception
вы спасаете от все, включая подклассы, такие как SyntaxError
, LoadError
и Interrupt
.
спасение Interrupt
предотвращает пользователь от использования CTRLC для выхода из программы.
спасение SignalException
предотвращает правильную реакцию программы на сигналы. Он будет неубиваем, кроме как kill -9
.
спасение SyntaxError
означает, что eval
s, которые терпят неудачу, будут делать это молча.
все это можно показать, запустив эту программу и пытаясь CTRLC или kill
это:
loop do
begin
sleep 1
eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
rescue Exception
puts "I refuse to fail or be stopped!"
end
end
спасение от Exception
даже не по умолчанию. Делать
begin
# iceberg!
rescue
# lifeboats
end
не спасает от Exception
, он спасает от StandardError
. Обычно вы должны указать что-то более конкретное, чем значение по умолчанию StandardError
, но спасая от Exception
расширяет область, а не сужение его, и может иметь катастрофические результаты и сделать охоту на ошибок чрезвычайно трудно.
если у вас есть ситуация, когда вы хотите спасти от StandardError
и вам нужна переменная с исключение, вы можете использовать эту форму:
begin
# iceberg!
rescue => e
# lifeboats
end
что эквивалентно:
begin
# iceberg!
rescue StandardError => e
# lifeboats
end
один из немногих распространенных случаев, когда разумно спасать от Exception
для целей регистрации / отчетности, в этом случае вы должны немедленно повторно вызвать исключение:
begin
# iceberg?
rescue Exception => e
# do some logging
raise e # not enough lifeboats ;)
end
на реальные правило: не выбрасывайте исключения. Объективность автора вашей цитаты сомнительна, о чем свидетельствует тот факт, что оно заканчивается
или я заколю тебя!--19-->
конечно, имейте в виду, что сигналы (по умолчанию) выбрасывают исключения, и обычно длительные процессы завершаются через сигнал, поэтому ловить исключение и не заканчиваться на исключениях сигнала сделает вашу программу очень трудно остановить. Так что не делайте этого:
#! /usr/bin/ruby
while true do
begin
line = STDIN.gets
# heavy processing
rescue Exception => e
puts "caught exception #{e}! ohnoes!"
end
end
нет, правда, не делай этого. Даже не проверяй, работает ли это.
однако, скажем, у вас есть потоковый сервер, и вы хотите, чтобы все исключения не:
- игнорируется (по умолчанию)
- остановить сервер (что произойдет, если вы скажете
thread.abort_on_exception = true
).
тогда это вполне приемлемо в вашем потоке обработки соединения:
begin
# do stuff
rescue Exception => e
myLogger.error("uncaught #{e} exception while handling connection: #{e.message}")
myLogger.error("Stack trace: #{backtrace.map {|l| " #{l}\n"}.join}")
end
выше получается вариация обработчика исключений Ruby по умолчанию, с тем преимуществом, что он также не убивает вашу программу. Rails делает это в своем обработчике запросов.
исключения сигнала поднимаются в основном потоке. Фоновые потоки не получат их, поэтому нет смысла пытаться поймать их там.
это особенно полезно в производственной среде, где вы делаете не хотите, чтобы ваша программа просто остановиться, когда что-то идет не так. Тогда вы можете взять стек дампы в ваших журналах и добавьте в свой код, чтобы иметь дело с конкретным исключением дальше по цепочке вызовов и более изящно.
обратите внимание также, что есть еще одна идиома Ruby, которая имеет тот же эффект:
a = do_something rescue "something else"
в этой строке, если do_something
вызывает исключение, он пойман Рубином, выброшен и a
назначен "something else"
.
как правило, не делайте этого, за исключением особых случаев, когда вы знаю вам не нужно беспокоиться. Один пример:
debugger rescue nil
на debugger
функция-довольно хороший способ установить точку останова в коде, но если она работает вне отладчика и Rails, возникает исключение. Теперь теоретически вы не должны оставлять отладочный код, лежащий в вашей программе (pff! никто так не делает!) но вы можете захотеть сохранить его там некоторое время по какой-то причине, но не постоянно запускать отладчик.
Примечание:
-
если ты чужой программа, которая ловит исключения сигнала и игнорирует их (скажем, код выше), затем:
- в Linux, в оболочке, типа
pgrep ruby
илиps | grep ruby
, найдите PID вашей оскорбительной программы, а затем запуститеkill -9 <PID>
. - в Windows, используйте Диспетчер задач (CTRL- SHIFT-ESC), перейдите на вкладку "процессы", найдите свой процесс, щелкните его правой кнопкой мыши и выберите "завершить процесс".
- в Linux, в оболочке, типа
-
если вы работают с чужой программой, которая по какой-то причине усыпана этими блоками игнорирования-исключения, а затем помещает это в верхнюю часть основной линии, это один из возможных вариантов:
%W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
это заставляет программу реагировать на обычные сигналы завершения, немедленно завершая, минуя обработчики исключений,без очистки. Это может привести к потере данных или чему-то подобному. Будьте осторожны!
-
Если вам нужно сделать это:
begin do_something rescue Exception => e critical_cleanup raise end
вы действительно можете сделать это:
begin do_something ensure critical_cleanup end
во втором случае
critical cleanup
будет вызываться каждый раз, независимо от того, вызвано ли исключение.
предположим, вы находитесь в машине (работает Ruby). Недавно вы установили новое рулевое колесо с системой обновления по воздуху (которая использует eval
), но вы не знали, что один из программистов напутал с синтаксисом.
def turn_left
self.turn left:
end
упс! Это наверное Не Хорошо™, к счастью, Руби поднимает SyntaxError
.
автомобиль должен немедленно остановиться - правильно?
Неа.
begin
#...
eval self.steering_wheel
#...
rescue Exception => e
self.beep
self.log "Caught #{e}.", :warn
self.log "Logged Error - Continuing Process.", :info
end
бип бип
Предупреждение: Поймано Исключение SyntaxError.
Info: Logged Error-Продолжение Процесса.
вы заметили, что что-то не так, и вы хлопаете на аварийных перерывах (^C
: Interrupt
)
бип бип
Предупреждение: Пойманное Исключение Прерывания.
информация: Logged Error-Продолжение Процесса.
Да - это не поможет. Вы довольно близко к рельсу, поэтому вы ставите машину на стоянку (kill
ing:SignalException
).
бип бип
Предупреждение: Поймано Исключение SignalException.
Info: Logged Error-Продолжение Процесса.
в последнюю секунду вы вытаскиваете ключи (kill -9
), и автомобиль останавливается, вы хлопаете вперед в рулевое колесо (подушка безопасности не может надуться, потому что вы не остановили программу - вы прекратили ее), и компьютер в задней части вашего автомобиля хлопает по сиденью перед ним. Недопитой колы перетекает бумаги. Продукты в задней части раздавлены, и большинство покрыто яичным желтком и молоком. Автомобиль нуждается в серьезном ремонте и чистке. (Потеря Данных)
надеюсь, у вас есть страховые (резервные копии). О да - потому что подушка безопасности не надулась, вы, вероятно, пострадали (увольнение и т. д.).
Но подождите! Есть больше причины, почему вы можете использовать rescue Exception => e
!
предположим, вы тот автомобиль, и вы хотите убедиться, что подушка безопасности надувается, если автомобиль превышает его безопасный момент остановки.
begin
# do driving stuff
rescue Exception => e
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
raise
end
вот исключение из правила: вы можете поймать Exception
только если вы повторно поднимаете исключение. Итак, лучшее правило-никогда не глотайте Exception
, и всегда повторно поднимать ошибка.
но добавление спасения легко забыть на таком языке, как Ruby, и положить заявление о спасении прямо перед повторным поднятием проблемы кажется немного не сухим. А ты! .. --33-->не хочу забыть raise
заявление. И если вы это сделаете, удачи в попытке найти эту ошибку.
к счастью, Ruby является удивительным, вы можете просто использовать ensure
ключевое слово, которое гарантирует выполнение кода. The ensure
ключевое слово будет запускать код независимо от того , что-если исключение выбрасывается, если нет, единственное исключение - если мир заканчивается (или другие маловероятные события).
begin
# do driving stuff
ensure
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
end
бум! И этот код должен работать в любом случае. Единственная причина, по которой вы должны использовать rescue Exception => e
если вам нужен доступ к исключению или если вы хотите, чтобы код выполнялся только для исключения. И не забудьте повторить ошибку. Каждый раз.
Примечание: как отметил @ Niall, убедитесь всегда работает. Это хорошо, потому что иногда программа может врать и не throw исключения, даже если возникают проблемы. С критическими задачами, такими как надувание подушек безопасности, вам нужно убедиться, что это произойдет независимо от того, что. Из-за этого проверка каждый раз, когда автомобиль останавливается, независимо от того, выбрасывается исключение или нет, является хорошей идеей. Несмотря на то, что надувание подушек безопасности является немного необычной задачей в большинстве контекстов программирования, это на самом деле довольно распространено с большинством задач очистки.
TL; DR
не rescue Exception => e
(и не повторно вызывать исключение) - или вы может съехал с моста.
потому что это захватывает всех исключений. Маловероятно, что ваша программа может восстановиться из любой из них.
вы должны обрабатывать только исключения,которые вы знаете, как восстановить. Если вы не ожидаете определенного исключения, не обрабатывайте его, громко аварийно завершите работу (запишите данные в журнал), затем диагностируйте журналы и исправьте код.
глотание исключений плохо, не делайте этого.
Это конкретный случай правила, которое вы не должны поймать любой исключение, вы не знаете, как справиться. Если вы не знаете, как с этим справиться, всегда лучше позволить какой-то другой части системы поймать и обработать его.