Haruskah seseorang memeriksa nol jika dia tidak mengharapkan nol?

113

Pekan lalu, kami memiliki argumen panas tentang penanganan null di lapisan layanan aplikasi kami. Pertanyaannya adalah dalam konteks .NET, tetapi akan sama di Jawa dan banyak teknologi lainnya.

Pertanyaannya adalah: apakah Anda harus selalu memeriksa nol dan membuat kode Anda berfungsi apa pun, atau membiarkan pengecualian muncul ketika nol diterima secara tak terduga?

Di satu sisi, memeriksa nol di mana Anda tidak mengharapkannya (yaitu tidak memiliki antarmuka pengguna untuk menanganinya), menurut pendapat saya, sama dengan menulis blok percobaan dengan tangkapan kosong. Anda hanya menyembunyikan kesalahan. Kesalahan mungkin karena ada sesuatu yang berubah dalam kode dan null sekarang merupakan nilai yang diharapkan, atau ada beberapa kesalahan lain dan ID yang salah diteruskan ke metode.

Di sisi lain, memeriksa nol mungkin merupakan kebiasaan yang baik secara umum. Terlebih lagi, jika ada pemeriksaan, aplikasi dapat berfungsi, hanya sebagian kecil dari fungsi yang tidak memiliki efek apa pun. Kemudian pelanggan mungkin melaporkan bug kecil seperti "tidak dapat menghapus komentar" bukannya bug yang lebih parah seperti "tidak dapat membuka halaman X".

Latihan apa yang Anda ikuti dan apa argumen Anda untuk atau menentang salah satu pendekatan?

Memperbarui:

Saya ingin menambahkan beberapa detail tentang kasus khusus kami. Kami mengambil beberapa objek dari database dan melakukan beberapa pemrosesan pada mereka (katakanlah, buat koleksi). Pengembang yang menulis kode tidak mengantisipasi bahwa objek tersebut bisa nol sehingga ia tidak menyertakan pemeriksaan, dan ketika halaman dimuat ada kesalahan dan seluruh halaman tidak memuat.

Jelas, dalam hal ini seharusnya ada cek. Lalu kami masuk ke argumen tentang apakah setiap objek yang diproses harus diperiksa, bahkan jika itu tidak diharapkan akan hilang, dan apakah proses akhirnya harus dibatalkan secara diam-diam.

Manfaat hipotetis adalah bahwa halaman akan terus berfungsi. Pikirkan hasil pencarian di Stack Exchange di berbagai grup (pengguna, komentar, pertanyaan). Metode ini dapat memeriksa nol dan membatalkan pemrosesan pengguna (yang karena bug adalah nol) tetapi mengembalikan bagian "komentar" dan "pertanyaan". Halaman akan terus berfungsi kecuali bahwa bagian "pengguna" akan hilang (yang merupakan bug). Haruskah kita gagal lebih awal dan menghancurkan seluruh halaman atau terus bekerja dan menunggu seseorang memperhatikan bahwa bagian "pengguna" tidak ada?

Stilgar
sumber
62
Prinsip Fox Mulder: Jangan percaya siapa pun.
yannis
14
@YannisRizos: prinsip itu bagus - prinsip ini sangat bagus sehingga pembuat Java dan C # membiarkan lingkungan waktu bahasa dijalankan dengan sendirinya. Jadi biasanya tidak perlu melakukan pemeriksaan eksplisit untuk null di mana pun dalam kode Anda dalam bahasa tersebut.
Doc Brown
4
mungkin rangkap dari Haruskah suatu metode memvalidasi parameternya?
Martin York
Saya setuju dengan jawaban tammmer. Saya percaya memeriksa nol itu salah karena Anda tidak memiliki cara yang masuk akal untuk menanganinya. Namun dalam skenario Anda, Anda dapat menangani bagian yang gagal seperti memvalidasi pengguna (atau mendapatkan komentar atau apa pun itu) dan memiliki kotak "oh tidak ada kesalahan telah terjadi". Saya pribadi mencatat semua pengecualian di aplikasi saya dan memfilter pengecualian tertentu seperti 404-an dan koneksi ditutup secara tidak terduga
2
assert(foo != null, "foo is web control within the repeater, there's no reason to expect it to be null, etc, etc...");
zzzzBov

Jawaban:

105

Pertanyaannya bukanlah seberapa banyak Anda harus memeriksa nullatau membiarkan runtime membuat pengecualian; itu adalah bagaimana Anda harus menanggapi situasi yang tidak terduga seperti itu.

Maka pilihan Anda adalah:

  • Lempar pengecualian generik ( NullReferenceException) dan biarkan menggelembung; jika Anda tidak melakukan nullpemeriksaan sendiri, ini yang terjadi secara otomatis.
  • Lempar pengecualian khusus yang menjelaskan masalah pada tingkat yang lebih tinggi; ini dapat dicapai baik dengan melemparkan nullcek, atau dengan menangkap NullReferenceExceptiondan melemparkan pengecualian yang lebih spesifik.
  • Pulihkan dengan mengganti nilai default yang sesuai.

Tidak ada aturan umum yang mana yang merupakan solusi terbaik. Secara pribadi, saya akan mengatakan:

  • Pengecualian umum adalah yang terbaik jika kondisi kesalahan akan menjadi tanda bug serius dalam kode Anda, yaitu, nilai yang nol tidak boleh diizinkan untuk sampai di sana. Pengecualian seperti itu kemudian dapat menggelembungkan semuanya ke dalam kesalahan log apa pun yang telah Anda atur, sehingga seseorang mendapat pemberitahuan dan memperbaiki bug.
  • Solusi nilai default baik jika memberikan output semi-berguna lebih penting daripada kebenaran, seperti browser web yang menerima HTML yang secara teknis salah dan melakukan upaya terbaik untuk menjadikannya masuk akal.
  • Kalau tidak, saya akan pergi dengan pengecualian khusus dan menanganinya di tempat yang cocok.
