null が望ましくない理由の簡潔な要約は、意味のない状態は表現可能であってはならないということだと思います。
ドアをモデリングしているとします。開いている、閉じているがロックされていない、閉じてロックされているの 3 つの状態のいずれかになります。これで、次のようにモデル化できます
class Door
private bool isShut
private bool isLocked
3 つの状態をこれら 2 つのブール変数にマッピングする方法は明らかです。しかし、これにより、4 番目の望ましくない状態が利用可能になりますisShut==false && isLocked==true
。表現として選択した型はこの状態を許容するため、クラスがこの状態にならないように (おそらく不変条件を明示的にコーディングして) 精神的な努力を払わなければなりません。対照的に、定義できる代数データ型またはチェックされた列挙型の言語を使用していた場合、
type DoorState =
| Open | ShutAndUnlocked | ShutAndLocked
それから私は定義することができました
class Door
private DoorState state
そしてもう心配はありません。型システムは、 のインスタンスが存在する可能性のある状態が 3 つだけであることを保証しますclass Door
。これが型システムの得意とするところです。コンパイル時にクラス全体のエラーを明示的に排除します。
問題null
は、すべての参照型が、通常は望ましくない空間でこの余分な状態を取得することです。変数は、任意のstring
文字列である可能性があります。またはnull
、問題のドメインにマップされないこの狂った余分な値である可能性があります。Triangle
オブジェクトには 3 つのPoint
s があり、それ自体にX
とY
値がありますが、残念ながら、Point
s またはTriangle
それ自体は、私が取り組んでいるグラフ作成ドメインにとって意味のないこのクレイジーな null 値である可能性があります。
存在しない可能性のある値をモデル化する場合は、明示的にオプトインする必要があります。私が人々をモデル化しようとしている方法が、すべての人Person
が aFirstName
と aLastName
を持っているが、一部の人だけがMiddleName
s を持っているということである場合、次のように言いたいと思います。
class Person
private string FirstName
private Option<string> MiddleName
private string LastName
ここstring
で、null 非許容型であると想定されます。NullReferenceException
次に、誰かの名前の長さを計算しようとするときに、確立するのが難しい不変条件や予期しないs はありません。型システムは、 を扱うすべてのコードMiddleName
が である可能性を説明することを保証しますがNone
、 を扱うすべてのコードFirstName
はそこに値があると安全に想定できます。
たとえば、上記の型を使用して、このばかげた関数を作成できます。
let TotalNumCharsInPersonsName(p:Person) =
let middleLen = match p.MiddleName with
| None -> 0
| Some(s) -> s.Length
p.FirstName.Length + middleLen + p.LastName.Length
心配なく。対照的に、文字列のような型の null 許容参照を持つ言語では、
class Person
private string FirstName
private string MiddleName
private string LastName
あなたは次のようなものをオーサリングすることになります
let TotalNumCharsInPersonsName(p:Person) =
p.FirstName.Length + p.MiddleName.Length + p.LastName.Length
入ってくる Person オブジェクトが、すべてが非 null であるという不変式を持っていない場合、これは爆発します。または
let TotalNumCharsInPersonsName(p:Person) =
(if p.FirstName=null then 0 else p.FirstName.Length)
+ (if p.MiddleName=null then 0 else p.MiddleName.Length)
+ (if p.LastName=null then 0 else p.LastName.Length)
または多分
let TotalNumCharsInPersonsName(p:Person) =
p.FirstName.Length
+ (if p.MiddleName=null then 0 else p.MiddleName.Length)
+ p.LastName.Length
最初/最後が存在することを保証すると仮定しp
ますが、中間はnullになる可能性があります。または、さまざまな種類の例外をスローするチェックを行うか、誰が何を知っているかを確認します。これらのクレイジーな実装の選択と考えるべきことはすべて、あなたが望まない、または必要としないこのばかげた表現可能な値があるためです。
Null は通常、不必要な複雑さを追加します。 複雑さはすべてのソフトウェアの敵であり、合理的な範囲で複雑さを軽減するよう努めるべきです。
(これらの単純な例でさえ、より複雑であることに注意してください。たとえ aFirstName
が でなくてもnull
、aは (空の文字列)string
を表すことができます。""
これはおそらく、モデル化しようとしている人の名前でもありません。 null 許容文字列の場合でも、「意味のない値を表現している」場合があります. 繰り返しますが、実行時に不変条件と条件付きコードを使用するか、型システムを使用して (たとえば、NonEmptyString
型を持つ)、これと戦うことを選択できます。後者はおそらく賢明ではありません (「良い」型は、一連の一般的な操作に対して「閉じられている」ことが多く、たとえば、NonEmptyString
閉じられていません.SubString(0,0)
)、しかし、それは計画空間でより多くの点を示しています。結局のところ、特定の型システムには、取り除くのが非常に得意な複雑さと、本質的に取り除くのが難しい他の複雑さがあります。このトピックの重要な点は、ほぼすべての型システムで、「デフォルトで null 許容参照」から「デフォルトで null 非許容参照」への変更は、ほとんどの場合単純な変更であり、複雑さとの戦いにおいて型システムを大幅に改善するということです。特定の種類のエラーや無意味な状態を除外します。したがって、非常に多くの言語がこのエラーを何度も繰り返し続けるのは非常にクレイジーです。)