著作一覧 |
いずれにしても、スコープルールがよろしくないのは議論の余地がないと思うので、とはいってもJavaを利用する限りは現時点ではそういうルールなのでとりあえずはしょうがない。
ではどうするのが良いのだろうか? という点に絞って考えてみる。
BufferedReaderについては明かなことがある。
それは、このクラスが利便性追求クラスだということだ。
というのは、すでにしてFileReaderがあるのだから、実際問題としてBufferedReaderのメリットは(バッファリングなんてOSが勝手にやってくれるのだから、別にJavaのクラスでやらなくても良い)、readLineメソッドの存在にある。
つまり、行指向のテキストファイル(CSVもそうだし、プロパティ形式もそうだし、まあ、世の中たくさんある)の読み取りを簡単にするための存在である。
java.io.*には、IOExceptionを飲み込むクラスがいくつかある。それらはIOExceptionをスローするクラスに対するデコレータで、特徴は利便性を追求したメソッド群の提供にある。
具体的には、PrintWriter。こいつはIOExceptionをスローする代わりにcheckErrorというエラー発生問い合わせメソッドを持つ。
なんという不統一。
BufferedReaderもしょせん利便性クラスなのだから、同じことをすれば良いのだ。あるいはどうしてもバッファリングを提供するということを目的にしていると言うのであれば、同じようなFileReaderのデコレータとしてLineReaderというようなクラスにしても良かったのだ(このほうが、readLineメソッドを提供するという特徴からはむしろ良いかも知れないが、かといって似たようなクラスがこれ以上増えるのも問題ではある)。
かくして、PrintWriterと同様な割り切りをすれば、readLineを提供するという目的に合致するのは、以下の実装である。
boolean error; public Iterator<String> iterator() { return new Iterator<String>() { String line; public boolean hasNext() { try { line = readLine(); } catch (IOException e) { error = true; } return line != null; } public String next() { return line; } public void remove() { throw new UpsupportedOperationException("read only"); } }; } public boolean checkError() { return error; }
これならば、元に戻って、forをうまく利用できる。
for (String buffer : bufferedReader) { foobar(buffer); if (buffer.checkError()) { ... // 必要なら } }
実際には未定義なので、もしforを使いたければ、次のようなぶかっこうで反DRYな方法となる。
for (String buffer = bufferReader.readLine(); buffer != null; buffer = bufferReader.readLine()) { foobar(buffer); }
でも、別の考え方もある。
もし、foobarを以下のように実装したらどうだろうか?
boolean foobar(String buffer) { if (buffer == null) return false; .... return true; }
もし、そうであれば
while (foobar(bufferReader.readLine());
どえらくシンプルで美しい。
でも、この美しさは、やっぱり偽りのもので、foobarがなぜ、BufferedReaderの代わりにEOF判定しなきゃならんのだ? ということになる。
実はもっと良い方法、つまり、IOExceptionの飲み込みもせず、役割をおかしな位置へ移動することもせずに済む方法、がある。
そもそもIOExceptionの飲み込み問題が起きているのは、Iteratorが外部にあるからだ。
次の標準イテレータの存在を仮定する。
public interface InternalIterator{ /** @return 列挙を中止するのであればfalse */ boolean next(T data); } ... public void readLine(InternalIterator i) throws IOException { ... }
この場合、元のアプリケーションは以下となる。
br = BufferedReader(...); try { br.readLine(new InternalIterator<String>() { public boolean next(String data) { foobar(data); return true; } }); } catch (IOException e) { ... }
美しい。
ジェズイットを見習え |