Open11

Null について立ち止まって考える

ykwsykws

Java には null という概念がある。
これは何もないことを表す。
数字における 0 と同じようでいて、少し考え方が異なる。コンテキストによっては同じものを指すこともある。それが混乱だったり、認識の齟齬を生む原因だったりして、モダンな言語( Swift や Kotlin など)では、この null という概念を扱うための言語仕様が用意されていることが多い。

ykwsykws

例えば、モバイルアプリにおいて、次のようなのユーザ名を保持する User class があるとする。

class User {
  String name;
}

これを API 経由で取得して画面に表示する場合に、以下のような疑似コードを用意した。

UserData data = api.getUserData();
User user = new User(data.name);

View view.name = user.name;

このコードには null について考える必要がある箇所がいくつあるだろうか。

ykwsykws
  1. api が null だったら
  2. data が null だったら
  3. data.name が null だったら
  4. user が null だったら
  5. user.name が null だったら
  6. view が null だったら
  7. view.name が null だったら

7 つもある。これについて、全てについて立ち止まって考える必要があるというお話。

ykwsykws

このコードをリリースしたところ、クライアントからユーザを表示するとアプリがクラッシュするのですぐに対応して欲しいと言われた。コードを次のように修正した。

try {
  UserData data = api.getUserData();
  User user = new User(data.name);

  View view.name = user.name;
} catch (Exception e) {
  // アプリがクラッシュしないようにする
}

アプリはクラッシュしなくなり、クライアントからは素早い対応で感謝された。素晴らしい!

ykwsykws

クライアントからはその後、問い合わせはこないものの、
アプリを利用しているユーザからは、自身の情報が表示されないことがあって、非常に不満であり、
このサービスは永くは続きませんでした。

極端ですが、今回の例だと、こういったシナリオも想定され得る対応になっています。

この対応は、多くの異常な状態を隠蔽し、あたかも正常に動いているかのように見せてしまっています。
このような対応を null の握りつぶし と呼んだりします。

では、どうしたら良かったでしょうか?

ykwsykws

クライアントから最初の報告をもらった時に時間を巻き戻してみます。

エラーのログを見ると、 data.namenull になっていることがわかりました。
そこでコードを次のように修正しました。

UserData data = api.getUserData();
if (data.name != null) {
  User user = new User(data.name);
  View view.name = user.name;
}

アプリはクラッシュしなくなり、クライアントからは素早い対応で感謝された。素晴らしい!

ykwsykws

しばらくすると、またアプリがクラッシュするとクライアントから至急の対応を依頼されました。

エラーログを見ると、今度は datanull になっていることがわかりました。
そこでコードを次のように修正しました。

UserData data = api.getUserData();
if (data != null && data.name != null) {
  User user = new User(data.name);
  View view.name = user.name;
}

アプリはクラッシュしなくなり、クライアントからは素早い対応で感謝された。素晴らしい!

ykwsykws

クライアントからはその後、問い合わせはこないものの、
今回の対応でもアプリを利用しているユーザからは、自身の情報が表示されないことがあって、非常に不満であり、結局このサービスは永くは続きませんでした。

今回の例でも、こういったシナリオも想定され得る対応になっています。

この対応は、局所的な null を考慮した対応をしたものの、コミュニケーションが不足しており、この対応でも、あたかも正常に動いているかのように見せてしまっています。
このような対応でも null の握りつぶし と呼ばれてしまいます。

では、どうしたら良かったでしょうか?

ykwsykws

立ち止まって考えるポイント

  • なぜ datadata.namenull になってしまったのか -> こういったケースでは、 API 仕様へのフィードバックが必要で、そちらのチームとコミュニケーションが必要
  • datadata.namenull の場合は、画面にどういった表示をしたいか -> プロダクトオーナーに確認し、そういったケースは想定しているのかコミュニケーションが必要
    • その状態をエラーとして扱うのか
    • エラーとして扱うならどのレベルか(アラートの表示やデフォルト表示等)
ykwsykws

そうすると、次のような要件が聞き出せました。

  • datanull の場合はサーバ側に異常が発生しているので、エラー表示と問い合わせボタンを表示して欲しい
  • data.namenull の場合は名前が未登録だとあり得るので、エラーとはせずに、デフォルトで「匿名ユーザ」と表示して欲しい

次のようにコードで表現しました。

UserData data = api.getUserData();
if (data == null) {
  showError();
  return;
}

User user = new User(if(data.name == null) ? "匿名ユーザ" : data.name);
View view.name = user.name;

今までと異なり、コミュニケーションが発生したため、すぐには修正はできませんでしたが、
クラッシュもなくなり、ユーザからも問題があった場合に問い合わせを受け付けることができ、
クライアントからも頻繁に追加要望が出てくるようになり、
サービスは永く続くことになりました。めでたしめでたし。

ykwsykws

続編。
アプリ内部ロジックにおいても安易に null を返さない
例外を返そう
さもないと状態の異常を握り潰してしまい、本当の問題が隠蔽されて、そこで発生する課題の解決が困難になる
Java 魂で読んだかもしれない