tammmer
sumber
1
@Stilgar untuk pengguna akhir tidak ada perbedaan. untuk pengembang, pengecualian khusus seperti "database-server tidak dapat dijangkau" lebih intuitif bahwa "null pointer exceptioneion"
k3b
32
Gagal dan dini berarti bahwa ada sedikit risiko terjadi kesalahan yang semakin halus, hal-hal seperti kesalahan otentikasi, data yang rusak tetap tersimpan, kebocoran informasi, dan hal-hal lucu lainnya.
Lars Viklund
1
@Stilgar: Ada dua masalah di sini: perilaku yang tidak dijaga, dan pemeliharaan. Sementara untuk perilaku yang tidak dijaga ada sedikit perbedaan antara pengecualian spesifik dan yang umum, untuk pemeliharaan itu dapat membuat perbedaan antara "oh, jadi itu sebabnya hal-hal meledak" dan debugathlon multi-jam.
Pelaku
3
Pelaku itu benar. "Referensi objek tidak disetel ke instance objek" adalah salah satu pengecualian paling menjengkelkan yang saya lihat dengan frekuensi terbanyak. Saya lebih suka melihat pengecualian khusus apakah itu ArgumentNullException dengan nama parameter, atau pengecualian kustom "Tidak ada catatan Post ada untuk pengguna TK421."
Sean Chase
1
@ k3b Untuk pengguna akhir juga, "database-server tidak dapat dijangkau" sangat membantu. Setidaknya, untuk pengguna akhir mana pun yang memiliki sedikit petunjuk tentang masalah umum - mungkin membantunya menemukan bahwa kabel longgar, router perlu dinyalakan kembali, dll. Daripada menjadi marah tentang perangkat lunak kereta.
Julia Hayward
58

IMHO mencoba menangani nilai nol yang tidak Anda harapkan mengarah pada kode yang terlalu rumit. Jika Anda tidak mengharapkan nol, jelaskan dengan melempar ArgumentNullException. Saya benar-benar frustrasi ketika orang memeriksa apakah nilainya nol dan kemudian mencoba menulis beberapa kode yang tidak masuk akal. Hal yang sama berlaku untuk menggunakan SingleOrDefault(atau bahkan lebih buruk mendapatkan koleksi) ketika seseorang benar-benar mengharapkan Singledan banyak kasus lain ketika orang takut (saya tidak benar-benar tahu apa) untuk menyatakan logika mereka dengan jelas.

empi
sumber
Alih-alih ArgumentNullException, seseorang harus menggunakan kontrak kode, (kecuali jika itu adalah kode sumber pra-2010 yang tidak mendukung kontrak kode).
Arseni Mourzenko
Saya setuju denganmu. Jika nol tidak diharapkan tidak boleh ditangani dengan cara yang aneh tetapi hanya dilaporkan.
Giorgio
9
jika saya membaca pertanyaan dengan benar, melemparkan ArgumentNullExceptionhitungan sebagai "penanganan" nilai nol: itu mencegah pengecualian referensi nol kemudian.
KutuluMike
@MichaelEdenfield: Saya tidak berpikir itu dianggap sebagai penanganan nyata sesuai dengan pertanyaan yang diberikan (tujuan kami bukan untuk membuat kode Anda bekerja tidak peduli apa ). Sejujurnya, saya sering lupa untuk menulis blok yang sesuai yang akan melempar jika nol dilewatkan. Namun, saya menganggap melempar NullArgumentException sebagai praktik terbaik dan praktik terburuk menurut saya adalah menulis kode yang tidak dapat menangani null tetapi alih-alih melempar pengembalian pengecualian, misalnya null lain sebagai hasil metode (atau string kosong, atau koleksi kosong, atau -1 , atau melewatkan eksekusi metode jika jenis kembali tidak valid, dll.).
empi
2
@Iorgio, ArgumentNullExceptionmembuatnya sangat jelas apa masalahnya dan bagaimana cara memperbaikinya. C # tidak cenderung menggunakan "tidak terdefinisi". Jika Anda memanggil sesuatu dengan cara yang tidak ditentukan, maka cara Anda memanggilnya tidak masuk akal. Pengecualian adalah cara yang tepat untuk menunjukkan hal itu. Sekarang, kadang-kadang saya akan membuat metode parsing yang mengembalikan nol jika int(misalnya) tidak dapat diuraikan. Tapi itu adalah kasus di mana hasil yang tidak dapat dipecahkan adalah kemungkinan yang sah dan bukan kesalahan penggunaan. Lihat juga artikel ini .
Kyralessa
35

Kebiasaan memeriksa nullpengalaman saya berasal dari mantan pengembang C atau C ++, dalam bahasa-bahasa tersebut Anda memiliki peluang besar untuk menyembunyikan kesalahan parah ketika tidak memeriksa NULL. Seperti yang Anda ketahui, di Java atau C # hal-hal berbeda, menggunakan null ref tanpa memeriksa nullakan menyebabkan pengecualian, sehingga kesalahan tidak akan disembunyikan secara diam-diam selama Anda tidak mengabaikannya di sisi panggilan. Jadi dalam kebanyakan kasus, memeriksa secara eksplisit nulltidak masuk akal dan membuat kode lebih rumit dari yang diperlukan. Itu sebabnya saya tidak menganggap pemeriksaan nol sebagai kebiasaan yang baik dalam bahasa tersebut (setidaknya, tidak secara umum).

