Apakah referensi nol benar-benar buruk?

161

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?

Tim Goodman
sumber
54
Saya akan waspada dengan pendekatan "jika Pelanggan ada / kemudian dapatkan Pelanggan", segera setelah Anda menulis dua baris kode seperti itu, Anda membuka pintu untuk kondisi balapan. Dapatkan, dan kemudian periksa pengecualian nulls / catch lebih aman.
Carson63000
26
Kalau saja kita bisa menghilangkan sumber dari semua kesalahan runtime, kode kita akan dijamin sempurna! Jika referensi NULL dalam C # mematahkan otak Anda, coba tidak valid, tetapi non-NULL, petunjuk dalam C;)
LnxPrgr3
ada operator penggabungan nol dan navigasi aman dalam beberapa bahasa
jk.
35
null per se tidak buruk. Tipe sistem yang tidak membuat perbedaan antara tipe T dan tipe T + null buruk.
Ingo
27
Kembali ke pertanyaan saya sendiri beberapa tahun kemudian, saya sekarang benar-benar seorang mualaf dari pendekatan Opsi / Mungkin.
Tim Goodman

Jawaban:

91

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 valueatau NONE.

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.

Jonas
sumber
5
Nullable <x> dalam C # adalah konsep yang sama tetapi jauh dari implementasi pola opsi. Alasan utama adalah bahwa dalam. NET System.Nullable terbatas pada tipe nilai.
MattDavey
27
+1 Untuk jenis Opsi. Setelah mendapatkan akrab dengan Haskell Mungkin , nulls mulai mencari aneh ...
Andres F.
5
Pengembang dapat membuat kesalahan di mana saja, jadi alih-alih pengecualian null pointer Anda akan mendapatkan pengecualian opsi kosong. Bagaimana yang terakhir lebih baik dari yang pertama?
greenoldman
7
@greenoldman tidak ada yang namanya "pengecualian opsi kosong". Coba masukkan nilai NULL ke dalam kolom basis data yang didefinisikan sebagai NOT NULL.
Jonas
36
@greenoldman Masalah dengan null adalah bahwa apa pun bisa menjadi nol, dan sebagai pengembang Anda harus ekstra hati-hati. Sebuah Stringtipe tidak benar-benar string. Itu String or Nulltipe. Bahasa yang sepenuhnya mematuhi gagasan jenis opsi tidak mengizinkan nilai nol dalam sistem jenisnya, memberikan jaminan bahwa jika Anda menetapkan jenis tanda tangan yang memerlukan String(atau apa pun), itu harus memiliki nilai. The Optiontipe 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.
KChaloux
127

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 ifpernyataan 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.

BlairHippo
sumber
27
+1 Biasanya kesalahan dalam kode produksi PERLU untuk diidentifikasi secepat mungkin sehingga dapat diperbaiki (biasanya kesalahan data). Semakin mudah untuk debug, semakin baik.
4
Jawaban ini sama jika diterapkan pada salah satu contoh OP. Baik Anda menguji kondisi kesalahan atau tidak, dan Anda menanganinya dengan anggun atau tidak. Dengan kata lain, menghapus referensi nol dari bahasa tidak mengubah kelas kesalahan yang akan Anda alami, itu hanya akan secara halus mengubah manifestasi kesalahan tersebut.
dash-tom-bang
19
+1 untuk bom yang tidak meledak. Dengan referensi nol, mengatakan 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.
Matt Olenik
12
@ greenoldman, contoh Anda dua kali lebih efektif untuk menunjukkan poin kami dalam penggunaan tipe primitif (baik bilangan nol atau bilangan bulat) sebagai model semantik adalah praktik yang buruk. Jika Anda memiliki tipe yang bilangan negatifnya bukan jawaban yang valid, Anda harus membuat kelas tipe baru untuk mengimplementasikan model semantik dengan makna itu. Sekarang semua kode untuk menangani pemodelan tipe non-negatif tersebut dilokalkan ke kelasnya, diisolasi dari bagian sistem yang lain, dan segala upaya untuk menciptakan nilai negatif untuknya dapat ditangkap dengan segera.
Huperniketes
9
@ greenoldman, Anda terus membuktikan bahwa tipe primitif tidak memadai untuk pemodelan. Pertama, ini karena angka negatif. Sekarang sudah lewat operator divisi ketika nol adalah pembagi. Jika Anda tahu Anda tidak dapat membagi dengan nol maka Anda dapat memprediksi hasilnya tidak akan cantik, dan bertanggung jawab untuk memastikan Anda menguji, dan memberikan nilai "valid" yang sesuai untuk kasus itu. Pengembang perangkat lunak bertanggung jawab untuk mengetahui parameter di mana kode mereka beroperasi, dan mengkode dengan tepat. Itulah yang membedakan teknik dari bermain-main.
Huperniketes
50

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.

