エラー処理の方法
仕事で他人(といっても先輩だけど…)の書いたコードを書き直すことがよくあるんだけど、
種類の違うエラーが全て1つのフラグ変数によって処理されてて、非常に読みにくかった。
雰囲気はこんな感じ
# 汎用のエラー変数(変数名の意味は謎...) my $iErrFlg = 0; my $Errmsg = q{}; # ... if ( !$dir_name = getTodaysdir() ) { $iErrFlg = 1; $Errmsg .= "Failed to make dir for today.\n"; # ここで抜けずに、処理はつづく... (つまり、最後までどんなエラー処理が行なわれるかわらない) } # ソースが続く ...(しかも途中で、エラー判断のたびに $iErrFlg が使いまわされてる) # やっと最後の段階になってエラー処理 if ($iErrFlg) { sendErrMessage($from, $to, $Errmsg); exit 1; }
直すのがめんどうに感じたので、このエラー処理構造は変えずにコードを編集していったら、さらに読みにくくなって、とてもじゃないけど、異常系の処理の流れが追えなくなってきた。
問題点は、
- エラーがその場で処理されないので、最後までソースを読まないとどう対処しているか分からない
- エラーがその場で処理されないので、エラーの処理を忘れる恐れがある
- そもそもエラーフラグを使いまわしているので、コードの位置によってフラグの意味が変わってる
- 「何かエラーが起きた」という意味は当然変わってないが、「何で起きたエラーか?」という意味が変わる
- このフラグでは「エラーが起きたかどうか」しか分からない。よってエラーの原因を探るにはコードを追う必要がある
反省して、「Perl Best Practice」を読んでみたら、まさに求めていた処理方法が詳しく載っていた。
ポイントは、何かがうまくいかなくなったら
「特別なエラー値を返す」
のではなく、
「例外を送出する」
というところ。
ファイルを開くだけの簡単な関数 file_open() を例にすると、以下のようになる。
use Carp; for my $file_name (@ARGV) { # 明示的にエラーを補足(補足しなければ例外が投げられてこの場で終了する) if (my $fh = eval { file_open($file_name) }) { print "Open SUCCEEDED for file ($file_name)\n"; # $fh を使ってごにょごにょ } else { print "Open FAILED for file ($file_name)\n"; # 失敗した場合の処理が続く(メールを送って終了するとか) } } sub file_open { my ($file_name) = @_; if (-r $file_name) { open my $fh, $file_name or croak "Readable ($file_name), but could not open ($file_name)"; return $fh; } croak "Not even readable ($file_name)"; }
これだったら、エラー処理のし忘れの心配もない。
また、例外を無視(補足)するためには、明示的に eval を使う必要があるので、「補足されないエラー」に注意を払うことができる。
明日会社に行ったら早速実践してみよ。
参考
Perlベストプラクティス:p.296