Saya menggunakan pengecualian untuk menangkap masalah lebih awal. Sebagai contoh:
public int getAverageAge(Person p1, Person p2){
if(p1 == null || p2 == null)
throw new IllegalArgumentException("One or more of input persons is null").
return (p1.getAge() + p2.getAge()) / 2;
}
Program saya seharusnya tidak pernah lulus null
dalam fungsi ini. Saya tidak pernah menginginkannya. Namun seperti yang kita semua tahu, hal-hal yang tidak diinginkan terjadi dalam pemrograman.
Melontarkan pengecualian jika masalah ini terjadi, memungkinkan saya mengenali dan memperbaikinya, sebelum menyebabkan lebih banyak masalah di tempat lain dalam program. Pengecualian menghentikan program dan memberi tahu saya "hal-hal buruk terjadi di sini, perbaiki". Alih-alih ini null
bergerak di sekitar program yang menyebabkan masalah di tempat lain.
Sekarang, Anda benar, dalam hal ini null
hanya akan menyebabkan NullPointerException
langsung, jadi itu mungkin bukan contoh terbaik.
Tetapi pertimbangkan metode seperti ini misalnya:
public void registerPerson(Person person){
persons.add(person);
notifyRegisterObservers(person); // sends the person object to all kinds of objects.
}
Dalam hal ini, a null
sebagai parameter akan dilewatkan di sekitar program, dan mungkin menyebabkan kesalahan jauh di kemudian hari, yang akan sulit dilacak kembali ke asalnya.
Mengubah fungsi seperti ini:
public void registerPerson(Person person){
if(person == null) throw new IllegalArgumentException("Input person is null.");
persons.add(person);
notifyRegisterObservers(person); // sends the person object to all kinds of objects.
}
Memungkinkan saya untuk menemukan masalah jauh sebelum menyebabkan kesalahan aneh di tempat lain.
Juga, null
referensi sebagai parameter hanyalah sebuah contoh. Bisa jadi ada banyak jenis masalah, dari argumen yang tidak valid hingga yang lainnya. Itu selalu lebih baik untuk menemukan mereka lebih awal.
Jadi pertanyaan saya sederhana: apakah ini praktik yang baik? Apakah saya menggunakan pengecualian sebagai alat pencegah masalah baik? Apakah ini aplikasi pengecualian yang sah atau bermasalah?
sumber
Jawaban:
Ya, "gagal sejak awal" adalah prinsip yang sangat baik, dan ini hanyalah salah satu cara yang mungkin untuk menerapkannya. Dan dalam metode yang harus mengembalikan nilai tertentu, tidak ada banyak hal lain yang dapat Anda lakukan untuk gagal dengan sengaja - itu bisa melempar pengecualian, atau memicu pernyataan. Pengecualian seharusnya menandakan kondisi 'luar biasa', dan mendeteksi kesalahan pemrograman tentu saja luar biasa.
sumber
Ya, melempar pengecualian adalah ide yang bagus. Lempar mereka lebih awal, lempar mereka sering, lempar mereka dengan penuh semangat.
Saya tahu ada debat "pengecualian vs pernyataan", dengan beberapa jenis perilaku luar biasa (khususnya, yang dianggap mencerminkan kesalahan pemrograman) yang ditangani oleh pernyataan yang dapat "dikompilasi" untuk runtime daripada debugging / pengujian build. Tetapi jumlah kinerja yang dikonsumsi dalam beberapa pemeriksaan ketelitian tambahan minimal pada perangkat keras modern, dan biaya tambahan apa pun jauh melebihi nilai memiliki hasil yang benar dan tidak rusak. Saya tidak pernah benar-benar bertemu basis kode aplikasi yang saya ingin (sebagian besar) pemeriksaan dihapus saat runtime.
Saya tergoda untuk mengatakan saya tidak ingin banyak pemeriksaan tambahan dan persyaratan dalam loop ketat kode numerik intensif ... tapi di situlah banyak kesalahan numerik dihasilkan, dan jika tidak ada, akan menyebar ke luar ke efek semua hasil. Bahkan di sana, cek layak dilakukan. Bahkan, beberapa algoritma numerik terbaik dan paling efisien didasarkan pada evaluasi kesalahan.
Satu tempat terakhir untuk menjadi sangat sadar akan kode tambahan adalah kode yang sangat sensitif terhadap latensi, di mana persyaratan tambahan dapat menyebabkan saluran pipa berhenti. Jadi, di tengah sistem operasi, DBMS, dan kernel middleware lainnya, dan komunikasi / protokol penanganan tingkat rendah. Tetapi sekali lagi, itu adalah beberapa kesalahan tempat yang paling mungkin diamati, dan pengaruhnya (keamanan, kebenaran, dan integritas data) paling merusak.
Satu perbaikan yang saya temukan adalah tidak hanya melemparkan pengecualian tingkat dasar.
IllegalArgumentException
itu bagus, tapi itu bisa datang dari mana saja. Tidak perlu banyak dalam kebanyakan bahasa untuk menambahkan pengecualian khusus. Untuk modul penanganan orang Anda, katakan:Lalu ketika seseorang melihat a
PersonArgumentException
Jelas dari mana asalnya. Ada tindakan penyeimbangan tentang berapa banyak pengecualian khusus yang ingin Anda tambahkan, karena Anda tidak ingin melipatgandakan entitas yang tidak perlu (Occam's Razor). Seringkali hanya beberapa pengecualian khusus yang cukup untuk memberi sinyal "modul ini tidak mendapatkan data yang tepat!" atau "modul ini tidak dapat melakukan apa yang seharusnya dilakukan!" dengan cara yang spesifik dan khusus, tetapi tidak terlalu tepat sehingga Anda harus menerapkan kembali seluruh hierarki pengecualian. Saya sering datang ke sekelompok kecil pengecualian khusus dengan memulai dengan pengecualian stok, kemudian memindai kode dan menyadari bahwa "N tempat ini meningkatkan pengecualian stok, tetapi mereka bermuara pada gagasan tingkat yang lebih tinggi bahwa mereka tidak mendapatkan data mereka membutuhkan;sumber
I've never actually met an application codebase for which I'd want (most) checks removed at runtime.
Maka Anda belum melakukan kode yang kritis terhadap kinerja. Saya sedang mengerjakan sesuatu saat ini yang tidak 37M ops per detik dengan pernyataan yang disusun dan 42M tanpa mereka. Pernyataan tidak ada di sana memvalidasi input luar, mereka ada di sana untuk memastikan kode itu benar. Pelanggan saya lebih dari senang menerima kenaikan 13% begitu saya puas bahwa barang saya tidak rusak.PersonArgumentException
tidak sejelasIllegalArgumentException
. Yang terakhir secara universal diketahui dilemparkan ketika argumen ilegal disahkan. Saya benar-benar berharap yang pertama dilempar jikaPerson
ada yang tidak valid untuk panggilan (Mirip denganInvalidOperationException
di C #).Gagal sesegera mungkin hebat ketika men-debug aplikasi. Saya ingat kesalahan segmentasi tertentu dalam program C ++ lama: tempat di mana bug terdeteksi tidak ada hubungannya dengan tempat di mana ia diperkenalkan (null pointer dengan senang hati dipindahkan dari satu tempat ke tempat lain dalam memori sebelum akhirnya menyebabkan masalah ). Jejak tumpukan tidak dapat membantu Anda dalam kasus tersebut.
Jadi ya, pemrograman defensif adalah pendekatan yang sangat efektif untuk mendeteksi dan memperbaiki bug dengan cepat. Di sisi lain, bisa berlebihan, terutama dengan referensi nol.
Dalam kasus spesifik Anda, misalnya: jika salah satu referensi adalah nol, maka
NullReferenceException
akan dilemparkan pada pernyataan berikutnya, ketika mencoba untuk mendapatkan usia satu orang. Anda tidak benar-benar perlu memeriksa hal-hal sendiri di sini: biarkan sistem yang mendasarinya menangkap kesalahan itu dan melemparkan pengecualian, itu sebabnya mereka ada .Untuk contoh yang lebih realistis, Anda dapat menggunakan
assert
pernyataan, yang:Lebih pendek untuk menulis dan membaca:
Dirancang khusus untuk pendekatan Anda. Di dunia di mana Anda memiliki kedua pernyataan dan pengecualian, Anda dapat membedakannya sebagai berikut:
Jadi, memaparkan asumsi Anda tentang input dan / atau status aplikasi Anda dengan pernyataan, biarkan pengembang berikutnya sedikit lebih memahami tujuan kode Anda.
Penganalisa statis (misalnya kompiler) mungkin lebih bahagia juga.
Akhirnya, pernyataan dapat dihapus dari aplikasi yang digunakan menggunakan satu sakelar. Tapi secara umum, jangan berharap untuk meningkatkan efisiensi dengan itu: pemeriksaan pernyataan saat runtime diabaikan.
sumber
Sejauh yang saya tahu programmer berbeda lebih suka satu solusi atau yang lain.
Solusi pertama biasanya lebih disukai karena lebih ringkas, khususnya, Anda tidak perlu memeriksa kondisi yang sama berulang-ulang dalam fungsi yang berbeda.
Saya menemukan solusi kedua, misalnya
lebih solid, karena
registerPerson()
dipanggil, bukan ketika pengecualian pointer nol dilemparkan ke suatu tempat di tumpukan panggilan. Debugging menjadi jauh lebih mudah: kita semua tahu sejauh mana nilai yang tidak valid dapat berjalan melalui kode sebelum memanifestasikan dirinya sebagai bug.registerPerson()
tidak membuat asumsi tentang fungsi mana yang akan berakhir menggunakanperson
argumen dan bagaimana mereka akan menggunakannya: keputusan yangnull
merupakan kesalahan diambil dan diimplementasikan secara lokal.Jadi, terutama jika kodenya agak rumit, saya cenderung memilih pendekatan kedua ini.
sumber
Secara umum, ya, itu ide yang baik untuk "gagal lebih awal". Namun, dalam contoh spesifik Anda, eksplisit
IllegalArgumentException
tidak memberikan peningkatan yang signifikan atas aNullReferenceException
- karena kedua objek yang dioperasikan dikirimkan sebagai argumen untuk fungsi tersebut.Tetapi mari kita lihat contoh yang sedikit berbeda.
Jika tidak ada argumen yang memeriksa konstruktor, Anda akan mendapatkan
NullReferenceException
ketika meneleponCalculate
.Tetapi bagian kode yang rusak bukanlah
Calculate
fungsi, atau konsumenCalculate
fungsi. Sepotong kode yang rusak adalah kode yang mencoba untuk membangunPersonCalculator
dengan nolPerson
- jadi di situlah kita ingin pengecualian terjadi.Jika kami menghapus pemeriksaan argumen eksplisit, Anda harus mencari tahu mengapa a
NullReferenceException
terjadi ketikaCalculate
dipanggil. Dan melacak mengapa objek itu dibangun dengannull
seseorang bisa menjadi rumit, terutama jika kode membangun kalkulator tidak dekat dengan kode yang sebenarnya memanggilCalculate
fungsi.sumber
Tidak dalam contoh yang Anda berikan.
Seperti yang Anda katakan, melempar secara eksplisit tidak membuat Anda banyak ketika Anda akan mendapatkan pengecualian segera sesudahnya. Banyak yang akan berpendapat bahwa memiliki pengecualian eksplisit dengan pesan yang baik lebih baik, meskipun saya tidak setuju. Dalam skenario pra-dirilis, jejak tumpukan cukup baik. Dalam skenario pasca-rilis, situs panggilan sering dapat memberikan pesan yang lebih baik daripada di dalam fungsi.
Bentuk kedua adalah memberikan terlalu banyak info ke fungsi. Fungsi itu seharusnya tidak perlu tahu bahwa fungsi-fungsi lain akan menghasilkan input nol. Bahkan jika mereka melempar input nol sekarang , menjadi sangat merepotkan jika harus berhenti menjadi kasus karena pemeriksaan nol tersebar di seluruh kode.
Tetapi secara umum, Anda harus melempar lebih awal begitu Anda menentukan bahwa ada sesuatu yang salah (sambil menghormati KERING). Ini mungkin bukan contoh yang bagus untuk itu.
sumber
Dalam fungsi contoh Anda, saya lebih suka Anda tidak melakukan pemeriksaan dan hanya mengizinkan
NullReferenceException
terjadi.Untuk satu, tidak masuk akal untuk memberikan nol di sana, jadi saya akan mencari tahu masalah segera berdasarkan pada melempar a
NullReferenceException
.Dua, adalah bahwa jika setiap fungsi melempar pengecualian yang sedikit berbeda berdasarkan pada jenis input yang jelas salah apa yang telah disediakan, maka segera Anda akan memiliki fungsi yang mungkin melempar 18 jenis pengecualian yang berbeda, dan segera Anda mendapati diri Anda mengatakan itu terlalu banyak pekerjaan yang harus dihadapi, dan hanya menekan semua pengecualian saja.
Tidak ada yang dapat Anda lakukan untuk membantu situasi kesalahan desain-waktu pada fungsi Anda, jadi biarkan kegagalan terjadi tanpa mengubahnya.
sumber
RuntimeException
tidak selalu merupakan asumsi yang valid.