Вопрос по ruby – Почему плохой стиль `спасать Exception => e` в Ruby?

840

Райан ДэвисРуби QuickRef говорит (без объяснения причин):

Don’t rescue Exception. EVER. or I will stab you.

Почему бы и нет? Как правильно поступить?

Мне очень неудобно призывать к насилию здесь. Это просто программирование. Darth Egregious
Взгляни наthis article в Ruby Exception с хорошимRuby Exception Hierarchy. Atul Khanduri
Я знаю ответ, я просто спрашиваю в надежде, что кто-то напишет хороший ответ, потому что я не смог найти хороший ответ за несколько минут поиска. Пока что ни один из ответов не является действительно правильным. John
@DarthEgregious Я не могу точно сказать, шутите ли вы или нет. Но я думаю, что это весело. (И это явно не серьезная угроза). Теперь каждый раз, когда я думаю о том, чтобы поймать Exception, я думаю, что это & apos; Стоит зарезать случайным парнем в Интернете. Steve Sether
Тогда вы, вероятно, могли бы написать свой собственный? :) Sergio Tulentsev

Ваш Ответ

5   ответов
77

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

or I will stab you

Конечно, имейте в виду, что сигналы (по умолчанию) генерируют исключения, и обычно длительные процессы завершаются с помощью сигнала, поэтому перехват исключений и не прекращение исключений сигналов очень затруднит остановку вашей программы. Так что не делайте этого:

#! /usr/bin/ruby

while true do
  begin
    line = STDIN.gets
    # heavy processing
  rescue Exception => e
    puts "caught exception #{e}! ohnoes!"
  end
end

Нет, правда, не делай этого. Даже не запускайте это, чтобы увидеть, работает ли оно.

Однако, скажем, у вас есть многопоточный сервер, и вы хотите, чтобы все исключения не:

be ignored (the default) stop the server (which happens if you say 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 делает это в своем обработчике запросов.

Исключения сигнала возникают в основном потоке. Фоновые нити не получат их, поэтому нет смысла пытаться их там поймать.

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

Также обратите внимание, что существует другая идиома Ruby, которая имеет почти такой же эффект:

a = do_something rescue "something else"

В этой строке, еслиdo_something возбуждает исключение, его ловит Руби, выбрасывает иa назначен"something else".

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

debugger rescue nil

debugger Функция - довольно хороший способ установить точку останова в вашем коде, но если она работает вне отладчика и Rails, она вызывает исключение. Теперь теоретически вы не должны оставлять отладочный код в своей программе (pff! Никто не делает этого!), Но вы можете захотеть оставить его там некоторое время по какой-то причине, но не постоянно запускать отладчик.

Замечания:

If you've run someone else's program that catches signal exceptions and ignores them, (say the code above) then:

in Linux, in a shell, type pgrep ruby, or ps | grep ruby, look for your offending program's PID, and then run kill -9 <PID>. in Windows, use the Task Manager (CTRL-SHIFT-ESC), go to the "processes" tab, find your process, right click it and select "End process".

If you are working with someone else's program which is, for whatever reason, peppered with these ignore-exception blocks, then putting this at the top of the mainline is one possible cop-out:

%W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }

This causes the program to respond to the normal termination signals by immediately terminating, bypassing exception handlers, with no cleanup. So it could cause data loss or similar. Be careful!

If you need to do this:

begin
  do_something
rescue Exception => e
  critical_cleanup
  raise
end

you can actually do this:

begin
  do_something
ensure
  critical_cleanup
end

In the second case, critical cleanup will be called every time, whether or not an exception is thrown.

Error: User Rate Limit Exceeded
Error: User Rate Limit ExceededneverError: User Rate Limit Exceededkill -9. John
Error: User Rate Limit Exceeded
Error: User Rate Limit ExceededensureError: User Rate Limit ExceededrescueError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
55

вы находитесь в машине (на которой работает Ruby). Вы недавно установили новое рулевое колесо с беспроводной системой обновления (которая используетeval), но вы не знали, что один из программистов испортил синтаксис.

Вы находитесь на мосту и понимаете, что идете немного к перилам, поэтому вы поворачиваете налево.

def turn_left
  self.turn left:
end

упс! Это, вероятно,Not Good& # x2122 ;, к счастью, Руби поднимаетSyntaxError.

Машина должна немедленно остановиться - верно?

Нету.

begin
  #...
  eval self.steering_wheel
  #...
rescue Exception => e
  self.beep
  self.log "Caught #{e}.", :warn
  self.log "Logged Error - Continuing Process.", :info