Tentu saja, ada pengecualian dari aturan itu, berikut adalah beberapa yang dapat saya pikirkan:

  • Anda menginginkan pesan kesalahan yang lebih baik dan dapat dibaca manusia, memberi tahu pengguna di bagian mana dari program referensi nol yang salah terjadi. Jadi tes Anda untuk null hanya melempar pengecualian yang berbeda, dengan teks kesalahan yang berbeda. Jelas, tidak ada kesalahan yang akan disembunyikan seperti itu.

  • Anda ingin membuat program Anda "gagal lebih awal". Misalnya, Anda ingin memeriksa nilai nol dari parameter konstruktor, di mana referensi objek hanya akan disimpan dalam variabel anggota dan digunakan nanti.

  • Anda dapat menangani situasi ref null dengan aman, tanpa risiko kesalahan berikutnya, dan tanpa menutupi kesalahan parah.

  • Anda ingin jenis pensinyalan kesalahan yang sama sekali berbeda (misalnya, mengembalikan kode kesalahan) dalam konteks Anda saat ini

Doc Brown
sumber
3
"Anda menginginkan pesan kesalahan yang lebih baik dan dapat dibaca manusia" - itulah alasan terbaik untuk melakukannya, menurut saya. "Referensi objek tidak disetel ke instance objek" atau "Pengecualian referensi kosong" tidak pernah membantu seperti "Produk tidak dipakai".
pdr
@ pdr: Alasan lain untuk memeriksa adalah memastikan bahwa selalu terdeteksi.
Giorgio
13
"Mantan pengembang C", mungkin. "Mantan pengembang C ++" hanya jika mereka sendiri memulihkan pengembang C. Sebagai pengembang C ++ modern, saya akan sangat curiga tentang kode yang menggunakan pointer telanjang atau alokasi dinamis sembrono. Bahkan, saya akan tergoda untuk membalikkan argumen dan mengatakan bahwa C ++ tidak mengalami masalah yang dijelaskan OP karena variabel-variabelnya tidak memiliki properti null-ness khusus tambahan yang dimiliki Java dan C #.
Kerrek SB
7
Paragraf pertama Anda hanya setengah dari cerita: ya, jika Anda menggunakan referensi nol dalam C #, Anda akan mendapatkan pengecualian, sedangkan di C ++ Anda akan mendapatkan perilaku yang tidak terdefinisi. Tetapi dalam C # yang ditulis dengan baik, Anda terjebak dengan referensi yang jauh lebih dapat dibatalkan daripada di C ++ yang ditulis dengan baik.
James McNellis
Semakin baik enkapsulasi, semakin baik jawaban ini berlaku.
radarbob
15

Gunakan menegaskan untuk menguji dan menunjukkan pra / postconditions dan invarian untuk kode Anda.

Itu membuatnya jauh lebih mudah untuk memahami apa yang diharapkan oleh kode dan apa yang bisa diharapkan untuk ditangani.

Penegasan, IMO, adalah cek yang cocok karena:

  • efisien (biasanya tidak digunakan dalam rilis),
  • singkat (biasanya satu baris) dan
  • jelas (prasyarat dilanggar, ini adalah kesalahan pemrograman).

Saya pikir kode mendokumentasikan diri sendiri adalah penting, oleh karena itu memiliki cek itu baik. Pemrograman defensif, gagal-cepat, membuat pemeliharaan lebih mudah, yang biasanya menghabiskan banyak waktu.

Memperbarui

Mulai elaborasi Anda. Biasanya baik untuk memberikan pengguna akhir aplikasi yang bekerja dengan baik di bawah bug, yaitu menunjukkan sebanyak mungkin. Kekokohan itu baik, karena surga hanya tahu apa yang akan hancur pada saat kritis.

Namun, pengembang & penguji harus mengetahui bug ASAP, jadi Anda mungkin menginginkan kerangka kerja logging dengan kail yang dapat menampilkan peringatan kepada pengguna bahwa ada masalah. Lansiran mungkin harus ditampilkan kepada semua pengguna, tetapi mungkin dapat dirancang secara berbeda tergantung pada lingkungan runtime.

Macke
sumber
menegaskan tidak hadir dalam rilis adalah negatif besar bagi saya, rilis adalah waktu yang tepat Anda menginginkan informasi tambahan
jk.
1
Nah, jangan ragu menggunakan fungsi throw throws tegas yang aktif dalam rilis juga jika Anda membutuhkannya. Saya sudah sering memutar sendiri.
Macke
7

Gagal lebih awal, sering gagal. nulladalah salah satu nilai tak terduga terbaik yang dapat Anda peroleh, karena Anda dapat gagal dengan cepat segera setelah Anda mencoba menggunakannya. Nilai tak terduga lainnya tidak mudah dideteksi. Karena nullsecara otomatis gagal untuk Anda setiap kali Anda mencoba menggunakannya, saya akan mengatakan bahwa itu tidak memerlukan pemeriksaan eksplisit.

