web-dev-qa-db-ja.com

RubyでException => eを救済するのが悪いスタイルなのはなぜですか?

Ryan Davis氏の Ruby QuickRef は言う(説明なし)。

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

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

848
John

TL; DR:一般的な例外キャッチのために代わりにStandardErrorを使用してください。元の例外が再度発生した場合(例:例外のみをログに記録するために救済した場合)、Exceptionを救済することはおそらく大丈夫です。


ExceptionRubyの例外階層 のルートであるため、rescue Exceptionを使用した場合はeverythingを使用します。SyntaxErrorLoadErrorInterruptなどのサブクラスも含まれます。

Interruptを救済すると、ユーザーは使用できなくなります CTRLC プログラムを終了します。

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

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

これらすべては、このプログラムを実行して、 CTRLC またはkill it:

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より具体的なものを指定すべきですが、Exceptionbroadensで範囲を狭めるのではなく範囲から救い出し、壊滅的な結果をもたらし、バグ探しを非常に困難にします。


あなたがStandardErrorから救いたいという状況があり、例外のある変数が必要な場合は、このフォームを使うことができます。

begin
  # iceberg!
rescue => e
  # lifeboats
end

これは以下と同等です。

begin
  # iceberg!
rescue StandardError => e
  # lifeboats
end

Exceptionから救うのが普通である数少ない一般的なケースの1つはロギング/報告の目的のためです、その場合あなたはすぐに例外を再発生させるべきです:

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

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

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

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

#! /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はこれをリクエストハンドラで行います。

メインスレッドでシグナル例外が発生します。バックグラウンドスレッドはそれらを取得しないので、それらをそこで捕まえようとしても意味がありません。

これは本番環境で特に便利です。not何か問題が発生したときにプログラムを単に停止させたい場合です。その後、スタック内のログをログに記録してコードに追加し、特定の例外をさらにコールチェーンの下位に、より優雅な方法で処理できます。

同じ効果を持つもう一つのRubyイディオムがあることにも注意してください。

a = do_something rescue "something else"

この行で、do_somethingが例外を発生させると、それはRubyによってキャッチされて捨てられ、a"something else"が割り当てられます。

あなたがknowである特別な場合を除いて、一般的に、そうしないでください、あなたは心配する必要はありません。一例です。

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
    

    後者の場合、例外がスローされるかどうかにかかわらず、critical cleanupが毎回呼び出されます。

79
Michael Slade

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

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

def turn_left
  self.turn left:
end

おっとっと!それはおそらくNot Good™です。幸いなことに、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:ログに記録されたエラー-継続プロセス。

何かがおかしいことに気づき、緊急休憩をたたく(^CInterrupt

ビープ音

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

Info:ログに記録されたエラー-継続プロセス。

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

ありがたいことに、Rubyは素晴らしいです。ensureキーワードを使用するだけで、コードを確実に実行できます。 ensureキーワードは、何があってもコードを実行します-例外がスローされた場合、そうでない場合、唯一の例外はワールドが終了した場合(またはその他のありそうもないイベント)です。

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

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

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


TL; DR

rescue Exception => e(および例外を再発生させない)-またはmight橋を運転しないでください。

62
Ben Aubin

これはすべての例外を捉えるためです。あなたのプログラムが any から回復できる可能性は低いです。

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

例外を飲み込むのは悪いことです、しないでください。

45

これは、 any 例外をキャッチしてはいけないという特別なケースです。処理方法がわからない場合は、システムの他の部分で処理して処理することをお勧めします。

9