Райан Дэвис Ruby QuickRef говорит (без объяснений):
Не спасайте исключение. КОГДА-ЛИБО. или я ударю вас.
Почему бы и нет? Что нужно делать?
Райан Дэвис Ruby QuickRef говорит (без объяснений):
Не спасайте исключение. КОГДА-ЛИБО. или я ударю вас.
Почему бы и нет? Что нужно делать?
TL; DR: используйте StandardError
вместо общего обнаружения catch. Когда исходное исключение повторно поднимается (например, при спасении для регистрации только исключения), спасение Exception
, вероятно, хорошо.
Exception
является корнем иерархии исключений Ruby, поэтому, когда вы rescue Exception
вы спасаете все, включая подклассы, такие как SyntaxError
, LoadError
и Interrupt
.
Rescuing Interrupt
не позволяет пользователю использовать CTRL C для выхода из программы.
Спасение SignalException
не позволяет программе правильно реагировать на сигналы. Это будет unkilable, за исключением kill -9
.
Rescuing SyntaxError
означает, что eval
, который завершится неудачей, сделает это тихо.
Все это можно показать, запустив эту программу и выбрав CTRL C или 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
Реальное правило: не выбрасывайте исключения. Объективность автора вашей цитаты сомнительна, о чем свидетельствует тот факт, что она заканчивается на
или я ударю вас
Конечно, имейте в виду, что сигналы (по умолчанию) вызывают исключения, а обычно длительные процессы завершаются сигналом, поэтому catching Exception и не заканчивается на исключениях сигнала, что очень сильно остановит вашу программу. Поэтому не делайте этого:
#! /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
вызывает исключение, оно улавливается Ruby, выбрасывается, а a
назначается "something else"
.
Как правило, не делайте этого, за исключением особых случаев, когда вы знаете, что вам не нужно беспокоиться. Один пример:
debugger rescue nil
Функция debugger
- довольно хороший способ установить контрольную точку в вашем коде, но если она работает за пределами отладчика и Rails, возникает исключение. Теперь теоретически вам не следует оставлять код отладки, лежащий в вашей программе (pff! Никто этого не делает!), Но вы можете захотеть сохранить его там какое-то время по какой-то причине, но не постоянно запускать отладчик.
Примечание:
Если вы запустили чужую программу, которая улавливает исключения и игнорирует их (скажем, код выше), выполните следующие действия:
pgrep ruby
или ps | grep ruby
, найдите свой ПИД-код нарушителя и запустите kill -9 <PID>
.Если вы работаете с какой-то другой программой, которая по какой-то причине наперена этими блоками исключений-исключений, то размещение этого в верхней части главной линии - это один из возможных способов:
%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
будет вызываться каждый раз, независимо от того, выбрано ли исключение.
Скажем, ты в машине (бежит Рубин). Недавно вы установили новое рулевое колесо с системой обновления по воздуху (которая использует eval
), но вы не знали, что один из программистов испортил синтаксис.
Вы находитесь на мосту и понимаете, что идете немного к перилам, поэтому поворачиваетесь налево.
def turn_left
self.turn left:
end
упс! Вероятно, это не так хорошо, но Ruby поднимает 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: Записанная ошибка - непрерывный процесс.
Вы замечаете, что что-то не так, и вы хлопаете от аварийных перерывов (^C
: Interrupt
)
звуковой сигнал
Предупреждение: Исправлено прерывание прерывания.
Info: Записанная ошибка - непрерывный процесс.
Да, это не помогло. Вы довольно близко к рельсу, поэтому вы ставите машину в парк (kill
ing: SignalException
).
звуковой сигнал
Предупреждение: исключение исключения SignalException.
Info: Записанная ошибка - непрерывный процесс.
В последнюю секунду вы вытаскиваете ключи (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, и поставить выражение о спасении прямо перед повторным поднятием проблемы, чувствуя себя немного не сухим. И вы не хотите забывать выражение о raise
. И если вы это сделаете, удачи, пытаясь найти эту ошибку.
К счастью, Ruby потрясающий, вы можете просто использовать ключевое слово " ensure
, которое гарантирует, что код будет запущен. Ключевое слово ensure
будет запускать код независимо от того, что - если выбрано исключение, если это не так, единственное исключение - если мир закончится (или другие маловероятные события).
begin
# do driving stuff
ensure
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
end
Boom! И этот код должен работать в любом случае. Единственная причина, по которой вы должны использовать rescue Exception => e
- это если вам нужен доступ к исключению или если вы хотите, чтобы код выполнялся только с исключением. И не забудьте повторно поднять ошибку. Каждый раз.
Примечание. Как отметил @Niall, убедитесь, что он всегда работает. Это хорошо, потому что иногда ваша программа может лежать вам и не бросать исключения, даже когда возникают проблемы. С критическими задачами, такими как надувание подушек безопасности, вам нужно убедиться, что это произойдет независимо от того, что. Из-за этого, проверяя каждый раз, когда машина останавливается, выбрасывается ли исключение или нет, это хорошая идея. Несмотря на то, что надувание подушек безопасности является частью необычной задачи в большинстве контекстов программирования, это на самом деле довольно часто встречается в большинстве задач очистки.
Не rescue Exception => e
(а не повторно поднимайте исключение) - или вы можете отключить мост.
Потому что это фиксирует все исключения. Маловероятно, что ваша программа может восстановиться из любого из них.
Вы должны обрабатывать только те исключения, из которых вы знаете, как восстановить. Если вы не ожидаете определенного исключения, не обрабатывайте его, громко ругайтесь (записывайте данные в журнал), затем диагностируйте журналы и исправьте код.
Проглатывание исключений плохо, не делайте этого.
Что конкретный случай правила, что вы не должны поймать какое-либо исключение, вы не знаете, как обращаться. Если вы не знаете, как с этим справиться, всегда лучше позволить какой-либо другой части системы поймать и обработать ее.
Это также спрячет ошибки от вас, например, если вы ошибочно указали имя метода:
def my_fun
"my_fun"
end
begin
# you mistypped my_fun to my_func
my_func # my_func()
rescue Exception
# rescued NameError (or NoMethodError if you called method with parenthesis)
end