DeadMG
sumber
28
Saya harap Anda maksud: Gagal lebih awal, gagal cepat, tidak sering ...
empi
12
Masalahnya adalah bahwa tanpa pemeriksaan eksplisit, nilai nol kadang-kadang dapat merambat cukup jauh sebelum menyebabkan pengecualian, dan kemudian bisa sangat sulit untuk mengetahui dari mana asalnya.
Michael Borgwardt
@MichaelBorgwardt Bukankah itu maksud dari tumpukan jejak?
R0MANARMY
6
@ R0MANARMY: jejak jejak tidak membantu ketika nilai null disimpan ke beberapa bidang dan NullPointerException dilempar jauh kemudian.
Michael Borgwardt
@ R0MANARMY bahkan jika Anda dapat mempercayai nomor baris dalam jejak tumpukan (dalam banyak kasus Anda tidak bisa) beberapa baris berisi beberapa variabel yang mungkin nol.
Ohad Schneider
6
  • Memeriksa nol harus menjadi sifat kedua Anda

  • API Anda akan digunakan oleh orang lain dan harapan mereka cenderung berbeda dari Anda

  • Tes unit menyeluruh biasanya menyoroti kebutuhan untuk pemeriksaan referensi nol

  • Memeriksa nol tidak menyiratkan pernyataan bersyarat. Jika Anda khawatir bahwa pengecekan nol membuat kode Anda lebih mudah dibaca, maka Anda mungkin ingin mempertimbangkan kontrak kode .NET.

  • Pertimbangkan untuk menginstal ReSharper (atau yang serupa) untuk mencari kode Anda untuk pemeriksaan referensi nol yang hilang

CodeART
sumber
Menggunakan kontrak kode untuk prasyarat hanyalah pernyataan kondisional yang dimuliakan.
CVn
Ya, saya setuju, tetapi saya juga berpikir bahwa kode terlihat lebih bersih ketika menggunakan kontrak kode, yaitu keterbacaannya ditingkatkan.
CodeART
4

Saya akan mempertimbangkan pertanyaan dari sudut pandang semantik, yaitu tanyakan pada diri saya apa yang mewakili NULL pointer atau argumen referensi nol.

Jika fungsi atau metode foo () memiliki argumen x tipe T, x dapat berupa wajib atau opsional .

Di C ++ Anda bisa memberikan argumen wajib sebagai

  • Nilai, misalnya void foo (T x)
  • Referensi, mis. Void foo (T & x).

Dalam kedua kasus, Anda tidak memiliki masalah memeriksa pointer NULL. Jadi, dalam banyak kasus, Anda tidak perlu memeriksa pointer nol sama sekali untuk argumen wajib.

Jika Anda melewatkan argumen opsional , Anda dapat menggunakan pointer dan menggunakan nilai NULL untuk menunjukkan bahwa tidak ada nilai yang diberikan:

void foo(T* x)

Alternatifnya adalah menggunakan smart pointer like

void foo(shared_ptr<T> x)

Dalam kasus ini, Anda mungkin ingin memeriksa pointer dalam kode, karena itu membuat perbedaan untuk metode foo () apakah x berisi nilai atau tidak ada nilai sama sekali.

Kasus ketiga dan terakhir adalah Anda menggunakan pointer untuk argumen wajib . Dalam hal ini, memanggil foo (x) dengan x == NULL adalah kesalahan dan Anda harus memutuskan bagaimana menangani ini (sama dengan indeks di luar batas atau input yang tidak valid):

  1. Tanpa centang: jika ada bug, biarkan crash dan berharap bug ini muncul cukup awal (selama pengujian). Jika tidak (jika Anda gagal gagal cukup awal), harap itu tidak akan muncul dalam sistem produksi karena pengalaman pengguna adalah mereka melihat aplikasi mogok.
  2. Periksa semua argumen di awal metode dan laporkan argumen NULL yang tidak valid, mis. Lempar pengecualian dari foo () dan biarkan beberapa metode lain menanganinya. Mungkin hal terbaik untuk dilakukan adalah menunjukkan kepada pengguna jendela dialog yang mengatakan bahwa aplikasi telah mengalami kesalahan internal dan akan ditutup.

Terlepas dari cara gagal yang lebih baik (memberikan informasi lebih banyak kepada pengguna), keuntungan dari pendekatan kedua adalah kemungkinan besar bug akan ditemukan: program akan gagal setiap kali metode dipanggil dengan argumen yang salah sedangkan dengan pendekatan 1 bug hanya akan muncul jika pernyataan dereferencing pointer dijalankan (misalnya jika cabang kanan pernyataan if dimasukkan). Jadi pendekatan 2 menawarkan bentuk yang lebih kuat dari "kegagalan awal".

Di Jawa Anda memiliki lebih sedikit pilihan karena semua objek dilewatkan menggunakan referensi yang selalu dapat menjadi nol. Sekali lagi, jika null mewakili nilai NONE dari argumen opsional, maka memeriksa null akan (mungkin) menjadi bagian dari logika implementasi.

Jika nol adalah input yang tidak valid, maka Anda memiliki kesalahan dan saya akan menerapkan pertimbangan serupa untuk kasus pointer C ++. Karena di Java Anda dapat menangkap pengecualian pointer nol, pendekatan 1 di atas setara dengan pendekatan 2 jika metode foo () meringkas semua argumen input di semua jalur eksekusi (yang mungkin sering terjadi untuk metode kecil).

