FLAGS

MENU

NOTICE

2007年12月30日日曜日

(思いっきりプログラムネタ) ストリーム例外の扱い (mixi05-u459989-200712302126)

ミクシ内で書かれた旧おかあつ日記を紹介します。
(思いっきりプログラムネタ) ストリーム例外の扱い
2007年12月30日21:26
思いっきりプログラムネタで、絶対に誰も読まないと思うけど、折角書いたし、あぁ普段こういうことをやってるのか、程度の事を勝手に主張するために載せてみようと思う。

僕の友達&知り合い... それも全国津々浦々世界中の友達全員ひっくるめても、この話題をふってついてくる人などどこにもいないので、限りなく独り言に近いと思うけど、そこを敢えてアップするという心意気。 (無意味)



ストリーム例外の扱い

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 ってなんだよ、その名前。 ほとんど オヤジギャグだよ。 ダサすぎる。 何かテキサスの田舎の古びたガソリンスタンドのさえないオヤジが、ハゲ面でボソボソつぶやいているギャグのような印象を持つのは僕だけだろうか...。


コメント一覧
トミィ   2007年12月30日 23:49
心意気を買い、一字一句読んだ。読んだけど、理解はできない(←できるわけないと言うツッコミはなしね)仮に理解出来たとして、その頃には考え過ぎて私の頭はザビハゲになっていると思う。
bobcat   2007年12月31日 00:57
イメージだけは理解できたような…ええかげんなソースコード書いとるからブラウザが落ちたりフリーズしたりするのね?
DivXのStage6を見てるんやけど,ストリーミングがすぐに始まらなくてバッファかかったりすると必ずSafariが落ちるのよ。
(Firefoxとは相性悪いみたい)
そーゆーこともあるかも知れんね,アップロードされてるファイルが重いこともあるけど。
んで,ローカルに落としてDivXPlayerで見るとちゃんと見れたりします。
ろんたりんくo(^-^)o   2007年12月31日 08:44
>その他、データの接続という物は常に予告無く切断されるケース
>がありえる。だから多少面倒なようであっても throws IOException
>で正しい。

瞬電等のことを考慮した記述が最適解であると理解すればよろしいでしょうか。

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

ライブラリ内での記述でしょうか?
であればそういうコードになっているのはただの手落ちなんでしょうかね?

んーと、読んでいてそんなことを思いました。
おかあつ   2007年12月31日 09:58
この話は、全然抽象的な話じゃなく、超ムチャムチャ具体的な話で、いっこいっこ対応する事例があります。 でもひとつひとつ具体例を挙げていません。 大変な分量になってしまうからです。 それを経験した事があるという事がこの文章を読むための前提条件になっています。 しかし、この文章を読む前提となる経験を身につけるためには、Javaでのコーディング経験が最低でも5年は必要だと思います。

例を挙げると、Javaでソケット通信等々のストリームオブジェクトを使ったデータ処理の経験が必要です。 また 「例外の送出方法」にまつわるJavaの言語使用についての知識が必要です。 また、ここでいうストリーム処理というのはいわゆるインターネットで映画を見たりするストリーミング処理とは違うもので、 UNIXでパイプと呼ばれているようなものなのです。

これを全部前提知識から最後まで理解できる文章を書くと、間違いなく本になると思います。そのうちのいくつかのパートは、これまで一度も本になっていない内容を含むと思います。

つまり、この文章は、誰にも理解できない悪い文章なんだと思います。 僕が今考えている事柄をまとめるだけのための文章でしかない、といわなければいけないと思います。
oxoofo   2008年01月02日 02:04
読みながら書いています。
>理由があると思う。 面倒だが、これで正しい。
そーね。面倒だが、意味があるっていうのが例外の意味よね。
あと10年かなんか経って、もっと分かりやすい「注意の喚起の仕方」が発明されて、「昔はレイガイって言ってな」とかなって行くとしても、その発明過程として、歴史的な進歩の記念碑なんだろうなとか思います。続きはまた明日読みます。喋る速度で読んで書いてみました。

あ、もうちょっと読んだ。
オープンソースが発信の手段として、ナイスではあったのだけど、読み手のスピードが追いついていない事が今ネックなのか?て事?
oxoofo   2008年01月02日 02:09
>という心意気。 (無意味)
というところを今読みました。
前から何度も言っているけれども、オカアツ、好き。
おかあつ   2008年01月02日 02:25
いつも oxoofo 様と技術系の話していると、何故か奥様からのとてもジェラシーのこもった冷たい視線を感じるのですが、ちょっと待ってください。 僕はノンケです! そうじゃないんです! 違うんですったら!
 
出展 2007年12月30日21:26 『(思いっきりプログラムネタ) ストリーム例外の扱い』

著者オカアツシについて


小学生の頃からプログラミングが趣味。都内でジャズギタリストからプログラマに転身。プログラマをやめて、ラオス国境周辺で語学武者修行。12年に渡る辺境での放浪生活から生還し、都内でジャズギタリストとしてリベンジ中 ─── そういう僕が気付いた『言語と音楽』の不思議な関係についてご紹介します。

特技は、即興演奏・作曲家・エッセイスト・言語研究者・コンピュータープログラマ・話せる言語・ラオ語・タイ語(東北イサーン方言)・中国語・英語/使えるシステム/PostgreSQL 15 / React.js / Node.js 等々




おかあつ日記メニューバーをリセット


©2022 オカアツシ ALL RIGHT RESERVED