Rubyで `rescue Exception => e`を使うのが悪いのはなぜですか?


895

Ryan DavisのRuby QuickRefは次のように述べています(説明なし):

例外を救出しないでください。これまで。または私はあなたを刺します。

何故なの?正しいことは何ですか?


35
次に、おそらく自分で書くことができますか?:)
Sergio Tulentsev 2012

65
ここでの暴力への呼びかけには非常に不快です。それはただのプログラミングです。
Darth Egregious


2
ライアン・デイビスがあなたを刺すからです。だから子供たち。例外を救出しないでください。
ムゲン、

7
@DarthEgregious冗談を言っているのかどうかわからない。しかし、それは陽気だと思います。(そしてそれは明らかに深刻な脅威ではありません)。今、私は例外をキャッチすることを考えるたびに、インターネット上のランダムな男に刺される価値があるかどうかを考えています。
Steve Sether

回答:


1375

TL; DRStandardError一般的な例外のキャッチに代わりに使用します。元の例外が再度発生した場合(例:例外のみをログに記録するように救出した場合)、救出Exceptionはおそらく問題ありません。


Exception根であるRubyの例外階層、だからrescue Exceptionあなたがから救うすべてのようなサブクラスを含む、SyntaxErrorLoadError、とはInterrupt

救出Interruptすると、ユーザーCTRLCがプログラムを終了するために使用できなくなります。

救出SignalExceptionすると、プログラムがシグナルに正しく応答できなくなります。以外では殺せませんkill -9

救助 SyntaxErrorとはeval、失敗したsが黙ってそうすることを意味します。

これらのすべては、このプログラムを実行している、としようによって示すことができる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

救出するのが常識であるいくつかの一般的なケースの1つは、Exceptionロギング/レポート作成の目的です。この場合、すぐに例外を再発生させる必要があります。

begin
  # iceberg?
rescue Exception => e
  # do some logging
  raise # not enough lifeboats ;)
end

129
つまりThrowable、Javaでキャッチするようなものです
ラチェットフリーク

53
このアドバイスは、クリーンなRuby環境に適しています。しかし、残念ながら、いくつかの宝石はExceptionから直接派生する例外を作成しています。私たちの環境には、OpenID :: Server :: EncodingError、OAuth :: InvalidRequest、HTMLTokenizerSampleなどの30があります。これらは、標準のレスキューブロックでキャッチしたい例外です。残念ながら、Rubyには、gemがExceptionから直接継承されるのを妨げたり、阻止したりするものはありません。
ジョナサン・スワーツ2013

20
@JonathanSwartz次に、Exceptionではなく、それらの特定のサブクラスから救出します。より具体的であるほど、ほとんどの場合、より明確で明確です。
アンドリューマーシャル

22
@JonathanSwartz-例外の継承元を変更するように、宝石の作成者にバグを報告します。個人的には、私の宝石にはすべての例外がMyGemExceptionから派生しているのが好きなので、必要に応じてそれを救うことができます。
Nathan Long

12
あなたはすることもできADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error, Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]、その後rescue *ADAPTER_ERRORS => e
j_mcnally

83

本当のルールは次のとおりです。例外を捨てないでください。あなたの引用の著者の客観性は疑問です、それがそれで終わるという事実によって証明されるように

または私はあなたを刺します

もちろん、シグナルは(デフォルトで)例外をスローし、通常は長時間実行されるプロセスはシグナルを介して終了するため、例外をキャッチしてシグナル例外で終了しないと、プログラムを停止するのが非常に難しくなります。したがって、これを行わないでください。

#! /usr/bin/ruby

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

いいえ、本当に、それをしないでください。動作するかどうか確認するためにそれを実行しないでください。

ただし、スレッド化されたサーバーがあり、すべての例外を禁止したいとします。

  1. 無視する(デフォルト)
  2. サーバーを停止します(これはと言う場合に起こります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!だれもそれをしません!)。しかし、デバッガを継続的に実行せずに、何らかの理由でしばらくそこに置いておきたい場合があります。