Meringkas

  • Baik di C ++ dan Java, memeriksa apakah argumen opsional adalah null adalah bagian dari logika program.
  • Dalam C ++, saya akan selalu memeriksa argumen wajib untuk memastikan bahwa pengecualian yang tepat dilemparkan sehingga program dapat menanganinya dengan cara yang kuat.
  • Di Java (atau C #), saya akan memeriksa argumen wajib jika ada jalur eksekusi yang tidak akan melempar untuk memiliki bentuk "gagal awal" yang lebih kuat.

SUNTING

Terima kasih kepada Stilgar untuk lebih jelasnya.

Dalam kasus Anda tampaknya Anda memiliki null sebagai hasil dari suatu metode. Sekali lagi, saya pikir Anda harus terlebih dahulu mengklarifikasi (dan memperbaiki) semantik metode Anda sebelum Anda dapat mengambil keputusan.

Jadi, Anda memiliki metode m1 () memanggil metode m2 () dan m2 () mengembalikan referensi (di Jawa) atau pointer (dalam C ++) ke beberapa objek.

Apa itu semantik m2 ()? Haruskah m2 () selalu mengembalikan hasil yang tidak nol? Jika demikian, maka hasil nol adalah kesalahan internal (dalam m2 ()). Jika Anda memeriksa nilai kembali di m1 () Anda dapat memiliki penanganan kesalahan yang lebih kuat. Jika tidak, Anda mungkin akan memiliki pengecualian, cepat atau lambat. Mungkin tidak. Berharap bahwa pengecualian dilemparkan selama pengujian dan tidak setelah penyebaran Pertanyaan : jika m2 () tidak boleh mengembalikan null, mengapa kembali null? Mungkin harusnya melemparkan pengecualian? m2 () mungkin buggy.

Alternatifnya adalah mengembalikan null adalah bagian dari semantik metode m2 (), yaitu memiliki hasil opsional, yang harus didokumentasikan dalam dokumentasi Javadoc dari metode tersebut. Dalam kasus ini, boleh saja memiliki nilai nol. Metode m1 () harus memeriksanya sebagai nilai lain: tidak ada kesalahan di sini, tetapi mungkin memeriksa apakah hasilnya nol hanyalah bagian dari logika program.

Giorgio
sumber
Dalam kasus itu bukan argumen yang nol. Kami membuat panggilan basis data untuk sesuatu yang diharapkan ada tetapi dalam praktiknya tidak ada. Pertanyaannya adalah apakah kita harus memeriksa bahwa setiap objek ada atau kita harus memeriksa hanya jika kita mengharapkannya hilang.
Stilgar
Jadi hasil yang dikembalikan oleh metode adalah referensi ke objek dan nol adalah nilai yang digunakan untuk mewakili hasil: TIDAK DITEMUKAN. Apakah saya mengerti dengan benar?
Giorgio
Saya telah memperbarui pertanyaan dengan detail tambahan.
Stilgar
3

Pendekatan umum saya adalah jangan pernah menguji kondisi kesalahan yang Anda tidak tahu bagaimana mengatasinya . Maka pertanyaan yang perlu dijawab dalam setiap contoh spesifik menjadi: dapatkah Anda melakukan sesuatu yang masuk akal di hadapan nilai yang tidak terduga null?

Jika Anda dapat melanjutkan dengan aman dengan mengganti nilai lain (koleksi kosong, katakanlah, atau nilai default atau contoh), maka dengan segala cara melakukannya. Contoh @ Shahbaz dari World of Warcraft menggantikan satu gambar dengan gambar lain termasuk dalam kategori itu; gambar itu sendiri kemungkinan hanya hiasan, dan tidak memiliki dampak fungsional . (Dalam build debug, saya akan menggunakan warna menjerit untuk menarik perhatian pada fakta bahwa substitusi telah terjadi, tetapi itu sangat tergantung pada sifat aplikasi.) Namun, detail log tentang kesalahan, sehingga Anda tahu itu terjadi; jika tidak, Anda akan menyembunyikan kesalahan yang mungkin ingin Anda ketahui. Menyembunyikannya dari pengguna adalah satu hal (sekali lagi, dengan asumsi pemrosesan dapat dilanjutkan dengan aman); menyembunyikannya dari pengembang adalah hal lain.

Jika Anda tidak dapat melanjutkan dengan aman di hadapan nilai nol, maka gagal dengan kesalahan yang masuk akal; secara umum, saya lebih memilih untuk gagal sesegera mungkin ketika jelas bahwa operasi tidak dapat berhasil, yang menyiratkan memeriksa prasyarat. Ada kalanya Anda tidak ingin langsung gagal, tetapi menunda kegagalan selama mungkin; pertimbangan ini muncul misalnya dalam aplikasi yang berhubungan dengan kriptografi, di mana serangan saluran samping mungkin mengungkapkan informasi yang tidak ingin Anda ketahui. Pada akhirnya, biarkan kesalahan muncul hingga beberapa penangan kesalahan umum, yang pada gilirannya mencatat rincian yang relevan dan menampilkan pesan kesalahan yang bagus (betapapun bagusnya) kepada pengguna.

Perlu juga dicatat bahwa respons yang tepat mungkin sangat berbeda antara pengujian / QA / debug builds, dan build produksi / rilis. Dalam debug build, saya akan gagal sejak awal dan sulit dengan pesan kesalahan yang sangat rinci, untuk membuatnya sangat jelas bahwa ada masalah dan memungkinkan post-mortem untuk menentukan apa yang perlu dilakukan untuk memperbaikinya. Membangun produksi, ketika dihadapkan dengan kesalahan yang dapat dipulihkan dengan aman , mungkin harus mendukung pemulihan.

sebuah CVn
sumber
Tentu saja ada kesalahan penangan umum atas rantai. Masalah dengan log adalah bahwa mungkin tidak ada orang yang memeriksanya untuk waktu yang sangat lama.
Stilgar
@ Stilgar, jika tidak ada yang memeriksa log, maka itu tidak masalah dengan ada atau tidak adanya cek nol.
CVn
2
Ada masalah. Pengguna akhir mungkin tidak melihat bahwa sistem tidak berfungsi dengan benar untuk waktu yang lama jika ada cek nol yang hanya menyembunyikan kesalahan dan menulisnya ke log.
Stilgar
@ Stilgar, itu sepenuhnya tergantung pada sifat kesalahan. Seperti yang saya katakan di posting, hanya jika Anda dapat melanjutkan dengan aman seandainya null yang tak terduga diganti / diabaikan. Menggunakan satu gambar daripada yang lain dalam rilis rilis (dalam debug build, gagal sedini dan sekuat mungkin sehingga masalah menjadi jelas) adalah binatang yang sangat berbeda daripada mengembalikan hasil yang tidak valid untuk perhitungan, misalnya. (Jawaban yang diedit untuk menyertakan itu.)
CVn
1
Saya akan gagal dengan cepat dan awal bahkan dalam produksi kecuali Anda entah bagaimana dapat login dan melaporkan kesalahan (tanpa terlalu bergantung pada pengguna yang cerdas). Menyembunyikan bug yang Anda gunakan dari pengguna adalah satu hal - menyembunyikannya dari diri Anda sendiri adalah berbahaya. Juga memungkinkan pengguna untuk hidup dengan bug halus dapat mengikis kepercayaan mereka pada aplikasi Anda. Terkadang lebih baik menggigit peluru dan memberi tahu mereka ada bug dan Anda memperbaikinya dengan cepat.
Merlyn Morgan-Graham
2

Tentu NULLtidak diharapkan , tetapi selalu ada bug!

Saya percaya Anda harus memeriksa NULLapakah Anda tidak mengharapkannya atau tidak. Biarkan saya memberi Anda contoh (meskipun mereka tidak di Jawa).

  • Dalam World of Warcraft, karena bug gambar salah satu pengembang muncul di sebuah kubus untuk banyak objek. Bayangkan jika mesin grafis tidak mengharapkan NULL pointer, akan ada crash, sementara itu berubah menjadi pengalaman yang tidak begitu fatal (bahkan lucu) bagi pengguna. Paling tidak, Anda tidak akan kehilangan koneksi atau tiba-tiba menyimpan poin.

    Ini adalah contoh di mana memeriksa NULL mencegah kerusakan dan kasus ditangani secara diam-diam.

  • Bayangkan program teller bank. Antarmuka melakukan pengecekan dan mengirimkan data input ke bagian yang mendasarinya untuk melakukan transfer uang. Jika bagian inti mengharapkan untuk tidak menerima NULL, maka bug di antarmuka dapat menyebabkan masalah dalam transaksi, mungkin sejumlah uang di tengah hilang (atau lebih buruk, digandakan).

    Dengan "masalah" Maksudku crash (seperti dalam crash crash (katakanlah program ini ditulis dalam C ++), bukan pengecualian null pointer). Dalam hal ini, transaksi harus dibatalkan jika NULL ditemukan (yang tentu saja tidak akan mungkin jika Anda tidak memeriksa variabel terhadap NULL!)

Saya yakin Anda mendapatkan idenya.

Memeriksa validitas argumen untuk fungsi Anda tidak mengacaukan kode Anda, karena itu adalah pasangan memeriksa di bagian atas fungsi Anda (tidak menyebar di tengah) dan sangat meningkatkan kekokohan.

Jika Anda harus memilih antara "program memberi tahu pengguna bahwa ia telah gagal dan dengan anggun mati atau gulung balik ke status yang konsisten yang mungkin membuat log kesalahan" dan "Pelanggaran Akses", tidakkah Anda memilih yang pertama? Itu berarti setiap fungsi harus dipersiapkan untuk dipanggil oleh kode buggy daripada mengharapkan semuanya benar, hanya karena bug selalu ada.

Shahbaz
sumber
Contoh kedua Anda membantah pendapat Anda. Dalam transaksi bank jika terjadi kesalahan, transaksi harus dibatalkan. Justru ketika Anda menutupi kesalahan bahwa uang dapat hilang atau digandakan. Memeriksa null akan seperti "Pelanggan mengirim uang nol ... baik saya hanya akan mengirim $ 10 (setara dengan gambar pengembang)"
Stilgar
@ Stilgar, Sebaliknya! Memeriksa NULL akan dibatalkan dengan pesan. Tidak memeriksa NULL akan macet . Sekarang crash di awal transaksi mungkin tidak buruk, tetapi di tengah bisa menjadi bencana. (Saya tidak mengatakan transfer bank harus menangani kesalahan seperti permainan! Hanya fakta bahwa itu harus menanganinya)
Shahbaz
2
Inti dari "transaksi" adalah bahwa itu tidak dapat setengah selesai :) Jika ada kesalahan maka itu dibatalkan.
Stilgar
Ini adalah nasihat yang mengerikan untuk apa pun di mana semantik transaksional sebenarnya diperlukan. Anda harus membuat semua transaksi Anda menjadi atom dan idempoten, jika tidak, setiap kesalahan yang Anda miliki akan menghilangkan jaminan sumber daya yang hilang atau digandakan (uang). Jika Anda harus berurusan dengan operasi non-atomik atau non-idempoten, Anda harus membungkusnya dengan sangat hati-hati dalam lapisan yang menjamin perilaku atom dan idempoten (bahkan jika Anda harus menulis sendiri layer-layer itu). Selalu berpikir: apa yang terjadi jika meteor mengenai server web saat transaksi ini terjadi?
Merlyn Morgan-Graham
1
@ MerlynMorgan-Graham, saya melakukannya. Harapan yang membersihkan kebingungan.
Shahbaz
2

