LoginSignup
2
2

More than 5 years have passed since last update.

例外処理の落とし穴

Posted at

言語側で実装されていれば、なかば強制的に使うことになりますし、そして正常系の処理と異常時の処理をスッキリ分離できるので便利な例外処理ですが、開発の過程では逆に厄介な現象を招くこともあります。

例外処理の基本

C++やJavaScriptなどでは、例外としてどんなものでも投げることができますが、RubyやJavaでは、特定の基底クラスから継承したものしか例外として投げることはできません。

そして、例外クラスはたいていの場合、継承ツリーを通じていくつかに区分されています。Javaであれば「処理系自体の異常(Error)」、「チェック例外(Exception)」、「非チェック例外(RuntimeException)」、Rubyでは「通常実行時に起きる例外(StandardError)」、「それ以外の例外(Exception)」となっています。そして、Javaでは検査例外がありますが、Rubyでは何も明示しなければStandardErrorだけを拾う、ということになっています。

ハマったパターン

Rubyでプログラムを書いていて、ちょうどRailsのsavesave!のように、例外を返すバージョンと返さないバージョンの関数を作り分けていました。

get-get!
def get!(*args)
  # 異常時には例外を起こす
  raise ArgumentError unless hoge
  # 各種の処理
  piyo
end

def get(*args)
  get!(*args) rescue nil
end

このようにして書き進めていたところ、getの返り値が(argsを考えれば何かしら返すはずなのに)nilになってしまう、ということが起きました。原因を追いかけてみると、get!の内部でハッシュの添字に指定する値を間違えたために取得した値がnilになっていて、それにメソッドを呼び出した結果がNoMethodErrorとなっていました。で、NoMethodErrorStandardErrorのサブクラスなので、get内でrescueされていて、外にも例外が見えなくなってしまっていたのでした。

結局、get!内のバグを修正したのはもちろんですが、rescueも内部で意図的に生成しているArgumentErrorだけ拾うようにして、今後似たような問題が起きるのを予防することにしました。

Rubyでは、呼び出した先にメソッドがないNoMethodErrorや、ローカル変数がないときのNameErrorといった例外もStandardErrorのサブクラスなので、何も書かないrescueでキャッチされてしまいます。

教訓

起きる例外がわかっている場面では、必要最小限の例外だけキャッチしましょう。Rubyでは、プログラムミスで起きる例外でもrescueされてしまうことがあります。

また、メモリ不足や処理系内部のエラーなどは、自分で対処する方法がないのであればキャッチしてはいけませんし、シンタックスエラーやファイルロードの失敗といった、基本的にプログラムミスでしか起きないようなものは、irbpryのような、ユーザー入力をプログラムにするようなものを作っているのでなければ、キャッチするよりプログラム側、gem導入など処理系側の修正が必要な場面でしょう。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2