Bence null'un istenmeyen olmasının özlü özeti, anlamsız devletlerin temsil edilemez olması gerektiğidir .
Diyelim ki bir kapı modelleniyorum. Üç durumdan birinde olabilir: açık, kapalı ancak kilidi açık ve kapalı ve kilitli. Şimdi onu çizgileri boyunca modelleyebilirim
class Door
private bool isShut
private bool isLocked
ve üç durumumu bu iki boole değişkenine nasıl eşleyeceğim açıktır. Ama bu yapraklar dördüncü, istenmeyen durum mevcut: isShut==false && isLocked==true
. Temsilim olarak seçtiğim türler bu durumu kabul ettiğinden, sınıfın asla bu duruma girmemesini sağlamak için zihinsel çaba sarf etmeliyim (belki de açıkça bir değişmezi kodlayarak). Buna karşılık, cebirsel veri türlerine sahip bir dil veya tanımlamama izin veren işaretli numaralandırmalar kullanıyordum
type DoorState =
| Open | ShutAndUnlocked | ShutAndLocked
o zaman tanımlayabilirim
class Door
private DoorState state
ve daha fazla endişe yok. Tür sistemi, içinde class Door
bulunabileceği bir örnek için yalnızca üç olası durum olmasını sağlayacaktır . Bu tür sistemlerin iyi olduğu şeydir - derleme zamanında tüm hata sınıfını açıkça ortadan kaldırır.
Sorun null
, her referans türünün kendi alanında genellikle istenmeyen bu fazladan durumu almasıdır. Bir string
değişken herhangi bir karakter dizisi olabilir, ya da bu çılgın ekstra olabilir null
benim sorunum etki alanına eşleşmiyor değer. Bir Triangle
nesnenin Point
kendileri X
ve Y
değerleri olan üç s'si vardır , ancak maalesef Point
s veya Triangle
kendisi çalıştığım grafik alanı için anlamsız olan çılgın null değer olabilir.
Muhtemel olmayan bir değeri modellemek istediğinizde, bu değeri açıkça seçmelisiniz. İnsanları modelleme niyetimde herkesin Person
a FirstName
ve a olması LastName
, ancak sadece bazı insanların sahip olması MiddleName
şuysa,
class Person
private string FirstName
private Option<string> MiddleName
private string LastName
burada string
null olmayan bir tür olduğu varsayılır. Sonra NullReferenceException
birisinin adının uzunluğunu hesaplamaya çalışırken kurması zor bir değişmez ve beklenmedik bir şey yoktur . Tür sistemi, MiddleName
hesaplarla ilgili herhangi bir kodun olma olasılığını garanti eder None
, oysa bu kodla ilgili herhangi bir kod, FirstName
orada bir değer olduğunu güvenle varsayabilir.
Örneğin, yukarıdaki türü kullanarak, bu aptalca işlevi yazabiliriz:
let TotalNumCharsInPersonsName(p:Person) =
let middleLen = match p.MiddleName with
| None -> 0
| Some(s) -> s.Length
p.FirstName.Length + middleLen + p.LastName.Length
endişelenmeden. Buna karşılık, dize gibi türler için null olabilecek referansları olan bir dilde,
class Person
private string FirstName
private string MiddleName
private string LastName
sonunda böyle şeyler yazıyorsun
let TotalNumCharsInPersonsName(p:Person) =
p.FirstName.Length + p.MiddleName.Length + p.LastName.Length
gelen Person nesnesinin her şeyin null olmayan değişkeni yoksa patlar veya
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)
ya da belki
let TotalNumCharsInPersonsName(p:Person) =
p.FirstName.Length
+ (if p.MiddleName=null then 0 else p.MiddleName.Length)
+ p.LastName.Length
p
ilk / sonun orada olduğunu ancak ortada boş olabileceğini varsayarsak , ya da farklı türden istisnalar atan ya da kimin ne olduğunu bilen kontroller yaparsınız. Tüm bu çılgın uygulama seçenekleri ve ürün hakkında düşünecek şeyler çünkü istemediğiniz veya ihtiyacınız olmayan bu aptal temsili-değer var.
Null tipik olarak gereksiz karmaşıklık ekler. Karmaşıklık tüm yazılımların düşmanıdır ve makul olduğunda karmaşıklığı azaltmaya çalışmalısınız.
(Not bile bu basit örneklerden daha karmaşık olduğunu iyi ki. Bir Bile FirstName
olamaz null
, bir string
temsil edebilir ""
da muhtemelen biz modele niyetinde olduklarını bir kişinin adı değil (boş dize). Bunun gibi, hatta olmayan ile nullable string'lerde yine de "anlamsız değerleri temsil ediyor" olmamız söz konusu olabilir. Yine, bununla ya çalışma zamanında değişmezler ve koşullu kod aracılığıyla ya da tür sistemini kullanarak (örneğin bir NonEmptyString
türe sahip olmak ) savaşmayı seçebilirsiniz . ikincisi ( "iyi" tipleri ortak operasyonlar kümesi üzerinde genellikle "kapalı", ve mesela belki sakıncalı olduğu NonEmptyString
üzerinde kapalı değil.SubString(0,0)
), ancak tasarım alanında daha fazla nokta gösterir. Günün sonunda, herhangi bir tip sistemde, kurtulmak için çok iyi olacak bazı karmaşıklıklar ve kurtulmak için kendinden daha zor olan diğer karmaşıklıklar vardır. Bu konunun anahtarı, hemen hemen her tür sistemde, "varsayılan olarak boş bırakılabilir başvurular" dan "varsayılan olarak boş bırakılamayan referanslara" geçişin, neredeyse her zaman, yazı sistemini karmaşıklıkla mücadele konusunda daha iyi hale getiren basit bir değişiklik olması ve belirli hata türlerini ve anlamsız durumları ortadan kaldırmak. Bu yüzden birçok dilin bu hatayı tekrar tekrar tekrarlaması oldukça çılgınca.)