Saya pikir ringkasan singkat mengapa nol tidak diinginkan adalah bahwa keadaan yang tidak berarti tidak dapat diwakili .
Misalkan saya sedang membuat model sebuah pintu. Itu bisa di salah satu dari tiga negara: buka, tutup tetapi tidak terkunci, dan tutup dan dikunci. Sekarang saya bisa memodelkannya di sepanjang garis
class Door
private bool isShut
private bool isLocked
dan jelas bagaimana memetakan ketiga status saya ke dalam dua variabel boolean ini. Tapi daun ini keempat, negara yang tidak diinginkan tersedia: isShut==false && isLocked==true
. Karena tipe yang saya pilih sebagai representasi saya mengakui keadaan ini, saya harus mengeluarkan upaya mental untuk memastikan bahwa kelas tidak pernah masuk ke keadaan ini (mungkin dengan secara eksplisit mengkode invarian). Sebaliknya, jika saya menggunakan bahasa dengan tipe data aljabar atau memeriksa enumerasi yang memungkinkan saya mendefinisikan
type DoorState =
| Open | ShutAndUnlocked | ShutAndLocked
maka saya bisa mendefinisikan
class Door
private DoorState state
dan tidak ada lagi kekhawatiran. Sistem tipe akan memastikan bahwa hanya ada tiga kemungkinan status untuk instance class Door
. Inilah tipe sistem yang baik - secara eksplisit mengesampingkan seluruh kelas kesalahan pada waktu kompilasi.
Masalahnya null
adalah bahwa setiap jenis referensi mendapatkan keadaan ekstra ini di ruangnya yang biasanya tidak diinginkan. Sebuah string
variabel bisa menjadi salah urutan karakter, atau bisa juga tambahan ini gila null
nilai yang tidak memetakan ke domain masalah saya. Sebuah Triangle
objek memiliki tiga Point
s, yang sendiri memiliki X
dan Y
nilai-nilai, tapi sayangnya Point
s atau Triangle
sendiri mungkin menjadi ini nilai null gila yang berarti untuk domain grafik saya bekerja di. Dll
Ketika Anda bermaksud memodelkan nilai yang mungkin tidak ada, maka Anda harus memilihnya secara eksplisit. Jika cara saya berniat memodelkan orang adalah setiap orang Person
memiliki a FirstName
dan a LastName
, tetapi hanya beberapa orang yang memiliki MiddleName
, maka saya ingin mengatakan
class Person
private string FirstName
private Option<string> MiddleName
private string LastName
di mana di string
sini diasumsikan sebagai tipe yang tidak dapat dibatalkan. Maka tidak ada invarian rumit untuk ditetapkan dan tidak ada yang tak terduga NullReferenceException
ketika mencoba untuk menghitung panjang nama seseorang. Sistem tipe memastikan bahwa kode apa pun yang berhubungan dengan MiddleName
akun untuk kemungkinan itu None
, sedangkan kode apa pun yang berurusan dengan FirstName
dapat dengan aman menganggap ada nilai di sana.
Jadi misalnya, menggunakan tipe di atas, kita bisa membuat fungsi konyol ini:
let TotalNumCharsInPersonsName(p:Person) =
let middleLen = match p.MiddleName with
| None -> 0
| Some(s) -> s.Length
p.FirstName.Length + middleLen + p.LastName.Length
tanpa khawatir. Sebaliknya, dalam bahasa dengan referensi nullable untuk tipe seperti string, lalu anggap
class Person
private string FirstName
private string MiddleName
private string LastName
Anda akhirnya mengarang hal-hal seperti
let TotalNumCharsInPersonsName(p:Person) =
p.FirstName.Length + p.MiddleName.Length + p.LastName.Length
yang meledak jika objek Orang yang masuk tidak memiliki invarian dari segala sesuatu yang bukan nol, atau
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)
atau mungkin
let TotalNumCharsInPersonsName(p:Person) =
p.FirstName.Length
+ (if p.MiddleName=null then 0 else p.MiddleName.Length)
+ p.LastName.Length
dengan asumsi bahwa p
memastikan pertama / terakhir ada tetapi tengah bisa nol, atau mungkin Anda melakukan pengecekan yang membuang berbagai jenis pengecualian, atau siapa yang tahu apa. Semua pilihan implementasi gila dan hal-hal untuk dipikirkan ini muncul karena ada nilai representatif bodoh yang tidak Anda inginkan atau butuhkan.
Null biasanya menambah kompleksitas yang tidak perlu. Kompleksitas adalah musuh dari semua perangkat lunak, dan Anda harus berusaha mengurangi kompleksitas kapan pun wajar.
(Catat juga bahwa ada lebih banyak kompleksitas pada contoh-contoh sederhana ini. Bahkan jika FirstName
tidak bisa null
, sebuah string
dapat mewakili ""
(string kosong), yang mungkin juga bukan nama orang yang ingin kita modelkan. Dengan demikian, bahkan dengan non- string nullable, mungkin masih menjadi kasus bahwa kami "mewakili nilai yang tidak berarti". Sekali lagi, Anda dapat memilih untuk melawan ini baik melalui invarian dan kode kondisional pada saat runtime, atau dengan menggunakan sistem tipe (misalnya untuk memiliki NonEmptyString
tipe). yang terakhir mungkin keliru (tipe "baik" sering "ditutup" pada serangkaian operasi umum, dan misalnya NonEmptyString
tidak ditutup.SubString(0,0)
), tetapi menunjukkan lebih banyak poin di ruang desain. Pada akhirnya, dalam sistem tipe apa pun, ada beberapa kompleksitas yang akan sangat baik untuk dihilangkan, dan kompleksitas lain yang secara intrinsik lebih sulit untuk dihilangkan. Kunci untuk topik ini adalah bahwa di hampir setiap sistem tipe, perubahan dari "referensi nullable secara default" ke "referensi non-nullable secara default" hampir selalu merupakan perubahan sederhana yang membuat sistem tipe jauh lebih baik dalam mengatasi kompleksitas dan mengesampingkan jenis kesalahan tertentu dan keadaan tidak berarti. Jadi sangat gila bahwa begitu banyak bahasa terus mengulangi kesalahan ini berulang kali.)