Memento memo.

Today I Learned.

Effective Ruby 第四章 例外 まとめ

Effective Ruby 第三章 コレクション まとめ - Memento memo. の続きです。

今回はRubyの例外についてです。

例外は、以下の2つの異なる言語機能をまとめたものだと考えられます。

  • エラーの説明
  • 制御フロー

raiseにはただの文字列ではなくカスタム例外を渡す

  • raise に文字列だけを渡さず、例外のクラス名を指定する
  • または例外クラスを(パラメータと共に)インスタンス化してraiseに渡す
  • カスタム例外クラスにinitializeメソッドを定義するときはsuperを呼び出す
raise("something wrong") # NG
raise(CustomError, "something wrong") # OK
raise(CustomError.new(param)) # OK
raise(CustomError.new(param), "something wrong") # OKだが、第一引数のオブジェクトのメッセージが失われる
  • 新しい例外クラスは、標準例外クラス(主にStandardError)を継承し、慣例的に名称をXxxErrorのようにする。
  • 1つのプロジェクトに複数例外クラスを作る場合はStandardErrorを継承した基底クラスを作成し、個々の例外クラスはそこから継承する。

出来る限り最も対象の狭い例外を処理する

  • 修復プロセスがわかっている特定の例外(DB、NW接続エラー等)だけをrescueで捕まえる
  • 限定された例外(詳細度の高いもの)から順にrescueする
  • 汎用例外クラスをrescueで補足してはダメ。ensure節を作るか、上流で処理すること。
  • rescue節内で例外が発生すると新しい例外が現在の例外を押しのけてしまう。その場合、新しい例外より元の例外をraiseするのが良い。

リソースはブロックとensureで管理する

  • メモリはGCで処理されるが、メモリ以外のリソース(オープンしたファイル等)の自動的な開放は保証されない。
  • ensure節でリソースを確実に開放する
  • ensure節の式はbegin本体と同じスコープだが、変数が初期化されていない可能性に注意する
# ensureによるリソース管理
begin
  file = File.open(file_name, 'w')
  ...
ensure
  file.close if file 
end
  • ブロックで抽象化して同等の処理が可能
File.open(file_name, 'w') do |file|
  ...
end
  • File::openと同じようなブロック&ensureパターンを独自のリソース管理クラスでも実装すると良い(以下のような実装)

Lock.rb

ensureは最後まで実行して抜ける

  • ensure節の中で制御文(return, throw, next, break)などの制御文は絶対に使ってはならない
  • ダメ、絶対

retryで回数上限を設け、頻度を変化させ、オーディットトレイルを残す

  • 無条件のretryは無限ループに等しい危険性がある
  • retry実行の境界変数はbegin節の外のスコープに定義する
  • retryを使うときはaudit trail(監査証跡)を作ること。エラーのイベント連鎖を必ずログに残す。
  • retry時のディレイはrescue節の中で値を(指数関数的に)増やしていくことを検討する。

retry.rb

スコープから飛び出したいときはraiseではなくthrowを使う

  • catchとthrowは例外とは関係ない。gotoの安全versionと考える方が近い。
  • 複雑な制御フローが必要な場合はraiseよりthrowを使う。throwの場合スタック上位にオブジェクトを送ることができる。
  • ただしthrow、catchを多用してはならない。出来る限り単純な制御構造(return)を使う。

ネストの深いループから一撃で脱出したい場合はthrowすると良いみたいです。ただ、ネストの深いコードは循環的複雑度が高くなっちゃうのでそもそもそういったコードは避けるべきですね。


Effective Rubyは八章構成なので、前半戦終わりです。

次はメタプログラミングです。一番重そうな章です。

Effective Ruby

Effective Ruby