$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

はてなインターンの事前課題をJavaでやった - Java入門記(その2)

はてなインターンの事前課題で非常に簡単なltsvパーサーを作るやつがあるのだけど、Javaの勉強のためにJavaで実装してみた。ltsvパーサーは結構いろんな言語で誰かが実装しているので、これどうするのがいいのかってなったら、その実装を見に行くとやり方を理解できて便利。

これでいいのか気になるところもあるので、詳しい人に添削されたい。


いろいろ勉強になったので、勉強になったことを書いておく。

日付操作

http://qiita.com/tag1216/items/91a471b33f383981bfaa あたりが参考になった。しかしなかなか使うのが難しい。

epoch秒から文字列で出力するのはこんな感じ?こんなのでいいのか気になる。

LocalDateTime dt = LocalDateTime.ofInstant(Instant.ofEpochMilli(123456789000), ZoneId.of("UTC"));
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
System.out.println(dt.format(f));

コレクション操作

ArrayとかListとか、Mapとかのコレクション操作を学べた。Javadocとか見ても全然よくわからなかったけど、https://github.com/tokuhirom/java-handbook のCollectionsの説明が分かりやすくて助かった。

最近はListをstreamに変換して、mapとかいろいろやって、Listに戻すみたいなのが簡単にできるようになったみたいで便利。

ファイル読み込み

歴史的経緯とかがいろいろあって、どうやるのがいいのか最初全くわからなかった。それで、Javaで1行ずつテキストデータを読み込むイディオムの変遷 - argius note の記事が参考になって、ようやくできるようになった。

  • try with resourcesによる自動close機能
  • Streamとラムダ式を使った読み込み

辺りの勉強になった。以下全行をリストで読み込む例。もうちょっといい方法あるのかな。

List<String> lines;
try (Stream<String> stream = Files.lines(Paths.get("path/to/file.txt"))) {
    lines = stream.collect(Collectors.toList());
} catch (IOException e) {
    throw e;
}

例外の処理

LtsvParserを作るにあたって、

  • 存在しないファイルを渡した時
  • ltsvとしてパースできなかった時

に、どのようにエラーを返すか迷った。Golangだったらerrを返したり、ScalaだったらEitherを返したり、Perlだったらdieしたり、いろんな方法がある。Javaだったらどうするんだろうと。


見てみるとJavaはThrowableの文化っぽい雰囲気を感じた。そうすると今度はException, RuntimeException, Errorっていうのがあって、どう使い分ければ良いのか分からないという感じになる。

参考になったのは Throwableについて本気出して考えてみた - 都元ダイスケ IT-PRESS 。これによると、以下のようにまとめられていた。

  • Error
    • calleeのバグ発生を通知するもの
    • コンポーネントの仕様として、「自分がバグってたら投げるよ」の明示が必須
    • なぜなら、仕様外の動きをされた上に、Errorのthrowが明示されていなかったらcallerが究極に困るからw
  • RuntimeException
    • callerのバグ発生を通知するもの
    • クラスの仕様として、「変な呼び方したら投げるよ」の明示が必須
    • callerとしてはcallerのバグだが、calleeとしては仕様の範囲内の動作である
    • なぜなら、Javadocに挙動が明示されているから
  • Exception(非RTE)
    • 三者の異常であり、当事者(caller, callee)には責任が無い
    • バグ発生を通知するものではない
    • caller, callee両者にとって、仕様の範囲内の動作である
    • なぜなら、Javadocに挙動が明示されていて、かつ、catchによる処理が保証されるから

これを読んで、LtsvParserの例で、存在しないファイルを渡した時とltsvとしてパースできなかった時にはExceptionかRuntimeExceptionをthrowすれば良さそう。じゃあどっちをthrowしたらいいのか。

どっちを使う可能性もあるけど、今回はLtsvParserには存在していてltsvでパースできるファイルパスを必ず渡さなければならないという仕様にしたいと考えたため、RuntimeExceptionをthrowすれば良さそうということになった。


ではException(チェック例外?)はどのような時にthrowするのかって考えてみたけど、例えば

  • Webアプリケーションで、ブログ記事を作成する機能を考える
  • ブログ記事はタイトルが1文字以上100文字以下、コンテンツが1文字以上必要とする
  • ブログ記事を作成するControllerがあって、そのControllerは単にブログ記事を作成するロジックBlogEntryLogic.postEntry(String title, String content)を呼ぶとする
  • BlogEntryLogic.postEntry内でtitleが1文字以上100文字以下でなければBlogEntryTitleLengthException, bodyが1文字以上でなければBlogEntryContentLengthExceptionをチェック例外として投げる
  • Controllerはその例外を受け取って、最終的にユーザーの画面にどのようなフィードバックを返すか決める

みたいな使い方をするかなと思った。この辺詳しく触れるとバリデーションをどこでするか問題とかにもぶち当たって説明が面倒なので、詳しくは書かない。

まとめ

はてなインターンの事前課題は、日付・コレクション操作・テスト・ファイル・例外処理が一通り学べていい題材だと思った。