Saya pernah mendengar bahwa dimasukkannya referensi nol dalam bahasa pemrograman adalah "kesalahan miliar dolar". Tapi kenapa? Tentu, mereka dapat menyebabkan NullReferenceExceptions, tetapi jadi apa? Elemen bahasa apa pun bisa menjadi sumber kesalahan jika digunakan dengan tidak benar.
Dan apa alternatifnya? Saya kira bukannya mengatakan ini:
Customer c = Customer.GetByLastName("Goodman"); // returns null if not found
if (c != null)
{
Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman. How lame!"); }
Anda bisa mengatakan ini:
if (Customer.ExistsWithLastName("Goodman"))
{
Customer c = Customer.GetByLastName("Goodman") // throws error if not found
Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman. How lame!"); }
Tapi bagaimana itu lebih baik? Either way, jika Anda lupa untuk memeriksa bahwa pelanggan itu ada, Anda mendapatkan pengecualian.
Saya kira bahwa CustomerNotFoundException sedikit lebih mudah untuk di-debug daripada NullReferenceException berdasarkan menjadi lebih deskriptif. Apakah hanya itu yang ada untuk itu?
language-design
exceptions
pointers
null
Tim Goodman
sumber
sumber
Jawaban:
null itu jahat
Ada presentasi tentang InfoQ tentang topik ini: Null Referensi: Kesalahan Billion Dollar oleh Tony Hoare
Jenis opsi
Alternatif dari pemrograman fungsional menggunakan jenis Opsi , yang dapat berisi
SOME value
atauNONE
.Artikel yang bagus Pola “Opsi” yang membahas jenis Opsi dan menyediakan implementasi untuk Java.
Saya juga telah menemukan laporan bug untuk Java tentang masalah ini: Tambahkan jenis Opsi Nice ke Jawa untuk mencegah NullPointerExceptions . Fitur yang diminta diperkenalkan di Java 8.
sumber
String
tipe tidak benar-benar string. ItuString or Null
tipe. Bahasa yang sepenuhnya mematuhi gagasan jenis opsi tidak mengizinkan nilai nol dalam sistem jenisnya, memberikan jaminan bahwa jika Anda menetapkan jenis tanda tangan yang memerlukanString
(atau apa pun), itu harus memiliki nilai. TheOption
tipe yang ada untuk memberikan representasi tipe-tingkat hal-hal yang mungkin atau mungkin tidak memiliki nilai, sehingga Anda tahu di mana Anda harus secara eksplisit menangani situasi tersebut.Masalahnya adalah bahwa karena secara teori objek apa pun bisa menjadi nol dan melemparkan pengecualian ketika Anda mencoba menggunakannya, kode berorientasi objek Anda pada dasarnya adalah kumpulan bom yang tidak meledak.
Anda benar bahwa penanganan kesalahan yang baik dapat secara fungsional identik dengan
if
pernyataan pemeriksaan-nol . Tetapi apa yang terjadi ketika sesuatu yang Anda yakini tidak mungkin menjadi nol sebenarnya adalah nol? Kerboom. Apa pun yang terjadi selanjutnya, saya berani bertaruh bahwa 1) itu tidak akan anggun dan 2) Anda tidak akan menyukainya.Dan jangan mengabaikan nilai "mudah debug." Kode produksi yang matang adalah makhluk yang gila dan tersebar luas; apa pun yang memberi Anda lebih banyak wawasan tentang apa yang salah dan di mana mungkin menghemat waktu menggali.
sumber
public Foo doStuff()
tidak berarti "melakukan hal-hal dan mengembalikan hasil Foo" itu berarti "melakukan hal-hal dan mengembalikan hasil Foo, ATAU mengembalikan nol, tetapi Anda tidak tahu apakah itu akan terjadi atau tidak." Hasilnya adalah bahwa Anda harus memeriksa nol SETIAP MANA untuk memastikan. Mungkin juga menghapus nulls dan menggunakan tipe atau pola khusus (seperti tipe nilai) untuk menunjukkan nilai yang tidak berarti.Ada beberapa masalah dengan menggunakan referensi nol dalam kode.
Pertama, ini biasanya digunakan untuk menunjukkan keadaan khusus . Alih-alih mendefinisikan kelas baru atau konstanta untuk setiap negara sebagai spesialisasi biasanya dilakukan, menggunakan referensi nol menggunakan tipe / nilai lossy, yang digeneralisasi secara besar-besaran .
Kedua, kode debug menjadi lebih sulit ketika referensi nol muncul dan Anda mencoba untuk menentukan apa yang menghasilkannya , negara mana yang berlaku dan penyebabnya bahkan jika Anda dapat melacak jalur eksekusi hulu.
Ketiga, referensi nol memperkenalkan jalur kode tambahan untuk diuji .
Keempat, sekali referensi nol digunakan sebagai status yang valid untuk parameter serta nilai pengembalian, pemrograman defensif (untuk kondisi yang disebabkan oleh desain) memerlukan lebih banyak pemeriksaan referensi nol yang harus dilakukan di berbagai tempat ... untuk berjaga-jaga.
Kelima, runtime bahasa sudah melakukan pemeriksaan tipe ketika melakukan pencarian pemilih pada tabel metode objek. Jadi, Anda menduplikasi upaya dengan memeriksa apakah jenis objek valid / tidak valid dan kemudian meminta runtime memeriksa jenis objek yang valid untuk memanggil metode tersebut.
Mengapa tidak menggunakan pola NullObject untuk mengambil keuntungan dari pemeriksaan runtime untuk memintanya menggunakan metode NOP khusus untuk keadaan itu (sesuai dengan antarmuka keadaan biasa) sementara juga menghilangkan semua pemeriksaan tambahan untuk referensi nol di seluruh basis kode Anda?
Ini melibatkan lebih banyak pekerjaan dengan membuat kelas NullObject untuk setiap antarmuka yang Anda inginkan untuk mewakili keadaan khusus. Tetapi setidaknya spesialisasi diisolasi untuk setiap negara khusus, bukan kode di mana negara mungkin hadir. TKI, jumlah tes berkurang karena Anda memiliki lebih sedikit jalur eksekusi alternatif dalam metode Anda.
sumber
[self.navigationController.presentingViewController dismissViewControllerAnimated: YES completion: nil];
runtime memperlakukannya sebagai urutan no-ops, tampilan tidak dihapus dari tampilan, dan Anda mungkin duduk di depan Xcode menggaruk-garuk kepala selama berjam-jam mencoba mencari tahu mengapa.Nulls tidak terlalu buruk, kecuali jika Anda tidak mengharapkannya. Anda harus menentukan secara eksplisit dalam kode yang Anda harapkan nol, yang merupakan masalah desain bahasa. Pertimbangkan ini:
Jika Anda mencoba memanggil metode pada
Customer?
, Anda akan mendapatkan kesalahan waktu kompilasi. Alasan mengapa lebih banyak bahasa tidak melakukan ini (IMO) adalah karena mereka tidak menyediakan jenis variabel untuk berubah tergantung pada cakupannya. Jika bahasa dapat mengatasinya, maka masalahnya dapat diselesaikan sepenuhnya di dalam jenis sistem.Ada juga cara fungsional untuk menangani masalah ini, menggunakan tipe-opsi dan
Maybe
, tapi saya tidak terbiasa dengannya. Saya lebih suka cara ini karena kode yang benar secara teoritis hanya perlu satu karakter ditambahkan untuk dikompilasi dengan benar.sumber
GetByLastName
mengembalikan null jika tidak ditemukan (sebagai lawan melemparkan pengecualian) - Saya hanya dapat melihat apakah ia kembaliCustomer?
atauCustomer
.Maybe
- AMaybe Customer
adalahJust c
(di manac
aCustomer
) atauNothing
. Dalam bahasa lain, namanyaOption
- lihat beberapa jawaban lain pada pertanyaan ini.Customer.FirstName
bertipeString
(berlawanan denganString?
). Itulah manfaat nyata - hanya variabel / properti yang tidak memiliki nilai apa - apa yang bermakna yang harus dinyatakan sebagai nol.Ada banyak jawaban bagus yang mencakup gejala malang
null
, jadi saya ingin menyajikan argumen alternatif: Null adalah cacat dalam sistem tipe.Tujuan dari sistem tipe adalah untuk memastikan bahwa komponen yang berbeda dari suatu program "cocok bersama" dengan benar; program yang diketik dengan baik tidak bisa "keluar dari rel" ke dalam perilaku yang tidak ditentukan.
Pertimbangkan dialek hipotetis Jawa, atau apa pun bahasa yang diketik secara statis, di mana Anda dapat menetapkan string
"Hello, world!"
ke variabel apa pun dari jenis apa pun:Dan Anda dapat memeriksa variabel seperti:
Tidak ada yang mustahil tentang ini - seseorang dapat merancang bahasa seperti itu jika mereka mau. Nilai khusus tidak perlu
"Hello, world!"
- itu bisa saja nomor 42, tuple(1, 4, 9)
, atau, katakanlahnull
,. Tetapi mengapa Anda melakukan ini? Variabel tipeFoo
hanya boleh menampungFoo
s - itulah inti dari sistem tipe!null
tidakFoo
lebih dari"Hello, world!"
itu. Lebih buruk lagi,null
ini bukan nilai dari jenis apa pun , dan tidak ada yang bisa Anda lakukan dengannya!Pemrogram tidak pernah dapat memastikan bahwa suatu variabel benar-benar memiliki
Foo
, dan begitu pula program; untuk menghindari perilaku tidak terdefinisi, ia harus memeriksa variabel"Hello, world!"
sebelum menggunakannya sebagaiFoo
s. Perhatikan bahwa melakukan pemeriksaan string pada cuplikan sebelumnya tidak menyebarkan fakta bahwa foo1 benar-benarFoo
-bar
kemungkinan akan memiliki pemeriksaan sendiri juga, hanya untuk aman.Bandingkan dengan menggunakan a
Maybe
/Option
type dengan pencocokan pola:Di dalam
Just foo
klausa, Anda dan program tahu pasti bahwaMaybe Foo
variabel kami benar-benar mengandungFoo
nilai - bahwa informasi disebarkan ke rantai panggilan, danbar
tidak perlu melakukan pemeriksaan apa pun. KarenaMaybe Foo
ini adalah tipe yang berbeda dariFoo
, Anda dipaksa untuk menangani kemungkinan yang dapat dikandungnyaNothing
, sehingga Anda tidak akan pernah dapat secara buta oleh aNullPointerException
. Anda dapat beralasan tentang program Anda lebih mudah dan kompiler dapat menghilangkan pemeriksaan nol karena mengetahui bahwa semua variabel tipeFoo
benar-benar berisiFoo
s. Semua orang menang.sumber
my Int $variable = Int
, di manaInt
nilai jenis nullInt
?this type only contains integers
sebagai lawanthis type contains integers and this other value
, Anda akan memiliki masalah setiap kali Anda hanya ingin bilangan bulat.??
,?.
, dan sebagainya) untuk hal-hal yang bahasa-bahasa seperti Haskell melaksanakan fungsi sebagai perpustakaan. Saya pikir itu jauh lebih rapi.null
/Nothing
propagasi sama sekali bukan "istimewa", jadi mengapa kita harus belajar lebih banyak sintaks untuk memilikinya?(Melemparkan topiku di atas cincin untuk pertanyaan lama;))
Masalah khusus dengan null adalah kerusakan pengetikan statis.
Jika saya punya
Thing t
, maka kompiler dapat menjamin saya bisa menelepont.doSomething()
. Nah, KECUALIt
adalah nol saat runtime. Sekarang semua taruhan dibatalkan. Kompiler mengatakan tidak apa-apa, tapi saya tahu kemudiant
ternyata TIDAKdoSomething()
. Jadi daripada bisa mempercayai kompiler untuk menangkap kesalahan tipe, saya harus menunggu sampai runtime untuk menangkapnya. Saya mungkin juga hanya menggunakan Python!Jadi, dalam arti tertentu, null memperkenalkan pengetikan dinamis ke dalam sistem yang diketik secara statis, dengan hasil yang diharapkan.
Perbedaan antara yang membagi dengan nol atau log negatif, dll adalah bahwa ketika saya = 0, itu masih sebuah int. Kompiler masih dapat menjamin tipenya. Masalahnya adalah bahwa logika salah menerapkan nilai itu dengan cara yang tidak diizinkan oleh logika ... tetapi jika logika melakukan itu, itu cukup banyak definisi bug.
(Kompilator dapat menangkap beberapa masalah tersebut, BTW. Suka menandai ekspresi seperti
i = 1 / 0
pada waktu kompilasi. Tetapi Anda tidak dapat benar-benar mengharapkan kompiler mengikuti fungsi dan memastikan bahwa semua parameter konsisten dengan logika fungsi)Masalah praktisnya adalah Anda melakukan banyak pekerjaan ekstra, dan menambahkan cek nol untuk melindungi diri Anda saat runtime, tetapi bagaimana jika Anda lupa satu? Kompiler menghentikan Anda dari menetapkan:
Jadi mengapa harus memungkinkan penugasan ke nilai (nol) yang akan mematahkan de-referensi
s
?Dengan mencampur "tidak ada nilai" dengan referensi "diketik" dalam kode Anda, Anda memberi beban tambahan pada programmer. Dan ketika NullPointerExceptions terjadi, melacaknya dapat menghabiskan lebih banyak waktu. Daripada mengandalkan pengetikan statis untuk mengatakan "ini adalah referensi ke sesuatu yang diharapkan," Anda membiarkan bahasa mengatakan "ini mungkin menjadi referensi untuk sesuatu yang diharapkan."
sumber
*int
mengidentifikasiint
, atauNULL
. Demikian juga dalam C ++, variabel tipe*Widget
mengidentifikasi aWidget
, suatu hal yang berasal dari tipeWidget
, atauNULL
. Masalah dengan Java dan turunannya seperti C # adalah bahwa mereka menggunakan lokasi penyimpananWidget
artinya*Widget
. Solusinya bukan berpura-pura bahwa*Widget
tidak seharusnya bisa tahanNULL
, melainkan untuk memilikiWidget
jenis lokasi penyimpanan yang tepat .Sebuah
null
pointer adalah alatbukan musuh. Anda hanya harus menggunakan mereka dengan benar.
Pikirkan hanya beberapa menit tentang berapa lama waktu yang dibutuhkan untuk menemukan dan memperbaiki bug pointer tidak valid yang khas , dibandingkan dengan mencari dan memperbaiki bug pointer nol. Mudah untuk memeriksa pointer
null
. Ini adalah PITA untuk memverifikasi apakah pointer tidak nol menunjuk ke data yang valid.Jika masih belum yakin, tambahkan dosis multithreading yang baik untuk skenario Anda, lalu pikirkan lagi.
Moral dari cerita ini: Jangan melempar anak-anak dengan air. Dan jangan salahkan alat untuk kecelakaan itu. Orang yang menemukan angka nol sejak dulu cukup pintar, karena hari itu Anda bisa menyebut "tidak ada". The
null
pointer tidak begitu jauh dari itu.EDIT: Meskipun
NullObject
pola tampaknya menjadi solusi yang lebih baik daripada referensi yang mungkin nol, itu memperkenalkan masalah sendiri:Referensi yang memegang suatu
NullObject
keharusan (menurut teori) tidak melakukan apa-apa ketika suatu metode dipanggil. Dengan demikian, kesalahan halus dapat diperkenalkan karena referensi yang tidak ditetapkan, yang sekarang dijamin tidak nol (yehaa!) Tetapi melakukan tindakan yang tidak diinginkan: tidak ada. Dengan NPE itu jelas di mana masalahnya terletak. DenganNullObject
perilaku yang entah bagaimana (tapi salah), kami memperkenalkan risiko kesalahan yang terdeteksi (terlalu) terlambat. Ini bukan kebetulan mirip dengan masalah pointer yang tidak valid dan memiliki konsekuensi yang serupa: Referensi menunjuk ke sesuatu, yang tampak seperti data yang valid, tetapi tidak, memperkenalkan kesalahan data dan mungkin sulit dilacak. Sejujurnya, dalam kasus ini saya akan tanpa berkedip lebih suka NPE yang gagal segera, sekarang,lebih dari kesalahan logis / data yang tiba-tiba mengejutkan saya di kemudian hari. Ingat studi IBM tentang biaya kesalahan menjadi fungsi pada tahap mana mereka terdeteksi?Gagasan melakukan apa-apa ketika suatu metode dipanggil pada
NullObject
tidak berlaku, ketika pengambil properti atau fungsi dipanggil, atau ketika metode mengembalikan nilai melaluiout
. Katakanlah, nilai baliknya adalahint
dan kami memutuskan, bahwa "tidak melakukan apa-apa" berarti "mengembalikan 0". Tetapi bagaimana kita bisa yakin, bahwa 0 adalah "tidak ada" yang tepat untuk dikembalikan? Bagaimanapun,NullObject
seharusnya tidak melakukan apa-apa, tetapi ketika ditanya nilai, itu harus bereaksi entah bagaimana. Gagal bukan pilihan: Kami menggunakanNullObject
untuk mencegah NPE, dan kami pasti tidak akan memperdagangkannya dengan pengecualian lain (tidak diterapkan, bagi dengan nol, ...), bukan? Jadi bagaimana Anda benar tidak mengembalikan apa pun ketika Anda harus mengembalikan sesuatu?Saya tidak bisa membantu, tetapi ketika seseorang mencoba menerapkan
NullObject
pola untuk setiap masalah, sepertinya mencoba memperbaiki satu kesalahan dengan melakukan yang lain. Ini tanpa banyak keraguan solusi yang baik dan berguna dalam beberapa kasus, tapi itu pasti bukan peluru ajaib untuk semua kasus.sumber
null
harus ada? Sangat mungkin untuk melakukan handwave tentang kekurangan bahasa apa pun dengan "ini bukan masalah, tapi jangan membuat kesalahan".Maybe T
yang mungkin mengandungNothing
, atauT
nilai. Kuncinya di sini adalah bahwa Anda dipaksa untuk memeriksa apakahMaybe
benar - benar berisiT
sebelum Anda diizinkan menggunakannya, dan memberikan tindakan jika seandainya tidak. Dan karena Anda telah menolak pointer tidak valid, sekarang Anda tidak perlu memeriksa apa pun yang bukanMaybe
.t.Something()
? Atau apakah ada cara untuk mengetahui Anda sudah melakukan pemeriksaan, dan tidak membuat Anda melakukannya lagi? Bagaimana jika Anda melakukan pemeriksaan sebelum Anda masukt
ke metode ini? Dengan jenis Opsi, kompiler tahu apakah Anda sudah melakukan pemeriksaan atau belum dengan melihat jenist
. Jika itu sebuah Opsi, Anda belum memeriksanya, sedangkan jika itu adalah nilai yang terbuka maka Anda memilikinya.Referensi kosong adalah kesalahan karena mereka memperbolehkan kode yang tidak masuk akal:
Ada beberapa alternatif, jika Anda memanfaatkan sistem tipe:
Gagasan umumnya adalah untuk meletakkan variabel dalam kotak, dan satu-satunya hal yang dapat Anda lakukan adalah menghapusnya, lebih baik meminta bantuan kompiler seperti yang diusulkan untuk Eiffel.
Haskell memilikinya dari awal (
Maybe
), di C ++ Anda dapat memanfaatkanboost::optional<T>
tetapi Anda masih bisa mendapatkan perilaku yang tidak terdefinisi ...sumber
Maybe
tidak dibangun ke dalam bahasa inti, definisi kebetulan berada di awal standar:data Maybe t = Nothing | Just t
.T
dibagi dengan nilai tipe lainnyaT
, Anda akan membatasi operasi ke nilai tipeT
dibagi dengan nilai tipeNonZero<T>
.Jenis dan pencocokan pola opsional. Karena saya tidak tahu C #, berikut adalah sepotong kode dalam bahasa fiksi yang disebut Scala # :-)
sumber
Masalah dengan nulls adalah bahasa yang memungkinkan mereka memaksa Anda memprogram untuk mempertahankannya. Dibutuhkan banyak upaya (jauh lebih banyak daripada mencoba menggunakan blok if-defensive) untuk memastikannya
Jadi, memang, nol akhirnya menjadi hal yang mahal untuk dimiliki.
sumber
Persoalan sejauh mana bahasa pemrograman Anda mencoba untuk membuktikan kebenaran program Anda sebelum dijalankan. Dalam bahasa yang diketik secara statis, Anda membuktikan bahwa Anda memiliki jenis yang benar. Dengan beralih ke referensi non-nullable default (dengan referensi nullable opsional) Anda dapat menghilangkan banyak kasus di mana null dilewatkan dan tidak seharusnya. Pertanyaannya adalah apakah upaya ekstra dalam menangani referensi yang tidak dapat dibatalkan bernilai manfaat dari segi kebenaran program.
sumber
Masalahnya bukan begitu banyak nol, itu adalah bahwa Anda tidak dapat menentukan jenis referensi non-nol dalam banyak bahasa modern.
Misalnya, kode Anda mungkin terlihat seperti
Ketika referensi kelas dilewatkan, pemrograman defensif mendorong Anda untuk memeriksa semua parameter untuk null lagi, bahkan jika Anda baru saja memeriksanya untuk null tepat sebelumnya, terutama ketika membangun unit yang dapat digunakan kembali yang sedang diuji. Referensi yang sama dapat dengan mudah diperiksa nol 10 kali melintasi basis kode!
Bukankah lebih baik jika Anda dapat memiliki tipe nullable normal ketika Anda mendapatkan barang itu, berurusan dengan null saat itu juga, kemudian meneruskannya sebagai tipe non-nullable ke semua fungsi dan kelas pembantu kecil Anda tanpa memeriksa nol semua waktu?
Itulah inti dari solusi Option - bukan untuk memungkinkan Anda untuk mengaktifkan status kesalahan, tetapi untuk memungkinkan desain fungsi yang secara implisit tidak dapat menerima argumen nol. Apakah implementasi tipe "default" adalah nullable atau non-nullable kurang penting daripada fleksibilitas memiliki kedua alat yang tersedia.
http://twistedoakstudios.com/blog/Post330_non-nullable-types-vs-c-fixing-the-billion-dollar-mistake
Ini juga terdaftar sebagai fitur # 2 yang paling banyak dipilih untuk C # -> https://visualstudio.uservoice.com/forums/121579-visual-studio/category/30931-languages-c
sumber
None
setiap langkah dari jalan.Null itu jahat. Namun, tidak adanya null bisa menjadi kejahatan yang lebih besar.
Masalahnya adalah bahwa di dunia nyata Anda sering memiliki situasi di mana Anda tidak memiliki data. Contoh versi tanpa null masih bisa meledak - Anda membuat kesalahan logika dan lupa memeriksa Goodman atau mungkin Goodman menikah antara saat Anda memeriksa dan ketika Anda melihatnya. (Ini membantu dalam mengevaluasi logika jika Anda pikir Moriarty menonton setiap bit kode Anda dan berusaha membuat Anda tersandung.)
Apa yang dilakukan pencarian Goodman ketika dia tidak ada di sana? Tanpa nol Anda harus mengembalikan semacam pelanggan default - dan sekarang Anda menjual barang ke default itu.
Pada dasarnya, ini tergantung pada apakah lebih penting bahwa kode bekerja tidak peduli apa atau itu berfungsi dengan benar. Jika perilaku yang tidak pantas lebih baik daripada tidak ada perilaku maka Anda tidak ingin nulls. (Sebagai contoh bagaimana ini mungkin pilihan yang tepat, pertimbangkan peluncuran Ariane V. yang pertama. Kesalahan yang tidak tertangkap / 0 menyebabkan roket berbelok dengan keras dan penghancuran diri terjadi ketika terpisah karena hal ini. Nilai tersebut itu mencoba untuk menghitung sebenarnya tidak lagi melayani tujuan apa pun saat booster dinyalakan - itu akan membuat orbit meskipun rutin mengembalikan sampah.)
Setidaknya 99 kali dari 100 saya akan memilih untuk menggunakan nol. Aku akan melompat kegirangan karena memperhatikan versi diri mereka.
sumber
null
adalah satu-satunya (atau bahkan lebih baik) cara pemodelan tidak adanya nilai.Optimalkan untuk kasus yang paling umum.
Harus memeriksa nol selalu membosankan - Anda ingin bisa hanya mendapatkan objek Pelanggan dan bekerja dengannya.
Dalam kasus normal, ini seharusnya bekerja dengan baik - Anda melakukan pencarian, mendapatkan objek dan menggunakannya.
Dalam kasus luar biasa , di mana Anda (secara acak) mencari pelanggan dengan nama, tidak tahu apakah catatan / objek itu ada, Anda perlu beberapa indikasi bahwa ini gagal. Dalam situasi ini, jawabannya adalah melempar pengecualian RecordNotFound (atau biarkan penyedia SQL di bawahnya melakukan ini untuk Anda).
Jika Anda berada dalam situasi di mana Anda tidak tahu apakah Anda bisa mempercayai data yang masuk (parameter), mungkin karena dimasukkan oleh pengguna, maka Anda juga bisa memberikan 'TryGetCustomer (nama, pelanggan keluar)' pola. Referensi silang dengan int.Parse dan int.TryParse.
sumber
Pengecualian tidak eval!
Mereka ada di sana karena suatu alasan. Jika kode Anda berjalan buruk, ada pola jenius, yang disebut pengecualian , dan itu memberi tahu Anda bahwa ada sesuatu yang salah.
Dengan menghindari menggunakan objek nol Anda menyembunyikan bagian dari pengecualian itu. Saya tidak suka tentang contoh OP di mana ia mengkonversi pengecualian pointer kosong ke pengecualian diketik dengan baik, ini mungkin sebenarnya hal yang baik karena meningkatkan keterbacaan. Bagaimana pun ketika Anda mengambil solusi jenis Opsi seperti yang ditunjukkan @Jonas, Anda menyembunyikan pengecualian.
Daripada di aplikasi produksi Anda, bukannya dibuang ketika Anda mengklik tombol untuk memilih opsi kosong, tidak ada yang terjadi begitu saja. Alih-alih pengecualian null pointer akan dibuang dan kami mungkin akan mendapatkan laporan pengecualian itu (seperti dalam banyak aplikasi produksi kami memiliki mekanisme seperti itu), dan daripada kita benar-benar bisa memperbaikinya.
Membuat kode Anda anti-peluru dengan menghindari pengecualian adalah ide yang buruk, membuat Anda kode anti-peluru dengan memperbaiki pengecualian, nah ini jalan yang akan saya pilih.
sumber
None
untuk sesuatu yang bukan Opsi menjadi kesalahan waktu kompilasi, dan juga menggunakanOption
seolah-olah memiliki nilai tanpa terlebih dahulu memeriksa itu adalah kesalahan waktu kompilasi. Kesalahan kompilasi tentu lebih baik daripada pengecualian run-time.s: String = None
, Anda harus mengatakans: Option[String] = None
. Dan jikas
merupakan Opsi, Anda tidak bisa mengatakannyas.length
, namun Anda dapat memeriksa apakah itu memiliki nilai dan mengekstraksi nilai pada saat yang sama, dengan pencocokan pola:s match { case Some(str) => str.length; case None => throw RuntimeException("Where's my value?") }
s: String = null
, tapi itu harga interoperabilitas dengan Java. Kami biasanya melarang hal semacam itu dalam kode Scala kami.Nulls
bermasalah karena harus diperiksa secara eksplisit, namun kompiler tidak dapat memperingatkan Anda bahwa Anda lupa memeriksanya. Hanya analisis statis yang memakan waktu yang dapat memberi tahu Anda hal itu. Untungnya, ada beberapa alternatif bagus.Keluarkan variabel dari jangkauan. Terlalu sering,
null
digunakan sebagai tempat pemegang ketika seorang programmer menyatakan suatu variabel terlalu dini atau membuatnya terlalu lama. Pendekatan terbaik adalah dengan menyingkirkan variabel. Jangan mendeklarasikannya sampai Anda memiliki nilai yang valid untuk dimasukkan ke sana. Batasan ini tidak sesulit yang Anda bayangkan, dan ini membuat kode Anda jauh lebih bersih.Gunakan pola objek nol. Terkadang, nilai yang hilang adalah kondisi yang valid untuk sistem Anda. Pola objek nol adalah solusi yang baik di sini. Ini menghindari perlunya pemeriksaan eksplisit konstan, tetapi masih memungkinkan Anda untuk mewakili keadaan nol. Ini bukan "papering atas kondisi kesalahan," seperti yang diklaim beberapa orang, karena dalam hal ini keadaan nol adalah keadaan semantik yang valid. Yang sedang berkata, Anda tidak harus menggunakan pola ini ketika keadaan nol bukan keadaan semantik yang valid. Anda hanya harus mengambil variabel Anda di luar ruang lingkup.
Gunakan Opsi / Mungkin. Pertama-tama, ini memungkinkan kompiler memperingatkan Anda bahwa Anda perlu memeriksa nilai yang hilang, tetapi itu lebih dari mengganti satu jenis cek eksplisit dengan yang lain. Dengan menggunakan
Options
, Anda dapat mengaitkan kode Anda, dan melanjutkan seolah-olah nilai Anda ada, tidak perlu benar-benar memeriksa sampai menit terakhir yang absolut. Di Scala, kode contoh Anda akan terlihat seperti:Di baris kedua, di mana kami membuat pesan yang luar biasa, kami tidak melakukan pemeriksaan eksplisit jika pelanggan ditemukan, dan keindahannya tidak perlu kami lakukan . Tidak sampai baris terakhir, yang mungkin banyak baris kemudian, atau bahkan dalam modul yang berbeda, di mana kita secara eksplisit memperhatikan diri kita sendiri dengan apa yang terjadi jika
Option
ituNone
. Tidak hanya itu, jika kami lupa melakukannya, itu tidak akan diperiksa jenisnya. Bahkan kemudian, itu dilakukan dengan cara yang sangat alami, tanpaif
pernyataan eksplisit .Bandingkan dengan a
null
, di mana ia mengetik pemeriksaan dengan baik, tetapi di mana Anda harus melakukan pemeriksaan eksplisit pada setiap langkah atau seluruh aplikasi Anda meledak saat runtime, jika Anda beruntung selama kasus penggunaan, unit Anda menguji latihan. Hanya saja tidak sepadan dengan kerumitannya.sumber
Masalah utama NULL adalah bahwa hal itu membuat sistem tidak dapat diandalkan. Pada tahun 1980 Tony Hoare dalam makalah yang didedikasikan untuk Penghargaan Turing-nya menulis:
Bahasa ADA telah banyak berubah sejak itu, namun masalah seperti itu masih ada di Jawa, C # dan banyak bahasa populer lainnya.
Merupakan kewajiban pengembang untuk membuat kontrak antara klien dan pemasok. Misalnya, dalam C #, seperti di Jawa, Anda dapat menggunakan
Generics
untuk meminimalkan dampakNull
referensi dengan membuat readonlyNullableClass<T>
(dua Opsi):dan kemudian menggunakannya sebagai
atau gunakan dua opsi gaya dengan metode ekstensi C #:
Perbedaannya signifikan. Sebagai klien dari suatu metode, Anda tahu apa yang diharapkan. Sebuah tim dapat memiliki aturan:
Jika kelas bukan tipe NullableClass maka instance itu harus bukan nol .
Tim dapat memperkuat ide ini dengan menggunakan Desain dengan Kontrak dan pemeriksaan statis pada waktu kompilasi, misalnya dengan prasyarat:
atau untuk string
Pendekatan ini secara drastis dapat meningkatkan keandalan aplikasi dan kualitas perangkat lunak. Desain oleh Kontrak adalah kasus logika Hoare , yang dihuni oleh Bertrand Meyer dalam bukunya yang terkenal Object-Oriented Software Construction dan bahasa Eiffel pada tahun 1988, tetapi tidak digunakan secara tidak valid dalam pembuatan perangkat lunak modern.
sumber
Tidak.
Kegagalan bahasa untuk memperhitungkan nilai khusus seperti
null
adalah masalah bahasa. Jika Anda melihat bahasa seperti Kotlin atau Swift , yang memaksa penulis untuk secara eksplisit menangani kemungkinan nilai nol di setiap pertemuan, maka tidak ada bahaya.Dalam bahasa seperti yang bersangkutan, bahasa memungkinkan
null
untuk menjadi nilai dari setiap ish jenis, tetapi tidak memberikan konstruksi manapun untuk mengakui bahwa kemudian, yang mengarah ke hal-hal yangnull
tak terduga (terutama ketika melewati Anda dari tempat lain), dan dengan demikian Anda mendapatkan crash. Itu adalah kejahatan: membiarkan Anda menggunakannull
tanpa membuat Anda menghadapinya.sumber
Pada dasarnya ide sentral adalah bahwa masalah referensi pointer nol harus ditangkap pada waktu kompilasi alih-alih waktu berjalan. Jadi jika Anda menulis fungsi yang mengambil beberapa objek dan Anda tidak ingin ada yang memanggilnya dengan referensi nol, maka ketik sistem harus memungkinkan Anda untuk menentukan persyaratan itu sehingga kompiler dapat menggunakannya untuk memberikan jaminan bahwa panggilan ke fungsi Anda tidak akan pernah kompilasi jika penelepon tidak memenuhi kebutuhan Anda. Ini dapat dilakukan dengan banyak cara, misalnya, mendekorasi tipe Anda atau menggunakan tipe opsi (juga disebut tipe yang dapat dibatalkan dalam beberapa bahasa) tetapi jelas itu lebih banyak pekerjaan untuk desainer kompiler.
Saya pikir itu bisa dimengerti mengapa pada tahun 1960-an dan 70-an, perancang kompiler tidak melakukan apa-apa untuk mengimplementasikan ide ini karena mesin terbatas dalam sumber daya, kompiler perlu dijaga relatif sederhana dan tidak ada yang benar-benar tahu seberapa buruk ini akan berubah 40 tahun ke depan.
sumber
Saya punya satu keberatan sederhana terhadap
null
:Itu memecahkan semantik kode Anda sepenuhnya dengan memperkenalkan ambiguitas .
Seringkali Anda mengharapkan hasil dari tipe tertentu. Ini terdengar semantik. Katakanlah, Anda meminta basis data untuk pengguna dengan tertentu
id
, Anda mengharapkan resut dari tipe tertentu (=user
). Tapi, bagaimana jika tidak ada pengguna id itu? Salah satu solusi (buruk) adalah: menambahkannull
nilai. Jadi hasilnya ambigu : itu adalahuser
atau itunull
. Namunnull
bukan tipe yang diharapkan. Dan di sinilah bau kode dimulai.Dalam menghindari
null
, Anda membuat kode Anda semantik jelas.Selalu ada jalan keluar
null
: refactoring ke koleksiNull-objects
,optionals
dll.sumber
Jika semantik mungkin untuk lokasi penyimpanan referensi atau tipe pointer untuk diakses sebelum kode dijalankan untuk menghitung nilai untuk itu, tidak ada pilihan yang sempurna untuk apa yang harus terjadi. Memiliki lokasi seperti itu default ke referensi pointer nol yang dapat dibaca dan disalin secara bebas, tetapi yang akan kesalahan jika dereferenced atau diindeks, adalah salah satu pilihan yang mungkin. Pilihan lain termasuk:
Pilihan terakhir dapat memiliki beberapa daya tarik, terutama jika bahasa termasuk tipe nullable dan non-nullable (orang dapat memanggil konstruktor array khusus untuk semua jenis, tetapi satu hanya diminta untuk memanggil mereka ketika membuat array tipe non-nullable), tetapi mungkin tidak akan layak sekitar waktu null pointer ditemukan. Dari pilihan lain, tidak ada yang tampak lebih menarik daripada membiarkan pointer nol untuk disalin tetapi tidak dereferensi atau diindeks. Pendekatan # 4 mungkin nyaman untuk dimiliki sebagai suatu opsi, dan harusnya cukup murah untuk diterapkan, tetapi seharusnya tidak menjadi satu-satunya pilihan. Mewajibkan pointer harus secara default menunjuk ke beberapa objek tertentu yang valid jauh lebih buruk daripada memiliki pointer default ke nilai nol yang dapat dibaca atau disalin tetapi tidak dereferenced atau diindeks.
sumber
Ya, NULL adalah desain yang mengerikan , di dunia berorientasi objek. Singkatnya, penggunaan NULL mengarah ke:
Periksa posting blog ini untuk penjelasan terperinci: http://www.yegor256.com/2014/05/13/why-null-is-bad.html
sumber