IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

Effective Java 読書会 13 日目 「Java の例外めんどくさい」

IOException の catch に何を書いていいか分かりません><!

IOExcep tion... - mmr - はてなセリフ

はじめに

順番が前後しますが、今回は Java の特徴のひとつである例外機構についてです。

今回の範囲

223 ページ 〜 250 ページ

Java の例外

  • throw 可能なオブジェクト
    • Throwable インタフェースを実装したもの
    • Exception を継承しない Throwable は基本的に使わない
  • チェック例外
    • メソッドの実装者が「呼び出し元が回復可能」だと考えている例外
    • ちゃんと「なぜ、例外だったのか」理由が提供されるべき
    • 呼び出し元は try catch で囲むか throws 宣言を書く必要がある
    • Exception を継承していて RuntimeException を継承していないクラス
  • 実行時例外
    • 事前条件違反(仕様で明確に定義された条件が揃わない状態でメソッドが呼び出されたこと)を通知する手段として使われる
    • 実行時にこれが発生する場合は、メソッドの呼び出し元がコードを直すべき
    • 呼び出し元が try catch も throws も書く必要がない
    • RutimeException を継承したクラス
  • エラー
    • JVM が使う

ちょっとイメージが沸きやすいように台詞で言い換えてみる

チェック例外

男子「好きです!付き合ってください!」(メソッドの実行)
女子「別に好きな男の子がいるんです!」(回復可能例外、理由付き)
男子「じゃあ、友達になって!相談とかのるよ!」(回復)

実行時例外

男子「好きです!付き合ってください!」(メソッドの実行)
女子「え、あなた誰ですか!?」(事前条件違反)

修正

男子「はじめまして、以前文化祭でチョコバナナを売ってましたよね」
女子「はい」
男子「あ、僕そのとき客として行ったの覚えてます?」
女子「あー!あのときの!」(事前条件)
時は流れて…
男子「好きです!付き合ってください!」(メソッドの実行)
女子「こちらこそ!」(成功)

エラー

男子「好きです!付き合ってください!」(メソッドの実行)
女子「…」
女子「…」
女子(神)「私だ」(エラー)
男子(神)「お前だったのか」
女子(神)「暇を持て余した、神々の」
男子(神)「遊び」
-- プログラム終了 --

チェック例外は負荷が高い

try catch は使う側のコードが非常に煩雑になる

チェック例外が生みだす煩雑さが正当かを考える

チェック例外は使う側のコードを煩雑にするけど、以下の二つの条件が成立する場合、その煩雑さは正当化される。

  • その例外が発生する事前条件を定義できない
  • 呼び出し元が、 catch したときに有用な処理を書ける(例外が、なんらかの情報を持っている)

この二つの条件を満たさない場合は、実行時例外にするべき
たとえば、呼び出し元が e.printStackTrace() くらいしかすることがないんだったら、実行時例外にしてしまおう。

標準例外を使おう

  • IllegalArgumentException
  • IllegalStateException
  • NullPointerException
  • IndexOutOfBoundsException
  • ConcurrentModificationException
  • UnsupportedOperationExcption

など、標準例外で用意されているものは積極的に使おう!

抽象概念に的した例外を投げる

適切に例外翻訳をしよう
たとえば、 findUser とかっていうメソッドから SQLException が投げられちゃだめで、 UserNotFoundException とかにしたほうがいい。
ただ、「何でも例外翻訳すればいい」はダメ。可能なら、そのメソッド内で例外が発生しないことを保証しよう。

例外を文書化しよう

  • チェック例外は @throws に書く
  • 実行時例外は javadoc の @throws には書くけどメソッドの throws には書かない

例外のメッセージに詳細な情報を含める

例外の文字列表現は、その例外の原因となった全ての情報を含んでいるべき。
以下のようなイディオムで書くといいよ

// コンストラクタで必要な情報をすべて受け取る
public FooException(int foo, String bar, long baz) {

    // 詳細メッセージを作る
    super("foo: " + foo + ", bar: " + bar + ", baz: " + baz);

    // フィールドに記録する
    this.foo = foo;
    this.bar = bar;
    this.baz = baz;
}

エラーアトミック性

すごい重要。
エラー、例外が発生した場合は、オブジェクトの状態をメソッド呼び出し前の状態に戻してあげる。

これは JavaScript をやっててすごく思うこと
たとえば複雑な DOM を構築するようなメソッドでエラーが発生した場合に DOM にゴミがいっぱいくっついた状態になってしまうことが多い
エラー発生時に内部で一回 catch して綺麗にするなどエラーアトミックにしておくことが重要。

例外を無視しない

チェック例外は、握りつぶさない。
例外を無視してもいい場合は、その理由を catch ブロックに記述する。
事前条件などで、例外が発生しないことが確実なら AssertionError を投げるなどする。

例外以外のこと

例外以外の項目に書いてあったこと

リフレクション
  • コンパイル時の検査がない
  • ぎこちなくて、冗長
  • パフォーマンスが悪い

あんまりメリットない。
フレームワークとか作るときには使うかもねー

ネイティブメソッド

JNI で C/C++ で書かれたメソッドを呼べる。
結構、めんどくさい。
速度のためにやるのは、オススメしない。
最近の Java は速いらしい

注意して最適化する

または、最適化なぞするな
最適化する前にプロファイリングなどをちゃんとやろう。

命名規約

空気を読んで、名前付けしましょう。

まとめ

うん! Java のチェック例外メッチャめんどくさい!