10億ドルの誤り
NullPointerExceptionをデバッグするのは 面倒でつまらない作業だが、改善することはできる。
nullチェックを忘れない
NPEの原因は9割方、戻り値のnullチェックをしないまま、それのメソッドを呼び出そうとすることにあると思う。 だからフェイルファーストでnullをはじいておけば、デバッグがやりやすくなる。
Object obj == something();
if (obj == null) throw new NullPointerException();
戻り値にnullを使わない
もちろん毎回nullチェックするのでもいいが、大変だしミスもしやすい。
初めからnullを戻り値にしないようにプログラムしておけば、チェックの必要もなくなるし、プログラムの流れ自体も分かりやすくなる。
戻り値にnullを使うことはあいまいだ。例えば、あるコレクションのget()
がnullを返してきたとき、それは値がnullなのか、キーが存在しないのか不明瞭だ。
nullを戻り値にしたいと思ったとき、その必要があるか立ち止まって考えてみるとよい。
String returnsString() {
return ""; // nullではなく空の文字列を返す
}
List returnsList() {
return Collections.emptyList(); // 空のリスト
}
SomeEnum returnsEnum() {
return NONE; // 列挙体の場合、何も存在しないことを表すオブジェクトを定義しておく
}
ライブラリーなどが戻り値にnullを使う場合、Java 8のOptionalでラップしてやる。それ以前であれば、GuavaのOptionalを使う。
public static Optional<SomeObj> wrap() {
return Optional.ofNullable( library.func() );
}
パラメーターを信用しない
コンストラクターのパラメーターや、メソッドの引数がnullであるという可能性を考える。
自分では分かっているつもりでも間違えてnullを渡してしまうことはあり得るし、他人がNPEの危険を考えずにそのメソッドを呼び出すこともあり得る。
少なくともpublicなメソッドは、必ずnullチェックをしたほうが良い。
Guavaはユーティリティーメソッドを提供しているので、積極的に使うこと。
public class Something {
private String field;
public Something(String param, Obj param2) {
if (param == null) throw new NullPointerException(); // フェイルファーストでnullをはじく。
this.field = Objects.requireNonNull(param); // Java 7のコンビニエンスメソッド。staticインポートすると便利。
this.field = Preconditions.checkNotNull(param, "Error: %s", param2); // Guavaは%sをプレースホルダーとしてエラーメッセージを設定できる。
// initialize
}
}
文書化する
nullを返す可能性がある場合、Javadocに、nullが戻り値となること、その条件を明記する。 JSR-305アノテーションは簡単な目印になるだけでなく、Findbugs等の解析ツールと組み合わせてNPEの可能性を教えてくれる。
compile 'com.google.code.findbugs:jsr305:2.0.1'
@Nullable // このメソッドはnullを返す可能性がある。
public Obj Func(@Nonnull Obj param) { // パラメーターはnullであってならない
// ...
}
Resolver
http://winterbe.com/posts/2015/03/15/avoid-null-checks-in-java/で紹介されていたパターン。
public static <T> Optional<T> resolve(Supplier<T> resolver) {
try {
T result = resolver.get();
return Optional.ofNullable(result);
} catch (NullPointerException e) {
return Optional.empty();
}
}
NPEが発生する可能性のある処理をラムダ式の中にまとめて書く。 例外をキャッチした場合、空のオプショナルを返す。