Mengapa kita tidak membuang pengecualian ini?

111

Saya menemukan halaman MSDN ini yang menyatakan:

Jangan melempar Exception , SystemException , NullReferenceException , atau IndexOutOfRangeException dengan sengaja dari kode sumber Anda sendiri.

Sayangnya, tidak repot-repot menjelaskan alasannya. Saya bisa menebak alasannya tetapi saya berharap seseorang yang lebih berwibawa pada subjek dapat menawarkan wawasan mereka.

Dua yang pertama masuk akal, tetapi dua yang terakhir tampak seperti yang ingin Anda gunakan (dan sebenarnya, saya punya).

Selanjutnya, apakah ini satu-satunya pengecualian yang harus dihindari? Jika ada yang lain, apakah mereka dan mengapa mereka juga harus dihindari?

DonBoitnott
sumber
22
Dari msdn.microsoft.com/en-us/library/ms182338.aspx : Jika Anda melempar tipe pengecualian umum, seperti Exception atau SystemException di pustaka atau kerangka kerja, ini memaksa konsumen untuk menangkap semua pengecualian, termasuk pengecualian yang tidak diketahui yang mereka lakukan tidak tahu bagaimana menanganinya.
Henrik
3
Mengapa Anda melempar NullReferenceException?
Rik
5
@Rik: mirip dengan NullArgumentExceptionbeberapa orang yang mungkin membingungkan keduanya.
Muslim Ben Dhaou
2
@ Metode ekstensi Rik, sesuai jawaban saya
Marc Gravell
3
Satu lagi yang tidak boleh Anda lempar adalahApplicationException
Matthew Watson

Jawaban:

98

Exceptionadalah tipe dasar untuk semua pengecualian, dan karenanya sangat tidak spesifik. Anda tidak boleh membuang pengecualian ini karena tidak mengandung informasi yang berguna. Penangkapan kode panggilan untuk pengecualian tidak dapat menghilangkan ambiguitas pengecualian yang sengaja dilemparkan (dari logika Anda) dari pengecualian sistem lain yang sama sekali tidak diinginkan dan menunjukkan kesalahan nyata.

Alasan yang sama juga berlaku untuk SystemException. Jika Anda melihat daftar jenis turunan, Anda dapat melihat sejumlah besar pengecualian lain dengan semantik yang sangat berbeda.

NullReferenceExceptiondan IndexOutOfRangeExceptiondari jenis yang berbeda. Sekarang ini adalah pengecualian yang sangat spesifik, jadi membuangnya bisa saja tidak masalah. Namun, Anda tetap tidak ingin membuang ini, karena biasanya itu berarti ada beberapa kesalahan aktual dalam logika Anda. Misalnya pengecualian referensi null berarti Anda mencoba mengakses anggota objek yang null. Jika itu adalah kemungkinan dalam kode Anda, Anda harus selalu memeriksa secara eksplisit nulldan melemparkan pengecualian yang lebih berguna sebagai gantinya (misalnya ArgumentNullException). Demikian pula, IndexOutOfRangeExceptions terjadi ketika Anda mengakses indeks yang tidak valid (pada array — bukan daftar). Anda harus selalu memastikan bahwa Anda tidak melakukan itu di tempat pertama dan memeriksa batas-batas misalnya array terlebih dahulu.

Ada beberapa pengecualian lain seperti keduanya, misalnya InvalidCastExceptionatau DivideByZeroException, yang diberikan untuk kesalahan tertentu dalam kode Anda dan biasanya berarti Anda melakukan sesuatu yang salah atau Anda tidak memeriksa beberapa nilai yang tidak valid terlebih dahulu. Dengan membuangnya secara sadar dari kode Anda, Anda hanya mempersulit kode pemanggil untuk menentukan apakah mereka terlempar karena beberapa kesalahan dalam kode, atau hanya karena Anda memutuskan untuk menggunakannya kembali untuk sesuatu dalam implementasi Anda.

Tentu saja, ada beberapa pengecualian (hah) untuk aturan ini. Jika Anda sedang membangun sesuatu yang dapat menyebabkan pengecualian yang sama persis dengan yang sudah ada, silakan gunakan itu, terutama jika Anda mencoba untuk mencocokkan beberapa perilaku bawaan. Pastikan Anda memilih jenis pengecualian yang sangat spesifik.

Namun secara umum, kecuali Anda menemukan pengecualian (spesifik) yang memenuhi kebutuhan Anda, Anda harus selalu mempertimbangkan untuk membuat jenis pengecualian Anda sendiri untuk pengecualian khusus yang diharapkan. Terutama saat Anda menulis kode perpustakaan, ini bisa sangat berguna untuk memisahkan sumber pengecualian.

