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

812

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

Не спасайте исключение. КОГДА-ЛИБО. или я ударю вас.

Почему бы и нет? Что нужно делать?

  • 11
    Я знаю ответ, я просто спрашиваю в надежде, что кто-то напишет хороший ответ, потому что я не смог найти хорошего за несколько минут поиска. Пока что ни один из ответов не является действительно правильным.
  • 32
    Тогда вы, вероятно, могли бы написать свой собственный? :)
Показать ещё 4 комментария
Теги:
exception-handling

6 ответов

1198
Лучший ответ

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
  • 124
    так что это как ловить Throwable в Java
  • 0
    Что вы думаете об этом использовании: stackoverflow.com/a/766228/513739
Показать ещё 10 комментариев
73

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

или я ударю вас

Конечно, имейте в виду, что сигналы (по умолчанию) вызывают исключения, а обычно длительные процессы завершаются сигналом, поэтому 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! Никто этого не делает!), Но вы можете захотеть сохранить его там какое-то время по какой-то причине, но не постоянно запускать отладчик.

Примечание:

  • Если вы запустили чужую программу, которая улавливает исключения и игнорирует их (скажем, код выше), выполните следующие действия:

    • в Linux, в оболочке, введите pgrep ruby или ps | grep ruby, найдите свой ПИД-код нарушителя и запустите kill -9 <PID>.
    • в Windows используйте диспетчер задач (CTRL - SHIFT - ESC), перейдите на вкладку "процессы", найдите свой процесс, щелкните его правой кнопкой мыши и выберите "Завершить процесс".
  • Если вы работаете с какой-то другой программой, которая по какой-то причине наперена этими блоками исключений-исключений, то размещение этого в верхней части главной линии - это один из возможных способов:

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

  • 20
    Извините, это неправильно. Сервер никогда не должен спасать Exception и ничего не делать, кроме как регистрировать его. Это сделает его неубиваемым, кроме как с помощью kill -9 .
  • 1
    ответ исправлен.
Показать ещё 5 комментариев
49

Скажем, ты в машине (бежит Рубин). Недавно вы установили новое рулевое колесо с системой обновления по воздуху (которая использует 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, убедитесь, что он всегда работает. Это хорошо, потому что иногда ваша программа может лежать вам и не бросать исключения, даже когда возникают проблемы. С критическими задачами, такими как надувание подушек безопасности, вам нужно убедиться, что это произойдет независимо от того, что. Из-за этого, проверяя каждый раз, когда машина останавливается, выбрасывается ли исключение или нет, это хорошая идея. Несмотря на то, что надувание подушек безопасности является частью необычной задачи в большинстве контекстов программирования, это на самом деле довольно часто встречается в большинстве задач очистки.


TL; DR

Не rescue Exception => e (а не повторно поднимайте исключение) - или вы можете отключить мост.

  • 10
    Хахаха! Это отличный ответ. Я в шоке, что никто не прокомментировал. Вы даете четкий сценарий, который делает все это действительно понятным. Ура! :-)
  • 0
    @JamesMilani Спасибо!
Показать ещё 3 комментария
42

Потому что это фиксирует все исключения. Маловероятно, что ваша программа может восстановиться из любого из них.

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

Проглатывание исключений плохо, не делайте этого.

9

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

1

Это также спрячет ошибки от вас, например, если вы ошибочно указали имя метода:

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню