まだぬるぽで消耗してるの?


このAA、もうだいぶ死語というか、使われなくなったというか、時代を感じさせるAAですが。。。

Javaで開発する上で避けて通れない(と思われている)のがNullPointerException(ぬるぽ)との戦いです。

例えばこんなコードを現場のあちこちで見かけるのではないでしょうか。


public class SomeService {
 
  public SomeModel calsSomething(SomeDto someDto){
    if(someDto==null){ //Java 7以降だとObjects#isNull
      //nullだったときの処理。
      //だいたいreturn null返したりとか、時にはIllegalArgumentException投げたりとか 
    }
    //なんやらかんやら
  }

}

「ぬるぽ」「ガッ」というインターネットスラングがあるように、「ぬるぽが起きたらその時点でnullチェック入れて潰す」ということを反射的にしている人も多いのではないでしょうか。

しかし、盲目的にnullチェックを入れるような対応をしていると、intやdoubleなどの基本データ型以外の引数を受け取るところ全てにnullチェックを入れないといけなくなります。

そこかしこにnullチェックが入っているとソースコードの見通しも悪くなりますし、あまり推奨できる方法ではありません。消耗します。

ただでさえ「冗長だ!」と批判を受けることの多いJavaなのですから、さらに冗長なコードを増やしたくありません。

ではどうするか。

設計段階でnullを排除する

まずこれを一番に考えるべきです。

業務要件上、nullを「許容しなければならない」ケースはほとんどありません。

「別に業務要件上は空欄であることが表現できればよい」といって、「別にnullでもいっか」という最初の設計の手抜きが、納品直前の地獄のnull潰しにつながります。

データベースにNULLを許容しない

まずデータベースに「NULLがない」状態を作りましょう。

運用を開始してからだと変更に手間がかかったりするので、最初の実装に入る前の設計でNULLを拒絶するのがベストです。

データベース定義は、デフォルトだとNULLを許容する仕様のものが多いので、ここで明示的にNOT NULLを指定することが重要です。

よくあるパターンは以下の3点です。

1.レコードの作成日時を表すcreated_atは、DEFAULT NULLではなく、NOT NULL DEFAULT CURRENT_TIMESTAMPで定義する

よくアプリ側でDaoのinsertメソッドを呼ぶときに

entity.setCreatedAt(Calender.getInstance().getDate());

でEntityに現在日時を突っ込んでいるコードを見かけますが、テーブル側でデフォルト値はCURRENT_TIMESTAMPだと宣言し、NULLを嫌うほうが安全です。

アプリ側で日付をセットするやり方をしていると、update系のメソッドでうっかりセットを忘れてレコードにnullが入ってしまう!とかあるあるです。

2.プライマリキーのシーケンスはアプリ側で作らず、PRIMARY KEY AUTO INCREMENTで定義する

「そんな馬鹿な設計ねえよ!」と思うかもしれませんが、プライマリキーのシーケンスをアプリ側で作っている残念なケースは結構あります。

そのメソッドがほぼ同時に呼ばれた場合、テーブルのレコードはどうなってしまうのでしょうか。。。

3.任意入力の項目は、DEFAULT NULLではなく、NOT NULL DEFAULT “”で定義する

業務系アプリの「備考欄」などでよく見られるパターンです。

「空欄でも良い」ものは、業務上の優先度も高くないので、開発者側の意識も結構いい加減になりがちです。

nullを許容していると、例えば文字列の長さをチェックする、以下のケースでNullPointerが発生しますね。


if(memo.length() == 0){
  doSomething();
  // 空文字だと安全だが、memoがnullだとNullPointerException
}

それではこのメソッドにnullチェックを入れれば良いのか?

そうではなく、「空欄は空文字で表現する」って最初に決めておいたほうが良いですよね。

コード量も少なくなります。

nullの可能性があるメソッドはOptionalを使う

どうしてもnullを許容しなければいけない(データがない、という状態を受け入れなければならない)場合、Java8から対応されたOptionalを使用しましょう。

例えば、idでテーブルを検索したがレコードが見つからない場合。


public class SomeService{

  @Autowired
  private SomeRepository someDao;

  //Daoがフレームワーク・ライブラリの仕様でnullを返す場合
  public Optional<SomeDto> findOne(long id){
    return Optional.ofNullable(someDao.findById(id));
  }

}

SomeService#findOneは、呼び出し側に「その値が存在するかどうかのチェック」を強要します。

強要するというと聞こえが悪いかもしれませんが、やることが明確になっているので、「どこにnullが飛んでくるかわからない!」状態よりはだいぶマシです。

「Optionalが戻り値として飛んでくる場合には存在チェックが必要、生のインスタンスが返ってくる場合には絶対にnullは入ってない!」というルールに従ってコーディングできます。

ポイントは、「どうしても」必要な場合にのみOptionalを使うことです。

何でもかんでもOptionalで表現していたら、結局Optionalのチェックでnullチェックと同じようなことをする羽目になります。

nullに対する考え方をチームで話し合って統一しよう

近年の言語だと上述したOptional的な考えを標準で取り入れている言語が多いので、開発者の価値観的にも、「まぁ、nullとかないよね」というところで暗黙のコンセンサスが取れていたりします。

ところが、Javaに長年携わってきたエンジニアはそうではないかもしれません。

「ぬるぽ」「ガッ」のネットスラングが示すように、アプリ側でnullチェックをするのが一昔前では当たり前のようにありました。

特に、Javaの古いライブラリを使っていたりすると当然のようにnullを返すメソッドで構成されていたりします。

JPAのEntityManagerでも、存在しないprimaryKeyに対して#findを呼ぶと普通にnullを返してきます。

Returns:the found entity instance or null if the entity does not exist

EntityManage APIDoc

そういう文化で育ってきた人にとっては「コードでnullチェックをするのが当たり前」であって、「設計でそもそもnullをなくして、nullチェックの手間を省こう」という発想はないかもしれません。

もちろん、DBだけではなくServiceとかJavaのビジネスロジックでも普通にnullを返すようなロジックを実装するかもしれません。

現場で一番大切なのが、やはり「nullに対しての扱いをチーム内で話し合って決める」ことだと思います。

極端なことを言えばOptionalですら選択肢の一つであって、要は「ここにnullは来るのか?どうなんだ!」ということが、いちいち呼び出し階層の底まで調査しなくてもわかること(そして闇雲なnullチェックをばらまかないこと)が一番重要なことです。

開発する上での前提をなる考え方を共有し、そこをブラさない。

そうすれば、「考慮しなければならない要素」がコードベースから一つ減るわけです。

考えることを減らして楽をしよう。

Optionalという考え方はJava特有のものではありません。

モダンな言語だと、「変数にnullは入れられない」のが言語仕様として決まっていたりします。(例えばswiftとかScala)

こういう言語では、「nullかもしれない」変数は明示的にそれを記述しなければいけないようになっています。

「Optionalが戻り値として飛んでくる場合には存在チェックが必要、生のインスタンスが返ってくる場合には絶対にnullは入ってない!」というルールで言語自体が作られているわけです。

別に意地悪がしたいわけではなく、そのほうが考えることが少なくて楽なわけです。

人間の頭のキャッシュメモリは有限です。(というかPCよりはるかに少ないと思います。)

頭に置いておかないことが少なければ少ないほど、業務ロジック(本来やるべきこと)に集中できます。

それでは、ぬるぽで消耗しない楽しいJavaライフを。