ひさびさにPerlベストプラクティスネタ。
Perlで例外処理というと、evalして$EVAL_ERROR(= $@)の文字列を判定するしかないと思っていたのだけれど、ちゃんと例外オブジェクト使う方法があった。やっぱりなんでもありだなPerlは。
PBPによると、例外オブジェクトは自分で作ることもできるのだけれど、いくつも注意しなければならないポイントがあるようだ。
- 文字列変換(q{""})のオーバーロード
- 例外をキャッチしたかを返すcaughtメソッド
- エラーメッセージの出力先の変更
このあたりが正しく実装できていなければだめ。しかしながら、CPANにちょっとお伺いをたてるだけで、例外用のException::Classといういかにも便利そうなモジュールを利用できるらしい。
このException::Classを利用して、Perlの例外オブジェクトを定義してみた。
use warnings; use strict; use English qw( -no_match_vars ); use IO::Prompt; use Exception::Class ( 'X::TooLongString' => { fields => ['value'], } ); sub X::TooLongString::full_message { my ($self) = @_; return "String " . $self->value . " is too long"; }
フィールドにvalueを持つ例外クラス、X::TooLongStringが定義できた。
このクラスをつかって例外を送出してみよう。次のサブルーチンではユーザから入力を得るのだけれども、あまりにそれが長過ぎた場合には例外を送出する。
sub process_string_from_user { # ユーザから値を得る # use IO::Promptが必要 my $user_str = prompt 'Give me long string: '; # 文字列の長さが100文字以上なら例外を送出 X::TooLongString->throw( value => $user_str ) if (length $user_str > 100); # 100文字以上の文字列を受け付けないデバイスを使った # 処理がはじまる }
Exception::Classでつくったクラスはthrowメソッドをもっているので、そのメソッッドを利用すれば、簡単に例外を投げることができる。
このコードをつかって実際に例外を捕捉するのには、次のようなコードを書いてみた。
# try eval { process_string_from_user(); }; # catch if ($EVAL_ERROR) { if ( X::TooLongString->caught ) { my $user_str = $EVAL_ERROR->value; print qq{"$user_str" is toooooooo loooong.\nSpilled data was lost.. Never mind.\n} } }
evalでキャッチされた例外は$EVAL_ERROR (= $@)に入る。そのとき、もし$EVAL_ERRORがX::TooLongStringオブジェクトであれば、X::TooLongStringのメソッドcaughtはTrueになる。
というわけで、実際にX::TooLongString例外を起こしてみよう。
$ perl exception_class.pl Give me long string: hakobe $ perl exception_class.pl Give me long string: toooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooolooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong "toooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooolooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong" is toooooooo loooong. Spilled data was lost.. Never mind.
ちょうながい文字列を入力すると確かに例外が送出された。んでもって、$EVAL_ERROR->valueをすることで発生した例外オブジェクトから得た、文字列データを利用することもできた。
例外オブジェクトを利用することで、より直感的に例外を扱えるようになった。再利用しにくい文字列処理やパッケージ変数(= グローバル変数)にもなるべく会わなくてすむ。
しかし、例外で処理すべき物とifで処理すべき物の違いが直感的にわかんないんだよな。ifで処理というとエラー処理かしらね。エラーと例外は違う物らしく、
- 例外: プログラムが実行を進めるなかで起こり得ないもの -> 起こったらどうしようもないもの?
- エラー: そのプログラム内でどうにか処理できるもの?
うーん?