Seperti yang ditunjukkan oleh jawaban yang lain. Jika Anda tidak berharap NULL, jelaskan dengan melempar ArgumentNullException. Dalam pandangan saya, ketika Anda men-debug proyek itu membantu Anda untuk menemukan kesalahan dalam logika program Anda lebih cepat.

Jadi, Anda akan merilis perangkat lunak Anda, jika Anda mengembalikan NullRefrencescek itu, Anda tidak kehilangan apa pun pada logika perangkat lunak Anda, itu hanya membuat dua kali lipat memastikan bahwa bug yang parah tidak akan muncul.

Poin lain adalah bahwa jika NULLmemeriksa parameter fungsi, maka Anda dapat memutuskan apakah fungsi itu tempat yang tepat untuk melakukannya atau tidak; jika tidak, temukan semua referensi ke fungsi dan sebelum mengirimkan parameter ke fungsi, tinjau skenario yang mungkin mengarah pada referensi nol.

Ahmad
sumber
1

Setiap nulldalam sistem Anda adalah bom waktu yang menunggu untuk ditemukan. Periksa nullbatas di mana kode Anda berinteraksi dengan kode yang tidak Anda kendalikan, dan larang di nulldalam kode Anda sendiri. Ini membebaskan Anda dari masalah tanpa naif mempercayai kode asing. Gunakan pengecualian atau semacam Maybe/ Nothingsebagai gantinya.