注意:

  1. シグナル例外をキャッチして無視する他の誰かのプログラムを実行した場合(上記のコードを言う):

    • Linuxでは、シェルでpgrep ruby、またはps | grep rubyを入力して、問題のプログラムのPIDを探し、を実行しkill -9 <PID>ます。
    • Windowsでは、タスクマネージャを使用します(CTRL- - SHIFT)、ESCそれを右クリックし、「エンドプロセス」を選択し、あなたのプロセスを見つけ、「プロセス」タブに移動します。
  2. 何らかの理由でこれらのignore-exceptionブロックが実行されている他の誰かのプログラムを使用している場合は、これをメインラインの先頭に配置することが1つの可能なコプトアウトです。

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

    これにより、プログラムは、クリーンアップなしで、例外ハンドラーをバイパスして直ちに終了することにより、通常の終了信号に応答します 。そのため、データの損失などが発生する可能性があります。注意してください!

  3. これを行う必要がある場合:

    begin
      do_something
    rescue Exception => e
      critical_cleanup
      raise
    end

    あなたは実際にこれを行うことができます:

    begin
      do_something
    ensure
      critical_cleanup
    end

    2番目のケースでcritical cleanupは、例外がスローされるかどうかにかかわらず、毎回呼び出されます。


21
すみません、これは間違っています。サーバーは、必要があります決して例外を救出しないと何もしませんが、それをログに記録します。それは以外では殺せないでしょうkill -9
ジョン

8
注3の例は同等ではありません。例外は発生したensureかどうかに関係rescueなく実行されますが、例外は発生した場合にのみ実行されます。
アンドリューマーシャル

1
それらは/正確に/同等ではありませんが、醜い方法で等価を簡潔に表現する方法を理解することはできません。
Michael Slade 2013年

3
最初の例のbegin / rescueブロックの後に別のcritical_cleanup呼び出しを追加するだけです。私は最もエレガントなコードではないことに同意しますが、2番目の例はエレガントな方法であることは明らかです。そのため、少しエレガントさは例の一部にすぎません。
GTD 2013年

3
「それが機能するかどうかを確認するためにそれを実行しないでください。」コーディングには悪いアドバイスのようです...逆に、誰かを盲目的に信じるのではなく、それを実行し、失敗するのを見て、失敗するかどうかを自分で理解することをお勧めします。とにかく素晴らしい答え:)
huelbois

69

TL; DR

しないでくださいrescue Exception => e(例外を再発生させないでください)。そうしないと、橋を降りてしまう可能性があります。


車に乗っているとします(Rubyを実行しています)。最近、無線アップグレードシステム(を使用eval)を備えた新しいステアリングホイールを取り付けましたが、プログラマーの1人が構文をめちゃくちゃにしたことを知りませんでした。

あなたは橋の上にいて、手すりに向かって少し進んでいることに気づいたので、左折します。

def turn_left
  self.turn left:
end

おっとっと!それはおそらくNot GoodSyntaxErrorです。幸い、Rubyはをレイズします。

車はすぐに止まるべきです-そうですか?

いいえ。

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

ビープビープ

警告:SyntaxError例外が発生しました。

情報:ログに記録されたエラー-処理を続行しています。