menyodok
sumber
3
Bagian ketiga tidak masuk akal bagi saya. Tentu, Anda sebaiknya menghindari menyebabkan kesalahan ini untuk memulai, tetapi ketika Anda misalnya menulis sebuah IListimplementasi, Anda tidak dapat mempengaruhi indeks yang diminta, itu kesalahan logika pemanggil ketika indeks tidak valid, dan Anda hanya dapat memberi tahu mereka kesalahan logika ini dengan memberikan pengecualian yang sesuai. Mengapa IndexOutOfRangeExceptiontidak sesuai?
7
@delnan Jika Anda mengimplementasikan IList, maka Anda akan membuang a ArgumentOutOfRangeExceptionseperti yang disarankan oleh dokumentasi antarmuka . IndexOutOfRangeExceptionadalah untuk array, dan sejauh yang saya tahu, Anda tidak dapat mengimplementasikan ulang array.
aduk
2
Apa yang mungkin juga agak terkait, NullReferenceExceptionbiasanya secara internal dilemparkan sebagai kasus khusus dari sebuah AccessViolationException(IIRC pengujiannya adalah seperti cmp [addr], addr, yaitu mencoba untuk mendereferensi penunjuk dan jika gagal dengan pelanggaran akses, menangani perbedaan antara NRE dan AVE dalam penanganan interupsi yang dihasilkan). Jadi selain alasan semantik, ada juga beberapa kecurangan yang terlibat. Ini juga dapat membantu mencegah Anda memeriksa nullsecara manual ketika tidak membantu - jika Anda tetap akan membuang NRE, mengapa tidak membiarkan .NET melakukannya?
Luaan
Sehubungan dengan pernyataan terakhir Anda, tentang pengecualian khusus: Saya tidak pernah merasa perlu untuk melakukan ini. Mungkin saya melewatkan sesuatu. Dalam kondisi apa seseorang perlu membuat jenis pengecualian khusus sebagai pengganti sesuatu dari kerangka kerja?
DonBoitnott
1
Nah, untuk aplikasi yang lebih kecil mungkin tidak diperlukan. Namun segera setelah Anda membuat sesuatu yang lebih kompleks di mana setiap bagian bekerja sebagai "komponen" independen, sering kali masuk akal untuk memperkenalkan pengecualian khusus untuk situasi kesalahan khusus. Misalnya, jika Anda memiliki beberapa lapisan kontrol akses, dan Anda mencoba menjalankan beberapa layanan meskipun Anda tidak diizinkan untuk melakukannya, Anda mungkin melontarkan "pengecualian akses ditolak" khusus atau sesuatu. Atau jika Anda memiliki parser yang mengurai beberapa file, Anda mungkin memiliki kesalahan parser sendiri untuk dilaporkan kembali kepada pengguna.
aduk
36

Saya menduga maksud dengan 2 terakhir adalah untuk mencegah kebingungan dengan pengecualian bawaan yang memiliki arti yang diharapkan. Namun, saya berpendapat bahwa jika Anda mempertahankan maksud pengecualian yang tepat : itu adalah yang benar untuk throw. Misalnya, jika Anda menulis koleksi ubahsuaian, tampaknya sepenuhnya masuk akal untuk digunakan IndexOutOfRangeException- lebih jelas dan lebih spesifik, IMO, daripada ArgumentOutOfRangeException. Dan sementara List<T>mungkin memilih yang terakhir, setidaknya ada 41 tempat (milik reflektor) di BCL (tidak termasuk array) yang melempar dipesan lebih dahulu IndexOutOfRangeException- tidak ada yang cukup "tingkat rendah" untuk pantas mendapatkan pengecualian khusus. Jadi ya, saya pikir Anda bisa dengan adil membantah bahwa pedoman itu konyol. Juga,NullReferenceException agak berguna dalam metode ekstensi - jika Anda ingin mempertahankan semantik yang:

obj.SomeMethod(); // this is actually an extension method

melempar NullReferenceExceptionketika objadalah null.

Marc Gravell
sumber
2
"jika Anda mempertahankan maksud yang tepat dari pengecualian" - tentunya jika itu kasusnya pengecualian akan dilemparkan tanpa Anda harus mengujinya sejak awal? Dan jika Anda sudah mengujinya, apakah itu bukan pengecualian?
PugFugly
5
@PugFugly membutuhkan waktu 2 detik untuk melihat contoh metode ekstensi: tidak, itu tidak akan dilempar tanpa Anda harus mengujinya. Jika SomeMethod()tidak perlu melakukan akses anggota, tidak benar jika memaksanya. Demikian juga: ambil poin itu dengan 41 tempat di BCL yang membuat kustom IndexOutOfRangeException, dan 16 tempat yang membuat kustomNullReferenceException
Marc Gravell
16
Saya berpendapat bahwa metode ekstensi masih harus membuang ArgumentNullExceptionalih - alih NullReferenceException. Meskipun gula sintaksis dari metode ekstensi memungkinkan sintaks yang sama seperti akses anggota normal, itu masih berfungsi dengan sangat berbeda. Dan mendapatkan NRE dari MyStaticHelpers.SomeMethod(obj)adalah salah.
aduk
1
@PugFugly BCL adalah "pustaka kelas dasar", pada dasarnya hal-hal inti di .NET.
aduk
1
@PugFugly: Ada banyak skenario di mana kegagalan mendeteksi kondisi secara preemtif akan mengakibatkan pengecualian dilemparkan pada waktu yang "tidak nyaman". Jika operasi tidak akan berhasil, membuat pengecualian lebih awal lebih baik daripada memulai operasi, menyelesaikan setengah jalan, dan kemudian harus membersihkan kekacauan yang sebagian diproses.
supercat
5