Doval
sumber
0

Sementara pengecualian null pointer pada akhirnya akan dibuang, akan sering dibuang jauh dari titik di mana Anda mendapat null pointer. Bantulah diri Anda sendiri dan persingkat waktu yang diperlukan untuk memperbaiki bug dengan setidaknya mencatat fakta bahwa Anda mendapatkan pointer nol sesegera mungkin.

Bahkan jika Anda tidak tahu apa yang harus dilakukan sekarang, masuk log acara akan menghemat waktu Anda nanti. Dan mungkin orang yang mendapatkan tiket akan dapat menggunakan waktu yang dihemat untuk mengetahui respon yang tepat saat itu.

Jon Strayer
sumber
-1

Karena Fail early, fail fastseperti yang dinyatakan oleh @DeadMG (@empi) tidak mungkin karena aplikasi web harus bekerja dengan baik di bawah bug Anda harus menegakkan aturan

tidak terkecuali menangkap tanpa login

jadi Anda sebagai pengembang menyadari masalah potensial dengan memeriksa log.

jika Anda menggunakan kerangka kerja logging seperti log4net atau log4j Anda dapat mengatur output logging khusus tambahan (alias appender) yang mengirimi Anda kesalahan dan kesalahan fatal. jadi Anda tetap mendapat informasi .

[memperbarui]

jika pengguna akhir halaman tidak menyadari kesalahan Anda dapat mengatur appender yang mengirimi Anda kesalahan dan kesalahan fatal Anda tetap mendapat informasi.

jika ok bahwa pengguna akhir halaman melihat pesan kesalahan cryptic Anda dapat mengatur appender yang menempatkan errorlog untuk pengolahan halaman di akhir halaman.

Tidak masalah bagaimana Anda menggunakan pencatatan jika Anda menelan pengecualian, program melanjutkan dengan data yang masuk akal dan menelan tidak lagi diam.

k3b
sumber
1
Pertanyaannya adalah apa yang terjadi pada halaman?
Stilgar
Bagaimana kita tahu bahwa data masuk akal dan sistem dalam keadaan konsisten. Bagaimanapun kesalahan itu tidak terduga sehingga kita tidak tahu apa dampaknya.
Stilgar
"Sejak Gagal awal, gagal cepat seperti yang dinyatakan oleh @DeadMG (@empi) tidak mungkin karena aplikasi web harus bekerja dengan baik di bawah bug Anda harus menegakkan aturan": Seseorang selalu dapat menangkap semua pengecualian (catch (Throwable t)) dan redirect ke beberapa halaman kesalahan. Bukankah ini mungkin?
Giorgio
-1

Sudah ada jawaban yang bagus untuk pertanyaan ini, mungkin satu tambahan untuk mereka: Saya lebih suka pernyataan dalam kode saya. Jika kode Anda hanya dapat bekerja dengan objek yang valid, tetapi gagal dengan nol, Anda harus menyatakan pada nilai nol.

Pernyataan dalam situasi seperti ini akan membiarkan unit dan / atau tes integrasi gagal dengan pesan yang tepat, sehingga Anda dapat menyelesaikan masalah dengan cukup cepat sebelum produksi. Jika kesalahan seperti itu menyelinap melalui tes dan pergi ke produksi (di mana pernyataan harus dinonaktifkan) Anda akan menerima NpEx, dan bug yang parah, tetapi itu lebih baik, daripada beberapa bug kecil yang jauh lebih kabur dan muncul di tempat lain di kode, dan akan lebih mahal untuk diperbaiki.

Lebih jauh lagi jika seseorang harus bekerja dengan pernyataan kode Anda memberitahukan kepadanya tentang asumsi Anda yang Anda buat selama mendesain / menulis kode Anda (dalam hal ini: Hei Bung, objek ini harus disajikan!), Yang membuatnya lebih mudah untuk dipertahankan.

Mendapatkan
sumber
Bisakah Anda menulis sesuatu untuk memilih? Saya sangat menghargai diskusi. Terima kasih
GeT
Saya tidak memilih, dan saya setuju dengan ide umum Anda. Bahasa ini membutuhkan beberapa pekerjaan. Pernyataan Anda menentukan kontrak API Anda, dan Anda gagal lebih awal / gagal cepat jika kontrak tersebut dilanggar. Melakukan hal ini di kulit aplikasi Anda akan membuat pengguna kode Anda tahu mereka melakukan sesuatu yang salah dalam kode mereka. Jika mereka melihat tumpukan melacak di ceruk kode Anda, mereka mungkin akan berpikir kode Anda bermasalah - dan Anda mungkin berpikir demikian sampai Anda mendapatkan repro yang solid dan men-debug-nya.
Merlyn Morgan-Graham
-1

Saya biasanya memeriksa nol tetapi saya "menangani" nol yang tidak terduga dengan melemparkan pengecualian yang memberikan sedikit lebih detail tentang di mana nol terjadi.

Sunting: Jika Anda mendapatkan nol ketika Anda tidak mengharapkan ada sesuatu yang salah - program harus mati.

