Saya tidak begitu mengerti bashing konsisten dari referensi nol oleh beberapa orang bahasa pemrograman. Apa yang buruk dari mereka? Jika saya meminta akses baca ke file yang tidak ada maka saya sangat senang mendapatkan pengecualian atau referensi nol dan namun pengecualian dianggap baik tetapi referensi nol dianggap buruk. Apa alasan di balik ini?
programming-languages
exceptions
null
davidk01
sumber
sumber
Jawaban:
Referensi Null tidak "dijauhi" lebih dari pengecualian, setidaknya oleh siapa pun yang pernah saya kenal atau baca. Saya pikir Anda salah memahami kebijaksanaan konvensional.
Yang buruk adalah upaya untuk mengakses referensi nol (atau dereferensi pointer nol, dll.). Ini buruk karena selalu menunjukkan bug; Anda tidak akan pernah melakukan sesuatu seperti ini dengan sengaja, dan jika Anda sedang melakukannya dengan sengaja, maka itu bahkan lebih buruk, karena itu membuat mustahil untuk membedakan perilaku yang diharapkan dari perilaku kereta.
Ada beberapa kelompok pinggiran di luar sana yang hanya benar-benar membenci konsep nullity karena suatu alasan, tetapi seperti yang Ed tunjukkan , jika Anda tidak memiliki
null
ataunil
kemudian Anda hanya perlu menggantinya dengan sesuatu yang lain, yang mungkin mengarah pada sesuatu lebih buruk daripada kerusakan (seperti korupsi data).Banyak kerangka kerja, pada kenyataannya, merangkul kedua konsep; misalnya, di .NET, pola yang sering Anda lihat adalah sepasang metode, yang diawali dengan kata
Try
(sepertiTryGetValue
). Dalam halTry
ini, referensi diatur ke nilai default (biasanyanull
), dan dalam kasus lain, pengecualian dilemparkan. Tidak ada yang salah dengan kedua pendekatan itu; keduanya sering digunakan di lingkungan yang mendukungnya.Itu benar-benar semua tergantung pada semantik. Jika
null
nilai pengembalian yang valid, seperti dalam kasus umum mencari koleksi, maka kembalinull
. Di sisi lain, jika itu bukan nilai balik yang valid - misalnya, mencari catatan menggunakan kunci utama yang berasal dari database Anda sendiri - maka kembalinull
akan menjadi ide yang buruk karena penelepon tidak akan mengharapkannya dan mungkin tidak akan memeriksanya.Ini benar-benar sangat sederhana untuk mengetahui semantik mana yang digunakan: Apakah masuk akal untuk hasil suatu fungsi yang tidak terdefinisi? Jika demikian, Anda dapat mengembalikan referensi nol. Jika tidak, berikan pengecualian.
sumber
null
, sehingga benar-benar, Haskell cukup relevan di sini.null
bertahan begitu lama, dan sebagian besar orang yang mengatakan sebaliknya tampaknya adalah akademisi dengan sedikit pengalaman merancang aplikasi dunia nyata dengan kendala seperti persyaratan yang tidak lengkap atau konsistensi akhirnya.Perbedaan besar adalah bahwa jika Anda meninggalkan kode untuk menangani NULLs, kode Anda akan terus menerjang pada tahap selanjutnya dengan beberapa pesan kesalahan yang tidak terkait, di mana seperti halnya pengecualian, pengecualian akan dimunculkan pada titik awal kegagalan (pembukaan file untuk dibaca dalam contoh Anda).
sumber
Karena nilai nol bukan bagian penting dari bahasa pemrograman dan merupakan sumber bug yang konsisten. Seperti yang Anda katakan, membuka file dapat menyebabkan kegagalan, yang dapat dikomunikasikan kembali sebagai nilai pengembalian nol atau melalui pengecualian. Jika nilai nol tidak diizinkan maka ada cara konsisten dan tunggal untuk mengkomunikasikan kegagalan.
Juga, ini bukan masalah yang paling umum dengan nulls. Kebanyakan orang ingat untuk memeriksa nol setelah memanggil fungsi yang dapat mengembalikannya. Masalah muncul jauh lebih banyak dalam desain Anda sendiri dengan memungkinkan variabel menjadi nol pada berbagai titik dalam pelaksanaan program Anda. Anda dapat mendesain kode Anda sehingga nilai-nilai nol tidak pernah diizinkan, tetapi jika nol tidak diizinkan di tingkat bahasa, semua ini tidak diperlukan.
Namun, dalam praktiknya Anda masih perlu beberapa cara untuk menandakan jika suatu variabel diinisialisasi atau tidak. Anda kemudian akan memiliki bentuk bug di mana program Anda tidak crash, tetapi terus menggunakan beberapa nilai default yang mungkin tidak valid. Sejujurnya saya tidak tahu mana yang lebih baik. Untuk uang saya, saya suka crash awal dan sering.
sumber
null
sebagai nilai pengembalian: Informasi yang Anda minta tidak ada. Tidak ada perbedaan. Either way, penelepon harus memvalidasi nilai kembali jika itu benar-benar perlu menggunakan informasi itu untuk tujuan tertentu. Apakah itu memvalidasinull
, objek nol, atau monad tidak membuat perbedaan praktis.Tony Hoare, yang menciptakan ide referensi nol di tempat pertama, menyebutnya kesalahan satu juta dolar .
Masalahnya bukan tentang referensi nol per-se, tetapi tentang kurangnya pengecekan jenis yang tepat di sebagian besar (kecuali) jenis bahasa aman.
Kurangnya dukungan ini, dari bahasa, berarti bahwa bug "null-bug" mungkin bersembunyi di program untuk waktu yang lama sebelum terdeteksi. Seperti itulah sifat bug, tentu saja, tetapi "null-bug" sekarang diketahui dapat dihindari.
Masalah ini terutama hadir dalam C atau C ++ (misalnya) karena kesalahan "keras" yang disebabkannya (crash program, langsung, tanpa pemulihan yang elegan).
Dalam bahasa lain, selalu ada pertanyaan tentang bagaimana menanganinya.
Di Java atau C # Anda akan mendapatkan pengecualian jika Anda mencoba untuk memanggil metode pada referensi nol, dan mungkin tidak apa-apa. Dan oleh karena itu sebagian besar programmer Java atau C # terbiasa dengan ini dan tidak mengerti mengapa orang ingin melakukan sebaliknya (dan menertawakan C ++).
Di Haskell, Anda harus secara eksplisit memberikan tindakan untuk case nol, dan karena itu programmer Haskell menertawakan rekan-rekan mereka, karena mereka sudah benar (benar?).
Ini benar-benar debat kode kesalahan / pengecualian lama, tapi kali ini dengan Nilai Sentinel sebagai pengganti kode kesalahan.
Seperti biasa, mana yang paling tepat sangat tergantung pada situasi dan semantik yang Anda cari.
sumber
null
benar-benar jahat, karena merongrong sistem tipe . (Memang, alternatifnya memiliki kelemahan dalam verbositasnya, setidaknya dalam beberapa bahasa.)Anda tidak bisa melampirkan pesan kesalahan yang bisa dibaca manusia ke pointer nol.
(Namun, Anda dapat meninggalkan pesan kesalahan dalam file log.)
Dalam beberapa bahasa / lingkungan yang memungkinkan aritmatika pointer, jika salah satu argumen pointer adalah nol dan diizinkan ke dalam perhitungan, hasilnya akan menjadi pointer yang tidak valid dan tidak nol . (*) Lebih banyak kekuatan untuk Anda.
(*) Hal ini sering terjadi pada pemrograman COM , di mana jika Anda mencoba memanggil ke metode antarmuka tetapi pointer antarmuka adalah nol, itu akan menghasilkan panggilan ke alamat yang tidak valid yang hampir, tetapi tidak cukup, persis tidak seperti nol.
sumber
Mengembalikan NULL (atau angka nol, atau boolean false) untuk memberi sinyal kesalahan adalah salah secara teknis dan konseptual.
Secara teknis, Anda membebani pemrogram dengan memeriksa nilai kembali segera , pada titik yang tepat di mana ia dikembalikan. Jika Anda membuka dua puluh file berturut-turut, dan pensinyalan kesalahan dilakukan dengan mengembalikan NULL, maka kode yang dikonsumsi harus memeriksa setiap file yang dibaca secara individual, dan keluar dari setiap loop dan konstruksi yang serupa. Ini adalah resep yang sempurna untuk kode yang berantakan. Namun, jika Anda memilih untuk memberi sinyal kesalahan dengan melemparkan pengecualian, kode yang dikonsumsi dapat memilih untuk menangani pengecualian dengan segera, atau membiarkannya naik ke level sebanyak yang sesuai, bahkan di seluruh panggilan fungsi. Ini membuat kode lebih bersih.
Secara konseptual, jika Anda membuka file dan ada yang tidak beres, maka mengembalikan nilai (bahkan NULL) salah. Anda tidak memiliki apa pun untuk dikembalikan, karena operasi Anda tidak selesai. Mengembalikan NULL adalah padanan konseptual dari "Saya telah berhasil membaca file, dan inilah yang dikandungnya - tidak ada". Jika itu yang ingin Anda ungkapkan (yaitu, jika NULL masuk akal sebagai hasil aktual untuk operasi yang dimaksud), maka tentu saja kembalikan NULL, tetapi jika Anda ingin memberi sinyal kesalahan, gunakan pengecualian.
Secara historis, kesalahan dilaporkan dengan cara ini karena bahasa pemrograman seperti C tidak memiliki penanganan perkecualian yang dibangun ke dalam bahasa tersebut, dan cara yang disarankan (menggunakan lompat jauh) agak berbulu dan agak kontra-intuitif.
Ada juga sisi pemeliharaan untuk masalah ini: dengan pengecualian, Anda harus menulis kode tambahan untuk menangani kegagalan; jika tidak, program akan gagal lebih awal dan sulit (mana yang baik). Jika Anda mengembalikan NULL ke sinyal kesalahan, perilaku default untuk program ini adalah mengabaikan kesalahan dan melanjutkannya, sampai mengarah ke masalah lain di jalan - data rusak, segfaults, NullReferenceExceptions, tergantung pada bahasanya. Untuk memberi sinyal kesalahan awal dan keras, Anda harus menulis kode tambahan, dan coba tebak: itulah bagian yang ditinggalkan ketika Anda berada di tenggat waktu yang ketat.
sumber
Seperti yang telah ditunjukkan, banyak bahasa tidak mengonversi dereferensi penunjuk nol menjadi pengecualian yang dapat ditangkap. Melakukan itu adalah trik yang relatif modern. Ketika masalah null pointer pertama kali dikenali, pengecualian bahkan belum ditemukan.
Jika Anda mengizinkan null pointer sebagai kasus yang valid, itu adalah kasus khusus. Anda memerlukan logika penanganan kasus khusus, seringkali di banyak tempat berbeda. Itu kompleksitas ekstra.
Apakah itu terkait dengan pointer berpotensi nol atau tidak, jika Anda tidak menggunakan lemparan pengecualian untuk menangani kasus luar biasa, Anda harus menangani kasus luar biasa dengan cara lain. Biasanya, setiap panggilan fungsi harus diperiksa untuk kasus-kasus yang luar biasa, baik untuk mencegah panggilan fungsi tidak tepat, atau untuk mendeteksi kasus kegagalan ketika fungsi keluar. Itu kompleksitas ekstra yang bisa dihindari menggunakan pengecualian.
Semakin banyak kompleksitas biasanya berarti lebih banyak kesalahan.
Alternatif untuk menggunakan pointer nol dalam struktur data (misalnya untuk menandai awal / akhir daftar tertaut) termasuk menggunakan item sentinel. Ini dapat memberikan fungsionalitas yang sama dengan kompleksitas yang jauh lebih sedikit. Namun, ada cara lain untuk mengelola kompleksitas. Salah satu caranya adalah dengan membungkus pointer berpotensi nol dalam kelas pointer pintar sehingga pemeriksaan nol hanya diperlukan di satu tempat.
Apa yang harus dilakukan tentang pointer nol ketika terdeteksi? Jika Anda tidak dapat membangun dalam penanganan kasus luar biasa, Anda selalu dapat melemparkan pengecualian, dan secara efektif mendelegasikan penanganan kasus khusus ke penelepon. Dan itulah yang dilakukan beberapa bahasa secara default ketika Anda melakukan dereferensi pointer nol.
sumber
Khusus untuk C ++, tetapi ada referensi nol dijauhi di sana karena nol semantik di C ++ dikaitkan dengan jenis pointer. Cukup masuk akal jika fungsi buka file gagal dan mengembalikan pointer nol; sebenarnya
fopen()
fungsi melakukan hal itu.sumber
Itu tergantung pada bahasanya.
Misalnya, Objective-C memungkinkan Anda untuk mengirim pesan ke objek nol (nil) tanpa masalah. Panggilan ke nil mengembalikan nihil dan dianggap sebagai fitur bahasa.
Saya pribadi suka karena Anda dapat mengandalkan perilaku itu dan menghindari semua
if(obj == null)
konstruksi bersarang yang berbelit-belit .Contohnya:
Dapat disingkat menjadi:
Singkatnya, itu membuat kode Anda lebih mudah dibaca.
sumber
Saya tidak memberikan teriakan apakah Anda mengembalikan nol atau melemparkan pengecualian, selama Anda mendokumentasikannya.
sumber
GetAddressMaybe
atauGetSpouseNameOrNull
.Referensi Null biasanya terjadi karena sesuatu dalam logika program tidak terjawab, yaitu: Anda mendapatkan baris kode tanpa melalui pengaturan yang diperlukan untuk blok kode itu.
Di sisi lain, jika Anda melemparkan pengecualian untuk sesuatu itu berarti Anda mengenali bahwa situasi tertentu dapat terjadi dalam operasi normal program, dan sedang ditangani.
sumber
Referensi kosong sering sangat berguna: Misalnya, elemen dalam daftar tertaut dapat memiliki beberapa penerus atau tidak ada penerus. Menggunakan
null
untuk "tidak ada penerus" adalah hal yang wajar. Atau, seseorang dapat memiliki pasangan atau tidak - menggunakannull
untuk "seseorang tidak memiliki pasangan" adalah sangat alami, jauh lebih alami daripada memiliki beberapa "tidak memiliki pasangan" - nilai khusus yangPerson.Spouse
dapat dirujuk oleh anggota tersebut.Tetapi: Banyak nilai tidak opsional. Dalam program OOP yang khas, saya akan mengatakan lebih dari setengah referensi tidak dapat
null
setelah inisialisasi, atau program akan gagal. Kalau tidak, kodenya harus penuh denganif (x != null)
cek. Jadi mengapa setiap referensi harus dibatalkan secara default? Itu harus benar-benar sebaliknya: variabel harus non-nullable secara default, dan Anda harus secara eksplisit mengatakan "oh, dan nilai ini juga bisanull
".sumber
Pertanyaan Anda membingungkan. Maksud Anda pengecualian referensi nol (yang sebenarnya disebabkan oleh upaya untuk dereferensi nol)? Alasan yang jelas untuk tidak menginginkan jenis pengecualian itu adalah bahwa ia tidak memberi Anda informasi tentang apa yang salah, atau bahkan kapan - nilainya bisa disetel ke nol pada titik mana pun dalam program. Anda menulis "Jika saya meminta akses baca ke file yang tidak ada maka saya sangat senang mendapatkan pengecualian atau referensi nol" - tetapi Anda tidak harus benar-benar bahagia mendapatkan sesuatu yang tidak memberikan indikasi penyebabnya . Tidak ada dalam string "upaya dilakukan untuk dereference null" apakah ada disebutkan membaca atau file yang tidak ada. Mungkin maksud Anda bahwa Anda akan sangat senang mendapatkan null sebagai nilai balik dari panggilan baca - itu masalah yang sangat berbeda, tetapi masih memberi Anda informasi tentang mengapa pembacaan gagal; saya t'
sumber