あなたは、何かが間違っている気づき、あなたは緊急時の休憩にスラム(^CInterrupt

ビープビープ

警告:割り込み例外をキャッチしました。

情報:ログに記録されたエラー-処理を続行しています。

うん-それはあまり役に立たなかった。あなたはかなりレールに近いので、車を公園に置きます(killing:)SignalException

ビープビープ

警告:SignalException例外が発生しました。

情報:ログに記録されたエラー-処理を続行しています。

最後の1秒間に、キー(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などの言語では忘れがちであり、問​​題を再提起する直前にレスキューステートメントを置くことは、少しDRYではないように感じます。そして、あなたraiseステートメントを忘れたくありません。もしそうなら、そのエラーを見つけようと頑張ってください。

ありがたいことに、Rubyは素晴らしいですensure。キーワードを使用するだけで、コードを確実に実行できます。ensureキーワードは、どんなコードを実行しません-例外がスローされる場合は、1つでない場合、唯一の例外があればという世界が終了する(または他の可能性は低い事象)。

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

ブーム!そして、そのコードはとにかく実行する必要があります。使用rescue Exception => eする必要がある唯一の理由は、例外にアクセスする必要がある場合、または例外でコードを実行するだけの場合です。そして、エラーを再度発生させることを忘れないでください。毎回。

注:@Niallが指摘したように、常に実行されるようにしてください。問題が発生した場合でも、プログラムが嘘をつき、例外をスローしないことがあるので、これは良いことです。エアバッグの膨張などの重要なタスクでは、何が起こっても確実に発生するようにする必要があります。このため、例外がスローされるかどうかにかかわらず、車が停止するたびに確認することをお勧めします。エアバッグの膨張は、ほとんどのプログラミングコンテキストでは一般的ではないタスクですが、これは実際にはほとんどのクリーンアップタスクでかなり一般的です。


12
ハハハッハッハ!これは素晴らしい答えです。誰もコメントしていないことにショックを受けました。あなたはすべてを本当に理解できるようにする明確なシナリオを与えます。乾杯!:-)
James Milani 2017

@JamesMilaniありがとうございます!
ベンAubinの

3
+💯この回答に。複数回賛成できることを願っています。😂–
engineerDave

1
あなたの答えを楽しんだ!
Atul Vaibhav 2017年

3
この答えは、完全に理解可能で正しい受け入れられた答えから4年後のことであり、現実的ではなく面白いように設計された不条理なシナリオでそれを再説明しました。バズキルになって申し訳ありませんが、これはredditではありません。回答は、面白いよりも簡潔で正しいことが重要です。また、のensure代替としての部分rescue Exceptionは誤解を招く-例はそれらが同等であることを示唆していますがensure、例外があるかどうかにかかわらず発生するので、何も問題がなくても5mphを超えたため、エアバッグが膨張します。
Niall 2018

47

これはすべての例外をキャプチャするためです。プログラムがそれらのどれから回復できる可能性はほとんどありません。

回復方法がわかっている例外のみを処理する必要があります。特定の種類の例外が予期されない場合は、それを処理せず、大音量でクラッシュし(詳細をログに書き込み)、ログを診断してコードを修正します。

例外を飲み込むのはよくありません。これをしないでください。


10

それはあなたがキャッチしてはならないというルールの具体的なケースだ任意のあなたが処理する方法がわからない例外を。それを処理する方法がわからない場合は、システムの他の部分にキャッチさせて処理させることをお勧めします。


0

honeybadger.ioで素晴らしいブログ投稿を読んだだけです

Rubyの例外とStandardError:違いは何ですか?

例外を救うべきでない理由

Exceptionのレスキューに関する問題は、Exceptionから継承するすべての例外を実際にレスキューすることです。それは...すべてです!

Rubyの内部で使用されるいくつかの例外があるため、これは問題です。彼らはあなたのアプリとは何の関係もありません、そしてそれらを飲み込むと悪いことが起こります。

ここにいくつかの大きなものがあります:

  • SignalException :: Interrupt-これを救うと、control-cを押してアプリを終了できなくなります。

  • ScriptError :: SyntaxError-構文エラーを飲み込むと、puts( "Forgot something)などのメッセージが表示されずに失敗します。

  • NoMemoryError-プログラムがすべてのRAMを使い果たした後、プログラムが実行し続けるとどうなるか知りたいですか?私もダメ。

begin
  do_something()
rescue Exception => e
  # Don't do this. This will swallow every single exception. Nothing gets past it. 
end

これらのシステムレベルの例外を実際に飲み込みたくないと思います。アプリケーションレベルのエラーをすべてキャッチしたいだけです。例外が原因でコードが発生しました。

幸いなことに、これを行う簡単な方法があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.