Loren Pechtel
sumber
-1

Itu tergantung pada implementasi bahasa pengecualian. Pengecualian memungkinkan Anda mengambil tindakan untuk mencegah kerusakan data atau melepaskan sumber daya secara bersih jika sistem Anda memasuki keadaan atau kondisi yang tidak terduga. Ini berbeda dari penanganan kesalahan yang merupakan kondisi yang dapat Anda antisipasi. Filosofi saya adalah bahwa Anda harus memiliki penanganan perkecil mungkin. Itu kebisingan. Bisakah metode Anda melakukan sesuatu yang masuk akal dengan menangani pengecualian? Bisakah itu pulih? Bisakah itu memperbaiki atau mencegah kerusakan lebih lanjut? Jika Anda hanya akan menangkap pengecualian dan kemudian kembali dari metode atau gagal dengan cara lain yang tidak berbahaya maka benar-benar tidak ada gunanya menangkap pengecualian. Anda hanya akan menambahkan noise dan kompleksitas pada metode Anda, dan Anda mungkin menyembunyikan sumber kesalahan dalam desain Anda. Dalam hal ini saya akan membiarkan kesalahan naik dan ditangkap oleh lingkaran luar. Jika Anda dapat melakukan sesuatu seperti memperbaiki objek atau menandainya rusak atau melepaskan beberapa sumber daya penting seperti file kunci atau dengan menutup soket dengan bersih maka ini adalah penggunaan pengecualian yang baik. Jika Anda benar-benar mengharapkan NULL untuk tampil sering sebagai tepi kasus yang valid maka Anda harus menangani ini dalam aliran logika normal dengan pernyataan if-the-else atau switch-case atau apa pun, tidak dengan penanganan pengecualian. Misalnya, bidang yang dinonaktifkan dalam formulir dapat diatur ke NULL, yang berbeda dari string kosong yang mewakili bidang yang dikosongkan. Ini adalah area di mana Anda harus menggunakan penilaian yang baik dan akal sehat. Saya belum pernah mendengar aturan praktis yang bagus tentang bagaimana menangani masalah ini di setiap situasi. Jika Anda dapat melakukan sesuatu seperti memperbaiki objek atau menandainya rusak atau melepaskan beberapa sumber daya penting seperti file kunci atau dengan menutup soket dengan bersih maka ini adalah penggunaan pengecualian yang baik. Jika Anda benar-benar mengharapkan NULL untuk tampil sering sebagai tepi kasus yang valid maka Anda harus menangani ini dalam aliran logika normal dengan pernyataan if-the-else atau switch-case atau apa pun, tidak dengan penanganan pengecualian. Misalnya, bidang yang dinonaktifkan dalam formulir dapat diatur ke NULL, yang berbeda dari string kosong yang mewakili bidang yang dikosongkan. Ini adalah area di mana Anda harus menggunakan penilaian yang baik dan akal sehat. Saya belum pernah mendengar aturan praktis yang bagus tentang bagaimana menangani masalah ini di setiap situasi. Jika Anda dapat melakukan sesuatu seperti memperbaiki objek atau menandainya rusak atau melepaskan beberapa sumber daya penting seperti file kunci atau dengan menutup soket dengan bersih maka ini adalah penggunaan pengecualian yang baik. Jika Anda benar-benar mengharapkan NULL untuk tampil sering sebagai tepi kasus yang valid maka Anda harus menangani ini dalam aliran logika normal dengan pernyataan if-the-else atau switch-case atau apa pun, tidak dengan penanganan pengecualian. Misalnya, bidang yang dinonaktifkan dalam formulir dapat diatur ke NULL, yang berbeda dari string kosong yang mewakili bidang yang dikosongkan. Ini adalah area di mana Anda harus menggunakan penilaian yang baik dan akal sehat. Saya belum pernah mendengar aturan praktis yang bagus tentang bagaimana menangani masalah ini di setiap situasi.

Java gagal dalam implementasi penanganan pengecualiannya dengan "pengecualian yang diperiksa" di mana setiap pengecualian yang mungkin dimunculkan oleh objek yang digunakan dalam suatu metode harus ditangkap atau dideklarasikan dalam klausa "throws" dari metode tersebut. Ini kebalikan dari C ++ dan Python di mana Anda dapat memilih untuk menangani pengecualian yang Anda inginkan. Di Jawa, jika Anda memodifikasi isi metode untuk memasukkan panggilan yang dapat menimbulkan pengecualian, Anda dihadapkan dengan pilihan menambahkan penanganan eksplisit untuk pengecualian yang mungkin tidak Anda pedulikan sehingga menambah kebisingan dan kerumitan kode Anda, atau Anda harus tambahkan pengecualian itu ke klausa "throws" dari metode yang Anda modifikasi yang tidak hanya menambah noise dan kekacauan tetapi juga mengubah tanda tangan metode Anda. Anda kemudian harus pergi kode modifikasi di mana pun metode Anda digunakan untuk menangani pengecualian baru metode Anda sekarang dapat meningkatkan atau menambahkan pengecualian pada klausa "melempar" metode tersebut sehingga memicu efek domino dari perubahan kode. "Persyaratan Tangkapan atau Tentukan" harus merupakan salah satu aspek yang paling menjengkelkan di Jawa. Pengecualian pengecualian untuk RuntimeExceptions dan rasionalisasi Java resmi untuk kesalahan desain ini adalah lemah.

Paragraf terakhir itu tidak ada hubungannya dengan menjawab pertanyaan Anda. Saya hanya membenci Jawa.

- Nuh

Noah Spurrier
sumber
posting ini agak sulit dibaca (dinding teks). Maukah Anda mengeditnya menjadi bentuk yang lebih baik?
nyamuk