Huperniket
sumber
7
... karena mencari tahu siapa yang menghasilkan (atau lebih tepatnya, tidak repot-repot mengubah atau menginisialisasi) data sampah yang mengisi objek Anda yang seharusnya berguna jauh lebih mudah daripada melacak referensi NULL? Saya tidak membelinya, meskipun saya kebanyakan terjebak di tanah yang diketik secara statis.
dash-tom-bang
Data sampah jauh lebih jarang daripada referensi nol. Khususnya dalam bahasa yang dikelola.
Dean Harding
5
-1 untuk Objek Null.
DeadMG
4
Poin (4) dan (5) solid, tetapi NullObject bukan obat. Anda akan jatuh ke dalam kesalahan perangkap - logika (alur kerja) yang lebih buruk. Ketika Anda tahu PERSIS situasi saat ini, tentu saja, itu bisa membantu, tetapi menggunakannya secara membabi buta (bukan nol) akan lebih mahal.
greenoldman
1
@ gnasher729, sementara runtime Objective-C tidak akan membuang pengecualian null-pointer seperti Java dan sistem lainnya, itu tidak membuat menggunakan referensi nol lebih baik karena alasan yang saya uraikan dalam jawaban saya. Sebagai contoh, jika tidak ada navigationController di [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.
Huperniketes
48

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:

Customer? c = Customer.GetByLastName("Goodman");
// note the question mark -- 'c' is either a Customer or null
if (c != null)
{
    // c is not null, therefore its type in this block is
    // now 'Customer' instead of 'Customer?'
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

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.

Catatan untuk memikirkan nama sendiri
sumber
11
Wow, itu keren sekali. Selain menangkap lebih banyak kesalahan pada waktu kompilasi, saya terhindar dari keharusan untuk memeriksa dokumentasi untuk mencari tahu apakah GetByLastNamemengembalikan null jika tidak ditemukan (sebagai lawan melemparkan pengecualian) - Saya hanya dapat melihat apakah ia kembali Customer?atau Customer.
Tim Goodman
14
@JBRWilkinson: Ini hanya contoh matang untuk menunjukkan ide, bukan contoh implementasi yang sebenarnya. Saya sebenarnya berpikir itu ide yang cukup rapi.
Dean Harding
1
Anda benar bahwa itu bukan pola objek nol.
Dean Harding
13
Ini seperti apa yang Haskell sebut Maybe- A Maybe Customeradalah Just c(di mana ca Customer) atau Nothing. Dalam bahasa lain, namanya Option- lihat beberapa jawaban lain pada pertanyaan ini.
MatrixFrog
2
@SimonBarker: Anda bisa memastikan apakah Customer.FirstNamebertipe String(berlawanan dengan String?). Itulah manfaat nyata - hanya variabel / properti yang tidak memiliki nilai apa - apa yang bermakna yang harus dinyatakan sebagai nol.
Yogu
20

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:

Foo foo1 = new Foo();  // Legal
Foo foo2 = "Hello, world!"; // Also legal
Foo foo3 = "Bonjour!"; // Not legal - only "Hello, world!" is allowed

Dan Anda dapat memeriksa variabel seperti:

if (foo1 != "Hello, world!") {
    bar(foo1);
} else {
    baz();
}

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, katakanlah null,. Tetapi mengapa Anda melakukan ini? Variabel tipe Foohanya boleh menampung Foos - itulah inti dari sistem tipe! nulltidak Foolebih dari "Hello, world!"itu. Lebih buruk lagi, nullini 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 sebagai Foos. Perhatikan bahwa melakukan pemeriksaan string pada cuplikan sebelumnya tidak menyebarkan fakta bahwa foo1 benar-benar Foo- barkemungkinan akan memiliki pemeriksaan sendiri juga, hanya untuk aman.

Bandingkan dengan menggunakan a Maybe/ Optiontype dengan pencocokan pola:

case maybeFoo of
 |  Just foo => bar(foo)
 |  Nothing => baz()

Di dalam Just fooklausa, Anda dan program tahu pasti bahwa Maybe Foovariabel kami benar-benar mengandung Foonilai - bahwa informasi disebarkan ke rantai panggilan, dan bartidak perlu melakukan pemeriksaan apa pun. Karena Maybe Fooini adalah tipe yang berbeda dari Foo, Anda dipaksa untuk menangani kemungkinan yang dapat dikandungnya Nothing, sehingga Anda tidak akan pernah dapat secara buta oleh a NullPointerException. Anda dapat beralasan tentang program Anda lebih mudah dan kompiler dapat menghilangkan pemeriksaan nol karena mengetahui bahwa semua variabel tipe Foobenar-benar berisi Foos. Semua orang menang.

Doval
sumber
Bagaimana dengan nilai seperti null di Perl 6? Mereka masih nol, kecuali mereka selalu diketik. Apakah masih menjadi masalah yang dapat Anda tulis my Int $variable = Int, di mana Intnilai jenis null Int?
Konrad Borowski
@xfix Saya tidak terbiasa dengan Perl, tetapi selama Anda tidak memiliki cara menegakkan this type only contains integerssebagai lawan this type contains integers and this other value, Anda akan memiliki masalah setiap kali Anda hanya ingin bilangan bulat.
Doval
1
+1 untuk pencocokan pola. Dengan jumlah jawaban di atas yang menyebutkan jenis Opsi, Anda akan berpikir seseorang akan menyebutkan fakta bahwa fitur bahasa sederhana seperti pencocokan pola dapat membuatnya lebih intuitif untuk digunakan daripada null.
Jules
1
Saya pikir pengikatan monadik bahkan lebih berguna daripada pencocokan pola (meskipun tentu saja mengikat untuk Mungkin diimplementasikan menggunakan pencocokan pola). Saya pikir itu semacam bahasa melihat lucu seperti C # menempatkan sintaks khusus di untuk banyak hal ( ??, ?., dan sebagainya) untuk hal-hal yang bahasa-bahasa seperti Haskell melaksanakan fungsi sebagai perpustakaan. Saya pikir itu jauh lebih rapi. null/ Nothingpropagasi sama sekali bukan "istimewa", jadi mengapa kita harus belajar lebih banyak sintaks untuk memilikinya?
sara
14

(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 menelepon t.doSomething(). Nah, KECUALI tadalah nol saat runtime. Sekarang semua taruhan dibatalkan. Kompiler mengatakan tidak apa-apa, tapi saya tahu kemudian tternyata TIDAK doSomething(). 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 / 0pada 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:

String s = Integer baru (1234);

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."

Rob Y
sumber
3
Dalam C, variabel tipe *intmengidentifikasi int, atau NULL. Demikian juga dalam C ++, variabel tipe *Widgetmengidentifikasi a Widget, suatu hal yang berasal dari tipe Widget, atau NULL. Masalah dengan Java dan turunannya seperti C # adalah bahwa mereka menggunakan lokasi penyimpanan Widgetartinya *Widget. Solusinya bukan berpura-pura bahwa *Widgettidak seharusnya bisa tahan NULL, melainkan untuk memiliki Widgetjenis lokasi penyimpanan yang tepat .
supercat
14

Sebuah nullpointer adalah alat

bukan 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 nullpointer tidak begitu jauh dari itu.


EDIT: Meskipun NullObjectpola tampaknya menjadi solusi yang lebih baik daripada referensi yang mungkin nol, itu memperkenalkan masalah sendiri:

  • Referensi yang memegang suatu NullObjectkeharusan (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. Dengan NullObjectperilaku 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 NullObjecttidak berlaku, ketika pengambil properti atau fungsi dipanggil, atau ketika metode mengembalikan nilai melalui out. Katakanlah, nilai baliknya adalah intdan 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, NullObjectseharusnya tidak melakukan apa-apa, tetapi ketika ditanya nilai, itu harus bereaksi entah bagaimana. Gagal bukan pilihan: Kami menggunakan NullObjectuntuk 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 NullObjectpola 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.

JensG
sumber
Bisakah Anda menyajikan argumen mengapa nullharus ada? Sangat mungkin untuk melakukan handwave tentang kekurangan bahasa apa pun dengan "ini bukan masalah, tapi jangan membuat kesalahan".
Doval
Nilai apa yang akan Anda tetapkan pointer tidak valid?
JensG
Yah, Anda tidak mengaturnya ke nilai apa pun, karena Anda seharusnya tidak mengizinkannya sama sekali. Sebagai gantinya, Anda menggunakan variabel dari tipe yang berbeda, mungkin disebut Maybe Tyang mungkin mengandung Nothing, atau Tnilai. Kuncinya di sini adalah bahwa Anda dipaksa untuk memeriksa apakah Maybebenar - benar berisi Tsebelum 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 bukan Maybe.
Doval
1
@ JensG dalam contoh terakhir Anda, apakah kompiler membuat Anda melakukan pemeriksaan nol sebelum setiap panggilan 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 masuk tke metode ini? Dengan jenis Opsi, kompiler tahu apakah Anda sudah melakukan pemeriksaan atau belum dengan melihat jenis t. Jika itu sebuah Opsi, Anda belum memeriksanya, sedangkan jika itu adalah nilai yang terbuka maka Anda memilikinya.
Tim Goodman
1
Apa yang Anda null objek kembali ketika ditanya nilai?
JensG
8

Referensi kosong adalah kesalahan karena mereka memperbolehkan kode yang tidak masuk akal:

foo = null
foo.bar()

Ada beberapa alternatif, jika Anda memanfaatkan sistem tipe:

Maybe<Foo> foo = null
foo.bar() // error{Maybe<Foo> does not have any bar method}

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 memanfaatkan boost::optional<T>tetapi Anda masih bisa mendapatkan perilaku yang tidak terdefinisi ...

Matthieu M.
sumber
6
-1 untuk "leverage"!
Mark C
8
Tapi tentunya banyak konstruksi pemrograman umum memungkinkan kode yang tidak masuk akal, bukan? Pembagian dengan nol adalah (bisa dibilang) tidak masuk akal, tapi kami masih mengizinkan pembelahan, dan berharap programmer mengingat untuk memeriksa bahwa pembagi itu bukan nol (atau menangani pengecualian).
Tim Goodman
3
Aku tidak yakin apa yang Anda maksud dengan "dari awal", tapi Maybetidak dibangun ke dalam bahasa inti, definisi kebetulan berada di awal standar: data Maybe t = Nothing | Just t.
fredoverflow
4
@ FredOverflow: sejak pembukaan dimuat secara default, saya akan menganggapnya "dari awal", bahwa Haskell memiliki sistem tipe yang cukup menakjubkan untuk memungkinkan ini (dan lainnya) lebih baik untuk didefinisikan dengan aturan umum bahasa hanya bonus. :)
Matthieu M.
2
@TimGoodman Sementara pembagian dengan nol mungkin bukan contoh terbaik untuk semua tipe nilai numerik (IEEE 754 mendefinisikan hasil untuk pembagian dengan nol), dalam kasus di mana ia akan melempar pengecualian, alih-alih memiliki nilai tipe Tdibagi dengan nilai tipe lainnya T, Anda akan membatasi operasi ke nilai tipe Tdibagi dengan nilai tipe NonZero<T>.
kbolino
8

Dan apa alternatifnya?

Jenis dan pencocokan pola opsional. Karena saya tidak tahu C #, berikut adalah sepotong kode dalam bahasa fiksi yang disebut Scala # :-)

Customer.GetByLastName("Goodman")    // returns Option[Customer]
match
{
    case Some(customer) =>
    Console.WriteLine(customer.FirstName + " " + customer.LastName + " is awesome!");

    case None =>
    Console.WriteLine("There was no customer named Goodman.  How lame!");
}
fredoverflow
sumber
6

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

  1. objek yang Anda harapkan bukan nol memang tidak pernah nol, dan
  2. bahwa mekanisme pertahanan Anda memang menangani semua NPE potensial secara efektif.

Jadi, memang, nol akhirnya menjadi hal yang mahal untuk dimiliki.

luis.espinal
sumber
1
Mungkin bermanfaat jika Anda menyertakan contoh yang memerlukan lebih banyak upaya untuk menangani null. Dalam contoh saya yang terlalu disederhanakan, upaya yang dilakukan hampir sama. Saya tidak setuju dengan Anda, saya hanya menemukan contoh kode spesifik (semu) memberikan gambaran yang lebih jelas daripada pernyataan umum.
Tim Goodman
2
Ah, aku tidak menjelaskan diriku dengan jelas. Bukannya lebih sulit untuk berurusan dengan nol. Adalah sangat sulit (jika bukan tidak mungkin) untuk menjamin bahwa semua akses ke referensi yang berpotensi nol sudah terlindungi dengan baik. Artinya, dalam bahasa yang memungkinkan referensi nol, tidak mungkin untuk menjamin bahwa sepotong kode ukuran sewenang-wenang bebas dari pengecualian null pointer, bukan tanpa melalui beberapa kesulitan dan upaya besar. Itulah yang membuat referensi nol menjadi hal yang mahal. Ini sangat mahal untuk menjamin tidak adanya pengecualian null pointer.
luis.espinal
4

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.

Winston Ewert
sumber
4

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

public void MakeCake(Egg egg, Flour flour, Milk milk)
{
    if (egg == null) { throw ... }
    if (flour == null) { throw ... }
    if (milk == null) { throw ... }

    egg.Crack();
    MixingBowl.Mix(egg, flour, milk);
    // etc
}

// inside Mixing bowl class
public void Mix(Egg egg, Flour flour, Milk milk)
{
    if (egg == null) { throw ... }
    if (flour == null) { throw ... }
    if (milk == null) { throw ... }

    //... etc
}

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

Michael Parker
sumber
Saya pikir poin penting lain tentang Opsi <T> adalah bahwa itu adalah monad (dan karena itu juga merupakan endofunctor), sehingga Anda dapat membuat perhitungan yang rumit atas aliran nilai yang berisi jenis opsional tanpa secara eksplisit memeriksa Nonesetiap langkah dari jalan.
sara
3

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.

Loren Pechtel
sumber
1
Ini jawaban yang bagus. Nol konstruk dalam pemrograman bukan masalah, itu semua contoh nilai-nilai nol. Jika Anda membuat objek default, pada akhirnya semua null Anda akan diganti dengan default-nya dan UI Anda, DB dan di mana-mana di antara keduanya akan diisi dengan itu, yang mungkin tidak lebih berguna. Tapi Anda akan bisa tidur di malam hari karena setidaknya program Anda tidak macet saya kira.
Chris McCall
2
Anda menganggap itu nulladalah satu-satunya (atau bahkan lebih baik) cara pemodelan tidak adanya nilai.
sara
1

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.

JBRWilkinson
sumber
Saya akan menegaskan bahwa Anda tidak pernah tahu apakah Anda bisa mempercayai data yang masuk karena itu datang ke fungsi dan merupakan tipe nullable. Kode dapat di refactored untuk membuat input sama sekali berbeda. Jadi, jika ada nol, Anda harus selalu memeriksa. Jadi Anda berakhir dengan sistem di mana setiap variabel diperiksa untuk null setiap kali sebelum diakses. Kasus-kasus di mana tidak diperiksa menjadi persentase kegagalan untuk program Anda.
Chris McCall
0

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.

Ilya Gazman
sumber
1
Saya tahu sedikit lebih banyak tentang jenis Opsi sekarang daripada ketika saya memposting pertanyaan beberapa tahun yang lalu, karena sekarang saya menggunakannya secara teratur di Scala. Maksud dari Opsi adalah membuat opsi Noneuntuk sesuatu yang bukan Opsi menjadi kesalahan waktu kompilasi, dan juga menggunakan Optionseolah-olah memiliki nilai tanpa terlebih dahulu memeriksa itu adalah kesalahan waktu kompilasi. Kesalahan kompilasi tentu lebih baik daripada pengecualian run-time.
Tim Goodman
Sebagai contoh: Anda tidak bisa mengatakan s: String = None, Anda harus mengatakan s: Option[String] = None. Dan jika smerupakan Opsi, Anda tidak bisa mengatakannya s.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?") }
Tim Goodman
Sayangnya di Scala Anda masih bisa mengatakan s: String = null, tapi itu harga interoperabilitas dengan Java. Kami biasanya melarang hal semacam itu dalam kode Scala kami.
Tim Goodman
2
Namun saya setuju dengan komentar umum bahwa pengecualian (digunakan dengan benar) bukan hal yang buruk, dan "memperbaiki" pengecualian dengan menelannya pada umumnya adalah ide yang mengerikan. Tetapi dengan jenis Opsi, tujuannya adalah mengubah pengecualian menjadi kesalahan waktu kompilasi, yang merupakan hal yang lebih baik.
Tim Goodman
@TimGoodman apakah ini taktik scala saja atau dapatkah itu digunakan dalam bahasa lain seperti Java dan ActionScript 3? Juga akan lebih baik jika Anda dapat mengedit jawaban Anda dengan contoh lengkap.
Ilya Gazman
0

Nullsbermasalah 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, nulldigunakan 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:

val customer = customerDb getByLastName "Goodman"
val awesomeMessage =
  customer map (c => s"${c.firstName} ${c.lastName} is awesome!")
val notFoundMessage = "There was no customer named Goodman.  How lame!"
println(awesomeMessage getOrElse notFoundMessage)

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 Optionitu None. Tidak hanya itu, jika kami lupa melakukannya, itu tidak akan diperiksa jenisnya. Bahkan kemudian, itu dilakukan dengan cara yang sangat alami, tanpa ifpernyataan 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.

Karl Bielefeldt
sumber
0

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:

Maka, saran terbaik saya kepada para pencetus dan perancang ADA telah diabaikan. .... Jangan biarkan bahasa ini dalam keadaan saat ini untuk digunakan dalam aplikasi di mana keandalan sangat penting, yaitu, pembangkit listrik tenaga nuklir, rudal jelajah, sistem peringatan dini, sistem pertahanan rudal antiballistik. Roket berikutnya yang tersesat akibat kesalahan bahasa pemrograman mungkin bukan roket ruang eksplorasi dalam perjalanan tidak berbahaya ke Venus: Mungkin hulu ledak nuklir yang meledak di salah satu kota kita sendiri. Bahasa pemrograman yang tidak dapat diandalkan menghasilkan program yang tidak dapat diandalkan merupakan risiko yang jauh lebih besar bagi lingkungan kita dan masyarakat kita daripada mobil yang tidak aman, pestisida beracun, atau kecelakaan di pembangkit listrik tenaga nuklir. Waspada untuk mengurangi risiko, bukan untuk meningkatkannya.

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 Genericsuntuk meminimalkan dampak Nullreferensi dengan membuat readonly NullableClass<T>(dua Opsi):

class NullableClass<T>
{
     public HasValue {get;}
     public T Value {get;}
}

dan kemudian menggunakannya sebagai

NullableClass<Customer> customer = dbRepository.GetCustomer('Mr. Smith');
if(customer.HasValue){
  // one logic with customer.Value
}else{
  // another logic
} 

atau gunakan dua opsi gaya dengan metode ekstensi C #:

customer.Do(
      // code with normal behaviour
      ,
      // what to do in case of null
) 

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:

function SaveCustomer([NotNullAttribute]Customer customer){
     // there is no need to check whether customer is null 
     // it is a client problem, not this supplier
}

atau untuk string

function GetCustomer([NotNullAndNotEmptyAttribute]String customerName){
     // there is no need to check whether customerName is null or empty 
     // it is a client problem, not this supplier
}

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.

Artru
sumber
0

Tidak.

Kegagalan bahasa untuk memperhitungkan nilai khusus seperti nulladalah 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 nulluntuk menjadi nilai dari setiap ish jenis, tetapi tidak memberikan konstruksi manapun untuk mengakui bahwa kemudian, yang mengarah ke hal-hal yang nulltak terduga (terutama ketika melewati Anda dari tempat lain), dan dengan demikian Anda mendapatkan crash. Itu adalah kejahatan: membiarkan Anda menggunakan nulltanpa membuat Anda menghadapinya.

Ben Leggiero
sumber
-1

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.

Shital Shah
sumber
-1

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: menambahkan nullnilai. Jadi hasilnya ambigu : itu adalah useratau itu null. Namun nullbukan 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 koleksi Null-objects, optionalsdll.

Thomas Junk
sumber
-2

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:

  1. Tetapkan lokasi sebagai penunjuk nol, tetapi jangan izinkan kode melihatnya (atau bahkan menentukan bahwa itu nol) tanpa menabrak. Mungkin menyediakan sarana eksplisit untuk menetapkan pointer ke nol.
  2. Tetapkan lokasi sebagai penunjuk null, dan lumpuh jika ada upaya untuk membacanya, tetapi berikan cara non-menabrak untuk menguji apakah penunjuk memegang null. Juga sediakan sarana untuk menetapkan pointer ke nol.
  3. Memiliki lokasi default ke pointer ke beberapa contoh default yang disediakan kompiler tertentu dari tipe yang ditunjukkan.
  4. Memiliki lokasi default ke pointer ke beberapa contoh default yang disediakan program khusus dari jenis yang ditunjukkan.
  5. Memiliki akses null-pointer (bukan hanya operasi dereferencing) memanggil beberapa program yang disediakan untuk memasok contoh.
  6. Rancang semantik bahasa sehingga koleksi lokasi penyimpanan tidak akan ada, kecuali kompiler yang tidak dapat diakses sementara, sampai waktu rutin inisialisasi dijalankan pada semua anggota (sesuatu seperti konstruktor array harus disediakan dengan salah satu contoh default, sebuah fungsi yang akan mengembalikan sebuah instance, atau mungkin sepasang fungsi - satu untuk mengembalikan sebuah instance dan satu yang akan dipanggil pada instance yang dibangun sebelumnya jika pengecualian terjadi selama konstruksi array).

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.

supercat
sumber
-2

Ya, NULL adalah desain yang mengerikan , di dunia berorientasi objek. Singkatnya, penggunaan NULL mengarah ke:

  • penanganan kesalahan ad-hoc (bukan pengecualian)
  • semantik ambigu
  • lambat bukannya gagal cepat
  • pemikiran komputer vs pemikiran objek
  • benda yang bisa berubah dan tidak lengkap

Periksa posting blog ini untuk penjelasan terperinci: http://www.yegor256.com/2014/05/13/why-null-is-bad.html

yegor256
sumber