840

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

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

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

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

  • Error: User Rate Limit ExceededneverError: User Rate Limit Exceededkill -9.

    от John
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit ExceededensureError: User Rate Limit ExceededrescueError: 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

    от
  • Error: User Rate Limit ExceededThrowableError: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • Тогда вы, вероятно, могли бы написать свой собственный? :)

    от Sergio Tulentsev
  • @DarthEgregious Я не могу точно сказать, шутите ли вы или нет. Но я думаю, что это весело. (И это явно не серьезная угроза). Теперь каждый раз, когда я думаю о том, чтобы поймать Exception, я думаю, что это & apos; Стоит зарезать случайным парнем в Интернете.

    от Steve Sether
  • Я знаю ответ, я просто спрашиваю в надежде, что кто-то напишет хороший ответ, потому что я не смог найти хороший ответ за несколько минут поиска. Пока что ни один из ответов не является действительно правильным.

    от John
  • Взгляни наthis article в Ruby Exception с хорошимRuby Exception Hierarchy.

    от Atul Khanduri
  • Мне очень неудобно призывать к насилию здесь. Это просто программирование.

    от Darth Egregious
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.

  • 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
    

  • 9

    Это особый случай правила, которое вы не должны ловитьany 

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

  • 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 ехать с моста.

  • 46

    Потому что это охватывает все исключения. Маловероятно

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

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

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