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
自身が持っているの、X
とY
の値を、残念ながらPoint
SまたはTriangle
自体は私が働いているグラフのドメインに意味があり、このクレイジーなNULL値であるかもしれない。等
存在しない可能性のある値をモデル化する場合は、明示的に選択する必要があります。私が人々をモデル化しようとしている方法が、すべてPerson
がFirstName
とを持っているというものであるがLastName
、一部の人々だけがを持っているMiddleName
という場合、
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
first / lastが確実に存在するが、middleがnullである可能性があると仮定したり、さまざまなタイプの例外をスローするチェックを実行したり、誰が何を知っているかを想定したりします。これらのクレイジーな実装の選択と考えるべきことはすべて、あなたが望まない、または必要としないこの愚かな表現可能な値があるためです。
Nullは通常、不必要な複雑さを追加します。 複雑さはすべてのソフトウェアの敵であり、合理的である場合はいつでも複雑さを軽減するよう努めるべきです。
(これらの単純な例でさえ、より複雑であることに注意してください。a FirstName
がでなくてもnull
、(空の文字列)をstring
表すことができ""
ます。これは、おそらく私たちがモデル化しようとしている人物の名前ではありません。 nullable文字列、それでも「意味のない値を表す」場合があるかもしれません。繰り返しますが、実行時に不変条件と条件付きコードを介して、または型システムを使用して(たとえばNonEmptyString
型を持つために)これと戦うことを選択できます。後者はおそらく不適切なアドバイスです(「良い」タイプは多くの場合、一連の一般的な操作で「クローズ」されます。たとえばNonEmptyString
、.SubString(0,0)
)、しかし、それはデザインスペースのより多くのポイントを示しています。結局のところ、特定の型システムでは、それを取り除くのに非常に優れた複雑性と、本質的に取り除くのが難しい他の複雑さがあります。このトピックの要点は、ほぼすべての型システムで、「デフォルトでnull可能参照」から「デフォルトでnull不可参照」への変更が、ほとんど常に単純な変更であり、タイプシステムが複雑さとの戦いにおいて非常に優れていることです。特定のタイプのエラーと無意味な状態を除外する。したがって、非常に多くの言語がこのエラーを何度も繰り返し続けるのはかなりおかしいです。)