it-swarm-id.com

Apakah referensi nol benar-benar buruk?

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 apakah ada pelanggan, 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?

165
Tim Goodman

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 tipe Option , yang dapat berisi SOME value atau 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 Bagus ke Java untuk mencegah NullPointerExceptions . fitur yang diminta diperkenalkan di Java 8.

93
Jonas

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 pernyataan if yang tidak dicentang. Tetapi apa yang terjadi ketika sesuatu yang Anda yakini tidak bisa mungkin menjadi nol sebenarnya adalah null? 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 untuk 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 Anda bisa menghemat waktu menggali.

129
BlairHippo

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 bagian sebagai spesialisasi biasanya dilakukan, menggunakan referensi nol menggunakan jenis/nilai lossy, generalisasi 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 pengecekan referensi nol lebih banyak dilakukan di berbagai tempat ... untuk jaga-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 memiliki runtime memeriksa jenis objek yang valid untuk memanggil metode.

Mengapa tidak menggunakan pola NullObject ke mengambil keuntungan dari pemeriksaan runtime untuk meminta dipanggil NOP metode 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 masing-masing keadaan khusus, alih-alih kode tempat keadaan tersebut mungkin ada. TKI, jumlah tes berkurang karena Anda memiliki lebih sedikit jalur eksekusi alternatif dalam metode Anda.

50
Huperniketes

Nulls tidak terlalu buruk, kecuali jika Anda tidak mengharapkannya. Anda harus perlu 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 harus 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 terlalu terbiasa dengannya. Saya lebih suka cara ini karena kode yang benar secara teoritis hanya perlu satu karakter ditambahkan untuk dikompilasi dengan benar.

Ada banyak jawaban bagus yang mencakup gejala malangnya null, jadi saya ingin menyajikan argumen alternatif: Null adalah cacat pada 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 dapat "mematikan rel" menjadi 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 Foo hanya boleh menampung Foos - itulah inti dari sistem tipe! null bukan Foo lebih dari "Hello, world!". Lebih buruk lagi, null bukan nilai dari jenis apa pun , dan tidak ada yang bisa Anda lakukan dengannya!

Pemrogram tidak pernah dapat memastikan bahwa suatu variabel benar-benar memegang Foo, dan begitu pula programnya; untuk menghindari perilaku yang tidak terdefinisi, ia harus memeriksa variabel untuk "Hello, world!" sebelum menggunakannya sebagai Foos. Perhatikan bahwa melakukan pemeriksaan string pada cuplikan sebelumnya tidak menyebarkan fakta bahwa foo1 benar-benar Foo - bar kemungkinan akan memiliki pemeriksaan sendiri juga, hanya agar aman.

Bandingkan dengan menggunakan tipe Maybe/Option dengan pencocokan pola:

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

Di dalam klausa Just foo, Anda dan program mengetahui dengan pasti bahwa variabel Maybe Foo Kami benar-benar mengandung nilai Foo - informasi tersebut disebarkan ke rantai panggilan, dan bar tidak perlu melakukan pemeriksaan. Karena Maybe Foo Adalah tipe yang berbeda dari Foo, Anda dipaksa untuk menangani kemungkinan bahwa ia bisa mengandung Nothing, jadi Anda tidak akan pernah bisa secara buta oleh NullPointerException. Anda dapat beralasan tentang program Anda lebih mudah dan kompiler dapat menghilangkan pemeriksaan nol karena mengetahui bahwa semua variabel tipe Foo benar-benar mengandung Foos. Semua orang menang.

20
Doval

Pointer null 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 benar, dibandingkan dengan mencari dan memperbaiki bug pointer nol. Sangat mudah untuk memeriksa pointer terhadap null. Ini adalah PITA untuk memverifikasi apakah penunjuk non-null menunjuk ke data yang valid.

Jika masih belum yakin, tambahkan dosis multithreading yang baik ke 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". Pointer null tidak jauh dari itu.


EDIT: Meskipun pola NullObject tampaknya menjadi solusi yang lebih baik daripada referensi yang mungkin batal, itu menimbulkan masalah sendiri:

  • Referensi yang memegang NullObject harus (menurut teori) tidak melakukan apa-apa ketika sebuah metode dipanggil. Dengan demikian, kesalahan halus dapat diperkenalkan karena referensi yang tidak ditugaskan, yang sekarang dijamin tidak nol (yehaa!) Tetapi melakukan tindakan yang tidak diinginkan: tidak ada. Dengan NPE itu jelas di mana masalahnya terletak. Dengan NullObject yang berperilaku entah bagaimana (tetapi 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 memilih NPE yang gagal segera, sekarang, karena kesalahan logis/data yang tiba-tiba mengenai saya nanti di jalan. Ingat studi IBM tentang biaya kesalahan menjadi fungsi pada tahap mana mereka terdeteksi?

  • Gagasan untuk tidak melakukan apa pun ketika suatu metode dipanggil pada NullObject tidak berlaku, ketika pengambil properti atau fungsi dipanggil, atau ketika metode mengembalikan nilai melalui out. Katakanlah, nilai kembali adalah int dan kami memutuskan, bahwa "tidak melakukan apa-apa" berarti "mengembalikan 0". Tapi bagaimana kita bisa yakin, bahwa 0 adalah "tidak ada" yang tepat untuk dikembalikan? Bagaimanapun, NullObject tidak boleh melakukan apa-apa, tetapi ketika ditanya nilainya, entah bagaimana ia harus bereaksi. Gagal bukan pilihan: Kami menggunakan NullObject untuk mencegah NPE, dan kami pasti tidak akan menukarnya dengan pengecualian lain (tidak diterapkan, bagi dengan nol, ...), bukan? Jadi bagaimana Anda benar tidak mengembalikan apa-apa ketika Anda harus mengembalikan sesuatu?

Saya tidak bisa membantu, tetapi ketika seseorang mencoba menerapkan pola NullObject 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.

15
JensG

(Melemparkan topiku di atas cincin untuk pertanyaan lama;))

