目次

概要

(書き掛け)

null って割かし、

  • (null 伝搬)引数に null を受け付けて、(もし null だったり、関数内でエラーがあったら)戻り値に null を返す

  • (非 null)null 自体受け付けない。引数に null が来たら ArgumentNullException

の2択だったりする。 割かし両極端で、null を使うときはとことん伝搬するし、使わないときは最初から一切使わないようなことが多い。

前者用に、

Ver. 6
null 伝搬用(null propagation)の演算子 ?. が追加される。 最後どこかで ?? 演算子を使って規定値を与える想定。

var x = points?.FirstOrDefault()?.X ?? -1;

非 null な例

例えばの話、ゲームのユニット。 ユニット詳細画面とか考えると、持っていないユニットとか、ゲーム中に存在しえないユニットとかの「ユニット詳細」を見ることはあり得ない。 あり得るとしたら、データが壊れてるとかいう状況なので、リリース前のテストが不十分っていう話。

元データも非 null だし

SelectedUnitId = 1001;

表示側の API も非 null 前提。

void ShowUnitDetail(int unitId)
{
    …
}

void ShowUnitDetail(Unit unit)
{
    if (unit == null)
        new ArgumentNullException();
    …
}

null はあっちゃいけない。 テストでしっかりエラーをなくす。 (もしものデータ不整合に備えて、プレイヤーに見られてもそこそこ問題なさげな最低限の体裁だけ整えて、エラー箇所をサーバーに送るなりして原因特定とかが無難。)

null 伝搬な例

一方でチーム編成みたいなのだと。 スロット(チーム中のユニットの配置場所)の並びとかに意味があったりする場合、空きにしておきたいスロットが出たりする。 「空きスロットには null を入れておく」みたいな設計はよくやるし、 そうなると、表示側も「null が来たらユニット アイコンの代わりに[追加]ボタンを表示する」みたいな仕様がよかったりする。

この場合、元データにも null が入ってたり入ってなかったり

Team = new Team
{
    { 1001, { 2001, 3001 } },
    { 2052, { null, 2231 } },
    null,
    { 3123, null },
};

表示側の API も null が来る前提。

void ShowUnitThumbnail(int? unitId)
{
    …
}

void ShowUnitThumbnail(Unit unit)
{
    if (unit == null)
        // [追加]アイコンを表示
    else
       // ユニットのアイコンを表示
}

null が伝搬する。 ユニット ID が null だったら null の Unit を返す、 Unit が null だったら null の画像 ID を返す、 画像 ID が null だったらユニット画像の代わりに[追加]ボタンを表示する…

非 null のパターン

そもそも .NET 自体に「null は認めない型」仕様を入れておくべきだったかもしれない(.NET 設計者の最大級の後悔のうちの1つ)。 値型には後から nullable 型が追加されたけども、参照型にも nullable/non-nullable があるべきだった。 それは今からの変更だと影響がでかすぎて無理らしいんだけども… 代わりといっちゃなんだけども、code contract の話。 if (arg == null) throw new ArgumentNullException(... だと何が問題なのかの話も書く。

何がネックになって非 null にするのが難しいか

Code Contract の紹介する?

null 伝搬のパターン

だいたいは、if (id == null) return null; ... みたいなコードだらけになってた。

Ver. 6

null 伝搬パターン用に、null 条件演算子(null conditional operator) ?. が追加される。

int? id = Team.Slots?[i]?.MainId;
var imageId = unitRepository.GetOrDefault(id)?.Master?.ImageId;
ShowUnitThumbnail(imageId);

最後どこかで ?? 演算子を使って規定値を与えたりすることも多い。

const int AddButtonImageId = 0x80000001;

var unitOrButtonImageId = imageId ?? AddButtonImageId;

補足: .NET と非 null 型

C#では、値型の場合は、Nullable型を使うことで非null系かnull伝播系かが分かります。しかし、参照型の場合にこれを区別する方法がありません。

(2×2マトリックスな図を書く。値型は非null、null伝搬できるのに、参照型では非nullを明示できない)

この、参照型ではnull伝播系の処理なのか、非null系の処理なのかが判別出来ないというのは、多くのプログラミング言語が同様ですが、たびたび問題にされます。 もちろん、メソッドの中身まで追うとか、ドキュメントで補うとかいった「緩和策」は取れます。しかし、往々にして、メソッドの中身やドキュメントまで読み込むのは面倒で、結局、どちらなのか分からないまま使われます。

C#でも、この問題は長らく批判に晒されています。C#開発チームも問題視はしています。ただ、後からの機能追加で「非nullの参照型」を足すのは、既存コードを壊してしまうような変更を起こす可能性が高く、追加に尻込みしています。

更新履歴

ブログ