end

beep beep

Warning: Caught SyntaxError Exception.

Info: Logged Error - Continuing Process.

Вы замечаете, что что-то не так, и хлопаете в аварийных перерывах^C: Interrupt)

beep beep

Warning: Caught Interrupt Exception.

Info: Logged Error - Continuing Process.

Да, это не очень помогло. Вы довольно близко к рельсу, поэтому вы поставили машину в парк (killING:SignalException).

beep beep

Warning: Caught SignalException Exception.

Info: Logged Error - Continuing Process.

В последнюю секунду вы вытащите ключи (kill -9) и машина останавливается, вы врезаетесь в руль (подушка безопасности не надувается, потому что вы не остановили программу изящно - вы прекратили ее), а компьютер в задней части автомобиля врезается в сиденье в перед этим. Наполовину полная банка кока-колы проливает на бумаги. Продукты в спине раздавлены, и большинство из них покрыты яичным желтком и молоком. Автомобиль нуждается в серьезном ремонте и чистке. (Потери данных)

Надеюсь, у вас есть страховка (резервные копии). О да - потому что подушка безопасности не надулась, вы, вероятно, получили травму (уволены и т. Д.).

Но ждать! Там & APOS; smore причины, по которым вы можете использоватьrescue Exception => e!

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

 begin 
    # do driving stuff
 rescue Exception => e
    self.airbags.inflate if self.exceeding_safe_stopping_momentum?
    raise
 end

Вот исключение из правила: вы можете пойматьException only if you re-raise the exception, Итак, лучшее правило - никогда не глотатьExceptionи всегда повторно поднимать ошибку.

Но добавление аварийного восстановления легко забыть на языке, подобном Ruby, и создание правильного выражения перед повторным поднятием проблемы выглядит немного неСУХОЙ. а выdo not хочу забытьraise заявление. И если вы это сделаете, удачи в попытке найти эту ошибку.

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

 begin 
    # do driving stuff
 ensure
    self.airbags.inflate if self.exceeding_safe_stopping_momentum?
 end

Boom! И этот код должен работать в любом случае. Единственная причина, по которой вы должны использоватьrescue Exception => e если вам нужен доступ к исключению, или если вы хотите, чтобы код выполнялся только для исключения. И не забудьте повторно поднять ошибку. Каждый раз.

Примечание: как отметил @Niall, убедитесь, чтоalways пробеги. Это хорошо, потому что иногда ваша программа может лгать вам и не генерировать исключения, даже когда возникают проблемы. С критическими задачами, такими как надувание подушек безопасности, вы должны быть уверены, что это произойдет, несмотря ни на что. Из-за этого, проверяя каждый раз, когда автомобиль останавливается, выбрасывается ли исключение, хорошая идея. Несмотря на то, что накачивание подушек безопасности является довольно редкой задачей в большинстве контекстов программирования, на самом деле это довольно часто встречается в большинстве задач по очистке.

TL;DR

Дон & APOS; тrescue Exception => e (а не повторно поднимать исключение) - или выmight ехать с моста.

Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit ExceededensureError: User Rate Limit Exceededrescue ExceptionError: User Rate Limit ExceededensureError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
9

any Исключение, которое вы не знаете, как обработать. Если вы не знаете, как с этим справиться, всегда лучше позволить какой-то другой части системы поймать и обработать ее.

46

что ваша программа сможет восстановиться послеany из них.

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

Глотать исключения - это плохо, не делай этого.

1300

TL;DR: ИспользоватьStandardError вместо этого для общего исключения ловли. Когда исходное исключение повторно вызывается (например, при спасении для регистрации только исключения), спасениеException вероятно, все в порядке.

Exception кореньИсключительная иерархия Rubyтак что когда тыrescue Exception вы спасаетесь отeverythingвключая подклассы, такие какSyntaxError, LoadError, а такжеInterrupt.

спасающийInterrupt не позволяет пользователю использоватьCTRLC выйти из программы.

спасающийSignalException не позволяет программе правильно реагировать на сигналы. Это будет неубиваемым, кроме какkill -9.

спасающийSyntaxError Значит этоevalТе, кто потерпит неудачу, будут делать это молча.

Все это можно показать, запустив эту программу и пытаясь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 broadens масштаб, а не сужение, может привести к катастрофическим результатам и сделать поиск ошибок чрезвычайно трудным.

Если у вас есть ситуация, когда вы хотите спасти от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
Error: User Rate Limit Exceeded
Error: User Rate Limit ExceededThrowableError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded

Похожие вопросы