仕事で外注のPGさんが悩んでいたバグが中々興味深いものだったので紹介。
因みに仕事ではjavaやCOBOLを「読む」ことが多いです。
以下のようなコードを実行するとNullPointerExceptionが出ておりました。
超絶うろ覚えですのでところどころ分かりにくいのはご勘弁を。
<問題のコード>
try{ /*classhogeのobj hogeObjを作成。確かdb接続処理系のクラス。 *なぜかnullで初期化してる・・・*/ classhoge new hogeObj = null; if(hoge == 1){ //・・・① //処理 }else{ return; } /*hogeObjのメソッドを呼んでなんか処理してる *その結果を自分に代入?? *じゃあ最初の初期化は何よって話だけどクラスの中身よく見てないから分からぬ*/ hogeObj = hogeObj.hogeMethod(foobar); //・・・② }catch(Exception e){ //例外処理 }finally{ hogeObj.foobarMethod(); //・・・③ }
こんなカンジのコードを実行して、①のif文でelseにとんだら
returnで呼び出し元に戻る・・なんて処理を想定していたようです。
で、hogeに1以外の値を入れて実行してみたらNullPointerExceptionに・・なんで?
javaのエラーコードを見るとどうやら③でNullPointerExceptionが起きている模様。
returnで抜ける時に何が起きたのか?処理順で言うと以下の流れで実行されていた。
- if文でhogeが1以外だったのでelseに飛んで呼び出し元にreturnしようとした
- if文がtry-catchブロックの中に書かれた処理で、そのtry-catchブロックのお尻にfinallyがくっついてたのでtry-catchブロックを抜ける前にfinallyブロックの処理が実行された
- しかしfinallyの③は最初にnullで初期化されたままの状態。本来は②でなんかする処理が必要だった。
- ので、当然ぬるぽとして検知され例外発生。
returnする場合のhogeObjの状態をきちんと考えずに
returnで抜けようとしてたのが原因。
finallyにそんな状態が分かりにくいオブジェクトのメソッド書くなとも言えるけど、
ここで呼んでるのは確かdb.closeみたいな処理だったので普通はfinallyに書くものか・・
やっぱオブジェクトの状態遷移というか、ちゃんとメソッドを呼べるようになっているか
どうかを考えながら書いてないのが原因かなぁ。
とりあえずの対処法として、tryの位置を①のif文の直後に変更して
if文はtry-catchブロックの中に含まれないようにすることで、
returnするときにfinallyに飛ばないようにしました。
hogeObjのインスタンス化をtryの直後にやってあげてもいいんだけど、
まぁif文の処理を見るに例外が出そうな処理じゃなかったのでtry-catchから
外すだけでいいんじゃね?と。
にしても、ワシもreturnは問答無用で呼び出し元に戻る命令だと思ってたので
やらかす可能性はあった訳です。
finally付きのtry catchの中でreturnするときはfinallyを必ず通ってから
呼び出し元に戻るという規則に気をつけねばなりませんなー
追記:
ちょっと違う話ですが、finally句でreturnしようとすると
そこに行き着くまでのtry文で発生した例外がthrowされなくなってしまうそうです。
これも気をつけないといけないですね。ってかfinallyでreturnしてるコード未だに見たことないけどw
finally句におけるreturnの問題