Seperti yang Anda tunjukkan, dalam artikel Membuat dan Melempar Pengecualian (Panduan Pemrograman C #) di bawah topik Hal-hal yang Harus Dihindari Saat Melempar Pengecualian , Microsoft memang mencantumkan System.IndexOutOfRangeExceptionsebagai jenis pengecualian yang tidak boleh dibuang dengan sengaja dari kode sumber Anda sendiri.

Sebaliknya, bagaimanapun, dalam lemparan artikel (C # Reference) , Microsoft tampaknya melanggar pedomannya sendiri. Berikut adalah metode yang disertakan Microsoft dalam contohnya:

static int GetNumber(int index)
{
    int[] nums = { 300, 600, 900 };
    if (index > nums.Length)
    {
        throw new IndexOutOfRangeException();
    }
    return nums[index];
}

Jadi, Microsoft sendiri tidak konsisten karena mendemonstrasikan pelemparan IndexOutOfRangeExceptiondalam dokumentasinya untuk throw!

Hal ini membuat saya percaya bahwa setidaknya untuk kasus IndexOutOfRangeException, mungkin ada saat-saat di mana jenis pengecualian itu dapat dilemparkan oleh programmer dan dianggap sebagai praktik yang dapat diterima.

DavidRR
sumber
1

Ketika saya membaca pertanyaan Anda, saya bertanya pada diri sendiri dalam kondisi apa seseorang ingin membuang tipe pengecualian NullReferenceException, InvalidCastExceptionatau ArgumentOutOfRangeException.

Menurut pendapat saya, ketika menemukan salah satu dari jenis pengecualian tersebut, saya (pengembang) merasa prihatin dengan peringatan dalam arti bahwa kompiler sedang berbicara kepada saya. Jadi, mengizinkan Anda (pengembang) untuk melempar tipe pengecualian tersebut sama dengan (kompilator) menjual tanggung jawab. Misalnya, ini menyarankan kompilator sekarang harus mengizinkan pengembang untuk memutuskan apakah suatu objek null. Tetapi membuat penentuan seperti itu seharusnya menjadi tugas compiler.

PS: Sejak tahun 2003 saya mengembangkan pengecualian saya sendiri sehingga saya bisa membuangnya sesuai keinginan. Saya pikir itu dianggap praktik terbaik untuk melakukannya.

Bellash
sumber
Poin bagus. Namun, saya pikir akan lebih akurat untuk mengatakan bahwa programmer harus membiarkan runtime .NETFramework membuang jenis pengecualian ini (dan bahwa programmer harus menanganinya dengan cara yang sesuai).
DavidRR
0

Mengesampingkan diskusi tentang NullReferenceExceptiondan IndexOutOfBoundsExceptionkesampingkan:

Bagaimana dengan menangkap dan melempar System.Exception. Saya telah banyak membuang jenis pengecualian ini dalam kode saya dan saya tidak pernah mengacaukannya. Demikian pula, sangat sering saya menangkap Exceptiontipe yang tidak spesifik , dan itu juga bekerja dengan cukup baik untuk saya. Jadi kenapa begitu?

Biasanya pengguna berpendapat, bahwa mereka harus bisa membedakan penyebab kesalahan. Dari pengalaman saya, hanya ada sedikit situasi di mana Anda ingin menangani jenis pengecualian yang berbeda secara berbeda. Untuk kasus tersebut, saat Anda mengharapkan pengguna menangani error secara terprogram, Anda harus menampilkan jenis pengecualian yang lebih spesifik. Untuk kasus lain, saya tidak yakin dengan pedoman praktik terbaik umum.

Jadi, terkait melempar, Exceptionsaya tidak melihat alasan untuk melarang ini dalam semua kasus.

EDIT: juga dari halaman MSDN:

Pengecualian tidak boleh digunakan untuk mengubah aliran program sebagai bagian dari eksekusi biasa. Pengecualian hanya boleh digunakan untuk melaporkan dan menangani kondisi kesalahan.

Klausa tangkapan yang berlebihan dengan logika individu untuk jenis pengecualian yang berbeda juga bukan praktik terbaik.

uebe
sumber
Pesan pengecualian masih ada untuk menceritakan apa yang terjadi. Saya tidak bisa membayangkan, bahwa Anda pergi dan membuat jenis pengecualian baru untuk setiap kesalahan yang berbeda, kalau-kalau konsumen mungkin ingin membedakan kesalahan tersebut, secara terprogram.
uebe
Apa yang terjadi dengan komentar saya sebelumnya?
DonBoitnott