Почему это плохой стиль, чтобы "спасти исключение => e" в Ruby?
Райан Дэвис Ruby QuickRef говорит (без объяснения причин):
Не спасайте исключение. КОГДА-ЛИБО. или я заколю тебя.
Почему бы и нет? Что правильно делать?
5 ответов:
TL; DR используйте
StandardErrorвместо этого для общего исключения ловли. Когда исходное исключение повторно вызывается (например, при спасении только для регистрации исключения), rescuingExceptionнаверное, все в порядке.
Exceptionкорень иерархия исключений Руби, так что когда выrescue Exceptionвы спасаете от все, включая подклассы, такие какSyntaxError,LoadErrorиInterrupt.спасение
Interruptпредотвращает пользователь от использования CTRLC для выхода из программы.спасение
SignalExceptionне позволяет программе правильно реагировать на сигналы. Это будет неубиваемо, кромеkill -9.спасение
SyntaxErrorозначает, чтоevals, которые терпят неудачу, будут делать это молча.все это можно показать, запустив эту программу и попытавшись 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
The реальные правило: не выбрасывайте исключения. Объективность автора вашей цитаты вызывает сомнения, о чем свидетельствует тот факт, что она заканчивается на
или Я убью вас!
конечно, имейте в виду, что сигналы (по умолчанию) выбрасывают исключения, и обычно длительные процессы завершаются через сигнал, поэтому перехват исключения и не прерывание исключений сигнала сделают вашу программу очень трудной для остановки. Так что не делайте этого:
#! /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 nilThe
debuggerфункция-это довольно хороший способ установить точку останова в коде, но если она работает вне отладчика и Rails, она вызывает исключение. Теперь теоретически вы не должны оставлять отладочный код, лежащий в вашей программе (pff! никто так не делает!) но вы можете захотеть сохранить его там на некоторое время по какой-то причине, но не постоянно запускать свой отладчик.Примечание:
если вы запустили чужой программа, которая ловит исключения сигнала и игнорирует их, (скажем код выше), то:
- в Linux, в оболочке, типа
pgrep rubyилиps | grep ruby, найдите PID вашей оскорбительной программы, а затем запуститеkill -9 <PID>.- в Windows, используйте Диспетчер задач (CTRL -SHIFT - ESC), перейдите на вкладку "процессы", найдите свой процесс, щелкните его правой кнопкой мыши и выберите "завершить процесс".
если вы работают с чужой программой, которая по какой-то причине пересыпана этими блоками ignore-exception, а затем помещает это в верхнюю часть основной линии-это один возможный выход:
%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)бип бип
Предупреждение: Поймано Исключение Прерывания.
информация: Ошибка В Журнале-Продолжение Процесса.
Да - это не поможет. Вы довольно близко к рельсу, поэтому вы ставите машину на стоянку (
killing: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, и всегда повторно поднимать ошибка.но добавление rescue легко забыть на таком языке, как Ruby, и поместить заявление о спасении прямо перед повторным поднятием проблемы кажется немного несухим. А ты не хочу забыть
raiseзаявление. И если вы это сделаете, удачи, пытаясь найти эту ошибку.к счастью, Рубин является удивительным, вы можете просто использовать
ensureключевое слово, которое гарантирует выполнение кода. Элементensureключевое слово будет запускать код независимо от того, что-если исключение бросается, если это не так, Единственное исключение-если мир заканчивается (или другие маловероятные события).begin # do driving stuff ensure self.airbags.inflate if self.exceeding_safe_stopping_momentum? endбум! И этот код должен работать в любом случае. Единственная причина, по которой вы должны использовать
rescue Exception => eесли вам нужен доступ к исключению, или если вы хотите, чтобы код выполнялся только на исключении. И не забудьте повторно поднять ошибку. Каждый раз.Примечание: как указал @Niall, убедитесь всегда работает. Это хорошо, потому что иногда программа может врать и не бросьте исключения, даже если возникают проблемы. С критическими задачами, такими как надувание подушек безопасности, вам нужно убедиться, что это происходит несмотря ни на что. Из-за этого, проверка каждый раз, когда автомобиль останавливается, будь то исключение или нет, это хорошая идея. Несмотря на то, что надувание подушек безопасности является немного необычной задачей в большинстве контекстов программирования, это на самом деле довольно часто встречается с большинством задач очистки.
TL; DR
не
rescue Exception => e(и не повторно поднять исключение)-или ты может съехал с моста.
потому что это захватывает всех исключений. Маловероятно, что ваша программа может восстановиться от любой из них.
вы должны обрабатывать только исключения,которые вы знаете, как восстановить. Если вы не ожидаете определенного вида исключения, не обрабатывайте его, громко аварийно завершите работу (запишите данные в журнал), затем диагностируйте журналы и исправьте код.
глотать исключения плохо, не делайте этого.
Это конкретный случай правила, которое вы не должны поймать любой исключение, вы не знаете, как справиться. Если вы не знаете, как с этим справиться, всегда лучше позволить какой-то другой части системы поймать и обработать его.
Comments