Masalah khusus dengan null adalah kerusakan pengetikan statis.

Jika saya memiliki Thing t, Maka kompiler dapat menjamin saya dapat memanggil t.doSomething(). Yah, KECUALI t adalah nol saat runtime. Sekarang semua taruhan dibatalkan. Kompilator mengatakan itu OK, tapi saya mengetahui kemudian bahwa t TIDAK BUKAN 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. Seperti ekspresi yang ditandai seperti i = 1 / 0 Pada waktu kompilasi. Tetapi Anda tidak bisa benar-benar mengharapkan kompiler untuk 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 mengizinkan penugasan ke nilai (nol) yang akan memecah de-referensi ke 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 "this is referensi untuk sesuatu yang diharapkan," Anda membiarkan bahasa mengatakan "this mungkin saja referensi ke sesuatu yang diharapkan. "

14
Rob

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!");
}
8
fredoverflow

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

foo = null
foo.bar()

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

8
Matthieu M.

Masalah dengan null adalah bahwa 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 menjadi 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.

6
luis.espinal

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

4
Winston Ewert

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 diedarkan, 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, lalu 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

4
Michael Parker

Null itu jahat. Namun, kurangnya nol 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 nol Anda masih dapat meledak - baik Anda melakukan 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 mencoba untuk 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 nulls. Aku akan melompat kegirangan karena memperhatikan versi diri mereka.

3
Loren Pechtel

Optimalkan untuk kasus yang paling umum.

Harus memeriksa null 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.

1
JBRWilkinson

Pengecualian tidak eval!

Mereka ada di sana karena suatu alasan. Jika kode Anda berjalan buruk, ada pola jenius, yang disebut pengecualian, dan ia 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 jenis Opsi solusi seperti @Jonas tunjukkan, 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.

0
Ilya Gazman

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 pada setiap pertemuan, maka tidak ada bahaya.

Dalam bahasa seperti yang dipermasalahkan, bahasa memungkinkan null menjadi nilai apa saja-ish ketik, tetapi tidak memberikan konstruksi apa pun untuk mengakui hal itu nanti, yang mengarah pada hal-hal yang menjadi null secara tak terduga (terutama ketika diteruskan kepada Anda dari tempat lain), dan dengan demikian Anda mendapatkan crash. Itu adalah kejahatan: membiarkan Anda menggunakan null tanpa membuat Anda menghadapinya.

0
Ben Leggiero

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 tempat ketika seorang programmer mendeklarasikan variabel terlalu dini atau membiarkannya 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. Kadang-kadang, nilai yang hilang adalah keadaan 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 tidak 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 sekadar mengganti satu jenis pemeriksaan eksplisit dengan yang lain. Menggunakan Options, Anda dapat mem-chain 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, tempat kami membuat pesan yang luar biasa, kami tidak melakukan pemeriksaan eksplisit jika pelanggan ditemukan, dan keindahannya adalah kami tidak perlu . Tidak sampai baris terakhir, yang mungkin banyak baris kemudian, atau bahkan dalam modul yang berbeda, di mana kita secara eksplisit menyibukkan diri dengan apa yang terjadi jika Option adalah None. Tidak hanya itu, jika kami lupa melakukannya, itu tidak akan ketik diperiksa. Bahkan kemudian, itu dilakukan dengan cara yang sangat alami, tanpa pernyataan if yang eksplisit.

Bandingkan dengan 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 penggunaan huruf latihan unit Anda. Hanya saja tidak sepadan dengan kerumitannya.

0
Karl Bielefeldt

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 izinkan bahasa ini dalam keadaan saat ini untuk digunakan dalam aplikasi di mana keandalan sangat penting, yaitu, stasiun 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 yang 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 Generics untuk meminimalkan dampak referensi Null dengan membuat readonly NullableClass<T> (dua pilihan):

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 suatu kelas bukan dari tipe NullableClass maka instance itu harus bukan nol .

Tim dapat memperkuat gagasan ini dengan menggunakan Desain berdasarkan Kontrak dan pemeriksaan statis pada waktu kompilasi, mis. 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 dapat meningkatkan secara drastis aplikasi keandalan dan kualitas perangkat lunak. Desain oleh Kontrak adalah kasus Hoare logic , yang dihuni oleh Bertrand Meyer dalam bukunya yang terkenal Object-Oriented Software Construction book dan bahasa Eiffel pada tahun 1988, tetapi tidak digunakan secara tidak sah dalam pembuatan perangkat lunak modern.

0
Artru