ログインしてさらにmixiを楽しもう

コメントを投稿して情報交換!
更新通知を受け取って、最新情報をゲット!

手作りネットプロトコル工房コミュの例外の扱い方 (JavaのDOM と Stream)

  • mixiチェック
  • このエントリーをはてなブックマークに追加
ストリーム例外の扱い

Javaの InputStream と OutputStream は若干扱いが厄介なところがあると思う。 なぜかというと、ほとんど全てのメソッドが throws IOException だからだ。 これのおかげで、まいどまいど try catch ブロックを書かなければいけない。 面倒だ。

しかし、僕はこれには理由があると思う。 面倒だが、これで正しい。 なぜなら、データの接続は常に切れるかもしれないからだ。 例えば Socket。 これは どんなにプログラムをきちんと書いても、接続先のPCの電源を落としてしまえば必ず接続は切れる。 これは、想定の範囲内の動作であり、正常なパターンだ。 だからこそ、報告の義務がある Exception を継承した例外クラス IOException が利用されるわけだ。 その他、データの接続という物は常に予告無く切断されるケースがありえる。 だから多少面倒なようであっても throws IOException で正しい。

このことはあまり知られていないようだ。 例えば、最近のJREシステムライブラリを更新している人の中にはこれをきちんと理解しないで作業している人も居る。 例えば 最近追加された機能 JConsole を実装した人は、いまいちこのことを理解していないようで、ソースを見てみると、わざわざ IOError という Error クラスを継承したクラスを sun パッケージ配下に作って、自分一人だけ勝手にそれを利用しているが、これは作法としてあまり良くない書き方だと思う。 何か別に理由があるのかもしれないが、このようにコーディングしたライブラリを利用するプログラムは、処理中に発生したデータ接続の破断を検知しづらくなってしまう。 検知できなくなるということはないのだが、ライブラリごとに IOExceptionの扱いが異なるというのは、ライブラリの利用者にとっては厄介な問題だ。

だから ライブラリの設計者は絶対に IOException は 無視してはいけない。 IOException は 特に理由が無い限りは throws を使って外部に波及させていくのが正解だ。 安易に RuntimeExceptionでラップして外部から隠蔽したりしてはいけない。 InputStream OutputStream が引数として渡されるメソッドは、必ず throws IOException と宣言すべきで、それがもっとも正しい IOException の 扱い方だ。 利用者に面倒をかけるようだが、こうすることで、そのメソッドの利用者は、事故的なデータ接続の破断を検地して、期待したとおりにエラーから復帰する事ができる。



もう一つセオリーがある。 InputStream / OutputStream を渡されたメソッドは、特にはっきりした理由が無い限りは絶対に close() してはいけないということだ。 InputStream / OutputStream は ものによって ライフサイクルがバラバラだ。 一般的に、 ソケット通信のプログラムを組んでいる時は、いろいろな出来合いのメソッドを再利用して組み合わせることで出来るだけ簡単に目的の処理を実現したいものだ。 こういうとき、利用しているメソッドの中に処理が完了するとご親切にも勝手に close() メソッドを呼び出すメソッドが居たらアウトである。 後続の処理が全てエラーになってしまう。 close() メソッドは そのストリームを作成した文脈だけが呼び出すべきなのだ。

それでも、「ストリームの終了を検知したのだから、もう二度と利用される事など無い、だから close() を呼び出すのが正しい」と思われる方もいるかもしれない。 それは間違っている。 ストリームの終端を検知したからといって以降絶対に再利用されないとは断言できない。 reset() を使って巻き戻して使うプログラムもあるからだ。 RandomAcceessFile.getFD() をつかって ファイルデスクリプタを共有している場合なども、これに相当する。 ストリームの終端を検知しても seek() すれば 再度読み込みが可能なのだ。 だから InputStream OutputStream をパラメーターとして渡されるメソッドは close() を呼び出してはいけない。

もちろん一概に全てのパターンでそうだとは言い切れない。 マルチスレッドでストリームを処理する場合など、どうしてもこのセオリーに乗っ取って処理を完了させる事が出来ない場合もある。 そういう場合でも、キャッチボールの様に誰がそのストリームのclose()を呼び出す権利を持っているのかをはっきりと意識してコーディングする事はとても大切だ。



今日随分悩んだ。 何故かいつの間にか 共有している FileDescriptor が 無効になってしまうのだ ...。 色々調べて一つだけわかったことがある。 XMLを解析しようとして DocumentBuilder.parse() を呼び出すと、いつの間にかその解析されたストリームが close() されているということだ。 これで気がついた。 Java DOM の InputStream / OutputStream / IOException の扱い方が、メチャメチャなのだ。

例えば、 InputStream を引数として渡すメソッド内で IOException が発生すると SAXException でラップされてしまう。 これでは、何が原因で処理が中断したのか区別が付かなくなってしまう。 要するにXMLの文法エラーだったのか、それとも ソケットが破断したのかわからないのだ。 しかも こういう場合の頼みの綱、getCause() は微妙にバグがあるようで、 2段以上ラップされると 中身が取得できなくなってしまうようだ。 これは致命的だと思う。 挙句の果てには、処理が終われば勝手に close してしまう。 全くわがまま奔放、勝手極まりないライブラリといえる。 これはちょっと酷い。

僕はこういうわがままを相手にするときはこういうクラスを書く。

final FileInputStream in = new FileInputStream( fd ) {
  @Override
  public void close() throws IOException {
  }
};

これでオッケー。

なお、getFD() を使って FileDescriptor を取得して使っている場合は、close() を呼び出すと その FileDescriptor を利用している全てのストリームが自動的に閉じられてしまうため、 何かトラブルがあったら close()メソッドを殺してしまうのが一番災いが少ない書き方だと思う。

閉じる時は、大元のストリームのclose() を呼べばよい。



僕は Javaがとても好きだ。 だけど、 Javaに関連する色々な有名ライブラリの設計が、実は結構酷い代物で、結局巡り巡って Javaが評判を落としているという事はしばしば目にするような気がする。 Java自体はとてもよく出来ているのに J2E○ とか Apa○○○とか そういう名前がつくライブラリは、捨ててもまだ害が残るような酷いものが多い様な気がする。

Derby も Apa○○○ だけど 僕は物凄く好きだ。 でも あれは もともと IBM の CloudScape というとっても素敵な名前のライブラリだったもので、IBMがApacheに寄贈したのだそうだ。 しかし Derby DB ってなんだよ、その名前。 ほとんど オヤジギャグだよ。 ダサすぎる。 何かテキサスの田舎の古びたガソリンスタンドのさえないオヤジが、ハゲ面でボソボソつぶやいているギャグのような印象を持つのは僕だけだろうか...。

コメント(0)

mixiユーザー
ログインしてコメントしよう!

手作りネットプロトコル工房 更新情報

手作りネットプロトコル工房のメンバーはこんなコミュニティにも参加しています

星印の数は、共通して参加しているメンバーが多いほど増えます。

人気コミュニティランキング