ノートの端の書き残し

UnityやらC#やら。設計が得意かもしれない。

null安全の正しさと誤り

null安全

null安全とは、nullを許容しないコンパイルルールのことを指します。 null参照は世の中のソフトウェアバグの中でも最多と言われていて、null参照を許すからダメなんだ、という思想ですね。言いたいことはわかります。

バグの原因はnull参照だ、というのは本当に正しいか

ある意味では正しいのですが、自身の経験を振り返ってよく考えてみると、null参照で発生していたバグは2つのケースに分けられると思います。

  1. 正常系でもnullが想定されてたけど、それを考慮せずにnull参照してしまったケース。例えば、RPGのパーティ編成で、空欄をnullで表現していたりするとこういうことが起こりそうですね。
  2. 異常系でnullが入ってしまって、null参照してしまったケース。例えば、File.Readのような関数を使った場合、ファイルが見当たらなければnullを返す、というのは自然でしょう。もちろん例外をスローする場合もあるでしょうが。

1. 正常系でもnullが想定されてたけど、それを考慮せずにnull参照してしまったケース

これはnull安全にすべきです。そりゃnullでもちゃんとチェックすれば動くでしょうが、nullを特別扱いしなければならない編成画面や戦闘中処理がかわいそうです。パーティメンバーをPartyMemberクラスで表現していたとすれば、空欄を意味するPartyMemberインスタンスを用意し、空欄の可能性を様々なAPIで伝える努力をするべきです。

2. 異常系でnullが入ってしまって、null参照してしまったケース

一方で、こちらはどうでしょうか。こちらのnull参照回避として

var (result, err) = file.Read("path");

こういう、go言語でよくみるような、ファイルが読めなかったらエラー変数に何か入れるというルールもあるでしょうし、関数型の言語ならResultやOptionといった、値を持つかどうか、処理が成功したかどうかを内包する構造体を使うこともあるでしょう。

ですが、これって実行時の処理という観点で言うと、nullチェックと変わらないんですよね。もちろんエラーの内容について型で判定できるとか、コンパイル時に判断できるとかの利点はありますけど、結局のところ、異常系の場合はこうする〜という処理は続けなくてはならない。nullチェックと実質同じです。nullの場合、というのに文脈上の意味を持たせているにすぎません。

null参照そのものが危険なのではなく、nullが意味を持ってアプリケーションレイヤーに表出するのが危険

2のケースは、例外が起こるパターンを考慮できていればそれで十分ですし、考慮できていなければダメなので、null参照がどうとかは正直関係がありません。

しかし、1のケースは、nullに「パーティメンバーが空欄である」という意味があると、チームで開発していたならチームメンバーもそれを知っている必要があります。ここに、不要な知識の要求が生まれるわけです。しかもコード上でコメントとか書かないと残らないような知識です。

問題の本質はここで、本来、アプリケーションの仕様上、nullが適切な場面というのは存在しません。「空欄」とか「何も持っていない」というのは、それをユーザーに見せなきゃいけない時点で、「『何もない』という情報を表現する実体」が必要になります。

DBの論理削除が批判されるのと同じだと思っています。論理削除の何が問題かというと、人が論理削除を選択するとき、本当にやりたいのは削除ではないんですよね。なんらかの処理から無視したいという要求です。ただ状態が変わっただけと言えます。であれば、その状態を素直にデータとして表現してやればいいわけです。

ドメインを正しく理解するべきでしょう。