Argumen melawan penindasan kesalahan

35

Saya telah menemukan sepotong kode seperti ini di salah satu proyek kami:

    SomeClass QueryServer(string args)
    {
        try
        {
            return SomeClass.Parse(_server.Query(args));
        }
        catch (Exception)
        {
            return null;
        }
    }

Sejauh yang saya mengerti, menekan kesalahan seperti ini adalah praktik yang buruk, karena menghancurkan informasi yang berguna dari pengecualian server asli dan membuat kode berlanjut ketika sebenarnya harus diakhiri.

Kapan tepat untuk sepenuhnya menekan semua kesalahan seperti ini?

pengguna626528
sumber
13
Eric Lippert menulis posting blog yang bagus yang disebut pengecualian menjengkelkan di mana ia mengklasifikasikan pengecualian menjadi 4 kategori yang berbeda dan menyarankan apa pendekatan yang tepat untuk masing-masing. Ditulis dari perspektif C # tetapi berlaku untuk berbagai bahasa, saya percaya.
Damien_The_Unbeliever
3
Saya percaya bahwa kode melanggar prinsip "Gagal-cepat". Ada kemungkinan besar bahwa bug aktual disembunyikan di dalamnya.
Euforia
4
Anda menangkap banyak. Anda juga akan mendapatkan bug, seperti NRE, indeks array di luar batas, kesalahan konfigurasi, ... Itu mencegah Anda menemukan bug itu dan mereka akan terus menyebabkan kerusakan.
usr
1
Jangan Paku Program Anda ke Posisi Tegak
Kevin - Reinstate Monica

Jawaban:

52

Bayangkan kode dengan ribuan file menggunakan banyak perpustakaan. Bayangkan mereka semua diberi kode seperti ini.

Bayangkan, misalnya, pembaruan server Anda menyebabkan satu file konfigurasi hilang; dan sekarang semua yang Anda miliki adalah jejak stack adalah pengecualian pointer nol ketika Anda mencoba menggunakan kelas itu: bagaimana Anda mengatasinya? Ini bisa memakan waktu berjam-jam, di mana setidaknya hanya mencatat jejak tumpukan mentah dari file yang tidak ditemukan [path file] dapat memungkinkan Anda untuk menyelesaikan sementara.

Atau bahkan lebih buruk: kegagalan di salah satu perpustakaan yang Anda gunakan setelah pembaruan yang membuat kode Anda macet nanti. Bagaimana Anda bisa melacak ini kembali ke perpustakaan?

Bahkan tanpa penanganan kesalahan yang kuat hanya melakukan

throw new IllegalStateException("THIS SHOULD NOT HAPPENING")

atau

LOGGER.error("[method name]/[arguments] should not be there")

dapat menghemat waktu Anda.

Tetapi ada beberapa kasus di mana Anda mungkin benar-benar ingin mengabaikan pengecualian dan mengembalikan nol seperti ini (atau tidak melakukan apa-apa). Terutama jika Anda mengintegrasikan dengan beberapa kode warisan yang dirancang dengan buruk dan pengecualian ini diharapkan sebagai kasus normal.

Bahkan ketika melakukan ini, Anda seharusnya bertanya-tanya apakah Anda benar-benar mengabaikan pengecualian atau "penanganan yang tepat" untuk kebutuhan Anda. Jika pengembalian nol adalah "penanganan yang benar" pengecualian Anda dalam kasus yang diberikan, maka lakukanlah. Dan tambahkan komentar mengapa ini adalah hal yang tepat untuk dilakukan.

Praktik terbaik adalah hal yang harus diikuti dalam sebagian besar kasus, mungkin 80%, mungkin 99%, tetapi Anda akan selalu menemukan satu kasus tepi di mana mereka tidak berlaku. Dalam hal itu, tinggalkan komentar mengapa Anda tidak mengikuti latihan untuk orang lain (atau bahkan diri Anda sendiri) yang akan membaca kode Anda beberapa bulan kemudian.

Walfrat
sumber
7
+1 untuk menjelaskan mengapa Anda merasa perlu melakukan ini dalam komentar. Tanpa penjelasan, pengecualian menelan selalu menaikkan lonceng alarm di kepalaku.
jpmc26
Masalah saya dengan itu adalah bahwa komentar macet di dalam kode itu. Sebagai konsumen fungsi, saya mungkin tidak akan pernah melihat komentar itu.
corsiKa
@ jpmc26 Jika pengecualian ditangani (misalnya dengan mengembalikan nilai khusus), itu sama sekali bukan pengecualian menelan.
Deduplicator
2
@corsiKa konsumen tidak perlu mengetahui detail implementasi jika fungsinya melakukan tugasnya dengan benar. mungkin mereka sebagian dari ini di perpustakaan apache, atau musim semi dan Anda tidak mengetahuinya.
Walfrat
@Dupuplikator ya tetapi menggunakan tangkapan sebagai bagian dari algoritma Anda tidak seharusnya menjadi praktik yang baik juga :)
Walfrat
14

Ada kasus di mana pola ini berguna - tetapi mereka biasanya digunakan ketika pengecualian yang dihasilkan seharusnya tidak pernah terjadi (yaitu ketika pengecualian digunakan untuk perilaku normal).

Misalnya, bayangkan Anda memiliki kelas yang membuka file, menyimpannya di objek yang dikembalikan. Jika file tidak ada, Anda dapat menganggap itu bukan kasus kesalahan, Anda mengembalikan nol dan membiarkan pengguna membuat file baru sebagai gantinya. Metode buka-file dapat mengeluarkan pengecualian di sini untuk menunjukkan tidak ada file, jadi dengan diam-diam menangkapnya mungkin merupakan situasi yang valid.

Namun, ini tidak sering Anda ingin melakukan ini, dan jika kode dikotori dengan pola seperti itu, Anda ingin menghadapinya sekarang. Paling tidak saya harapkan tangkapan diam seperti itu untuk menulis baris log mengatakan ini adalah apa yang telah terjadi (Anda telah melacak, benar) dan komentar untuk menjelaskan perilaku ini.

gbjbaanb
sumber
2
"Digunakan ketika pengecualian yang dihasilkan seharusnya tidak" tidak ada hal seperti itu. TITIK pengecualian adalah menandakan ada yang salah.
Euforia
3
@ Euphoric Tapi kadang-kadang penulis perpustakaan tidak tahu maksud dari pengguna akhir perpustakaan itu. "Kesalahan besar" seseorang mungkin adalah kondisi orang lain yang mudah dipulihkan.
Simon B
2
@ Euphoric, itu tergantung pada apa yang dianggap oleh bahasa Anda "salah besar": Orang-orang Python menyukai pengecualian untuk hal-hal yang sangat dekat dengan kontrol aliran di (misalnya,) C ++. Mengembalikan nol mungkin dapat dipertahankan dalam beberapa situasi - misalnya, membaca dari cache tempat item dapat tiba-tiba dan tidak dapat digusur.
Matt Krause
10
@ Euphoric, "File tidak ditemukan bukan pengecualian normal. Anda harus terlebih dahulu memeriksa apakah file ada jika ada kemungkinan itu tidak ada." Tidak! Tidak! Tidak! Tidak! Itu adalah pemikiran klasik yang salah seputar operasi atom. File mungkin dihapus di antara Anda memeriksa apakah ada dan mengaksesnya. Satu-satunya cara yang benar untuk menangani situasi semacam itu adalah mengaksesnya dan menangani pengecualian jika itu terjadi, misalnya dengan mengembalikan nulljika itu yang terbaik yang dapat ditawarkan bahasa pilihan Anda.
David Arno
3
@ Euphoric: Jadi, tulis kode untuk menangani karena tidak dapat mengakses file setidaknya dua kali? Itu melanggar KERING, dan kode juga dieksekusi adalah kode rusak.
Deduplicator
7

Ini adalah 100% tergantung konteks. Jika penelepon fungsi tersebut memiliki persyaratan untuk menampilkan atau mencatat kesalahan di suatu tempat, maka itu jelas tidak masuk akal. Jika penelepon mengabaikan semua pesan kesalahan atau pengecualian, tidak masuk akal untuk mengembalikan pengecualian atau pesannya. Ketika persyaratannya adalah untuk membuat kode hanya berakhir tanpa menampilkan alasan apa pun, maka panggilan

   if(QueryServer(args)==null)
      TerminateProgram();

mungkin akan cukup. Anda harus mempertimbangkan apakah ini akan membuat sulit untuk menemukan penyebab kesalahan oleh pengguna - jika itu masalahnya, ini adalah bentuk penanganan kesalahan yang salah. Namun, jika kode panggilan terlihat seperti ini

  result = QueryServer(args);
  if(result!=null)
      DoSomethingMeaningful(result);
  // else ??? Mask the error and let the user run into trouble later

maka Anda harus berdebat dengan pengembang selama tinjauan kode. Jika seseorang menerapkan bentuk penanganan yang tidak salah hanya karena dia terlalu malas untuk mencari tahu tentang persyaratan yang benar, maka kode ini tidak boleh diproduksi, dan pembuat kode harus diajarkan tentang kemungkinan konsekuensi dari kemalasan semacam itu. .

Doc Brown
sumber
5

Pada tahun-tahun saya habiskan untuk pemrograman dan pengembangan sistem, hanya ada dua situasi di mana saya menemukan pola tersebut berguna (dalam kedua kasus supresi terkandung juga pencatatan pengecualian yang dilemparkan, saya tidak menganggap tangkapan biasa dan nullkembali sebagai praktik yang baik. ).

Dua situasi adalah sebagai berikut:

1. Saat pengecualian tidak dianggap sebagai kondisi luar biasa

Ini adalah ketika Anda melakukan operasi pada beberapa data, yang mungkin melempar, Anda tahu itu mungkin melempar tetapi Anda masih ingin aplikasi Anda tetap berjalan, karena Anda tidak memerlukan data yang diproses. Jika Anda menerimanya, itu baik, jika tidak, itu juga baik.

Beberapa atribut opsional suatu kelas mungkin muncul dalam pikiran.

2. Saat Anda menyediakan implementasi perpustakaan yang baru (lebih baik, lebih cepat?) Menggunakan antarmuka yang sudah digunakan dalam suatu aplikasi

Bayangkan Anda memiliki aplikasi menggunakan semacam perpustakaan lama, yang tidak membuang pengecualian tetapi kembali nullkarena kesalahan. Jadi, Anda membuat adaptor untuk pustaka ini, cukup banyak menyalin API asli pustaka, dan menggunakan antarmuka baru (masih non-melempar) dalam aplikasi Anda dan menanganinull pemeriksaan sendiri.

Versi baru pustaka hadir, atau mungkin pustaka yang sama sekali berbeda menawarkan fungsi yang sama, yang alih-alih kembali null , melempar pengecualian dan Anda ingin menggunakannya.

Anda tidak ingin membocorkan pengecualian ke aplikasi utama Anda, jadi Anda menekan dan mencatatnya di adaptor yang Anda buat untuk membungkus ketergantungan baru ini.


Kasus pertama bukan masalah, itu adalah perilaku kode yang diinginkan. Namun, dalam situasi kedua, jika di mana-mana nilai nullkembali adaptor perpustakaan benar-benar berarti kesalahan, refactoring API untuk melempar pengecualian dan menangkapnya alih-alih mengecek nullmungkin (dan biasanya kode-bijaksana) ide yang bagus.

Saya pribadi menggunakan pengecualian supresi hanya untuk kasus pertama. Saya hanya menggunakannya untuk kasus kedua, ketika kami tidak memiliki anggaran untuk membuat sisa aplikasi berfungsi dengan pengecualian, bukan nulls.

Andy
sumber
-1

Walaupun tampaknya logis untuk mengatakan bahwa program seharusnya hanya menangkap pengecualian, mereka tahu cara menangani, dan tidak mungkin tahu cara menangani pengecualian yang tidak diantisipasi oleh programmer, klaim seperti itu mengabaikan fakta bahwa banyak operasi dapat gagal dalam suatu hampir tidak terbatas jumlah cara yang tidak memiliki efek samping, dan bahwa dalam banyak kasus penanganan yang tepat untuk sebagian besar kegagalan tersebut akan identik; detail pasti dari kegagalan itu tidak akan relevan, dan akibatnya tidak masalah apakah programmer mengantisipasi mereka.

Jika, misalnya, tujuan fungsi adalah untuk membaca file dokumen ke objek yang sesuai dan membuat jendela dokumen baru untuk menunjukkan objek itu atau melaporkan kepada pengguna bahwa file tidak dapat dibaca, upaya untuk memuat file dokumen tidak valid seharusnya tidak crash aplikasi - itu seharusnya bukan menampilkan pesan yang menunjukkan masalah tetapi biarkan sisa aplikasi terus berjalan normal kecuali untuk beberapa alasan upaya untuk memuat dokumen telah merusak keadaan sesuatu yang lain dalam sistem .

Pada dasarnya, penanganan eksepsi yang tepat sering kali tidak terlalu bergantung pada tipe eksepsi daripada lokasi di mana ia dilemparkan; Jika sumber daya dijaga oleh kunci baca-tulis, dan pengecualian dilemparkan dalam metode yang telah memperoleh kunci untuk membaca, perilaku yang tepat umumnya harus melepaskan kunci karena metode tidak bisa melakukan apa pun untuk sumber daya . Jika pengecualian dilemparkan saat kunci diperoleh untuk menulis, kunci harus sering tidak valid, karena sumber daya yang dijaga mungkin dalam keadaan tidak valid; jika primitif penguncian tidak memiliki status "tidak valid", seseorang harus menambahkan bendera untuk melacak pembatalan tersebut. Melepaskan kunci tanpa membatalkannya adalah buruk karena kode lain dapat melihat objek yang dijaga dalam keadaan tidak valid. Namun, meninggalkan kunci tergantung, bukan ta solusi yang tepat juga. Solusi yang tepat adalah dengan membatalkan kunci sehingga segala upaya yang tertunda atau di masa depan akuisisi akan segera gagal.

Jika ternyata sumber daya yang tidak valid ditinggalkan sebelum ada upaya untuk menggunakannya, tidak ada alasan untuk membatalkan aplikasi. Jika sumber daya yang tidak valid sangat penting untuk kelanjutan operasi aplikasi, aplikasi harus diturunkan tetapi sumber daya yang tidak valid kemungkinan akan membuat itu terjadi. Kode yang menerima pengecualian asli sering kali tidak memiliki cara untuk mengetahui situasi mana yang berlaku, tetapi jika itu membatalkan sumber daya, ia dapat memastikan bahwa tindakan yang benar akan berakhir diambil dalam kedua kasus tersebut.

supercat
sumber
2
Ini gagal menjawab pertanyaan karena menangani pengecualian tanpa mengetahui detail pengecualian! = Secara diam-diam mengonsumsi pengecualian (generik).
Taemyr
@Taemyr: Jika tujuan suatu fungsi adalah untuk "melakukan X jika memungkinkan, atau menunjukkan bahwa itu tidak", dan X tidak mungkin, sering kali tidak praktis untuk membuat daftar semua jenis pengecualian yang tidak berfungsi atau penelepon tidak akan peduli tentang "Tidak mungkin untuk melakukan X". Penanganan pengecualian Pokemon sering kali merupakan satu-satunya cara praktis untuk membuat hal-hal berfungsi dalam kasus-kasus seperti itu, dan tidak perlu menjadi sama jahatnya dengan beberapa orang jika kode dibuat disiplin tentang membatalkan hal-hal yang menjadi rusak oleh pengecualian yang tidak terduga.
supercat
Persis. Setiap metode harus memiliki tujuan, jika dapat memenuhi tujuannya terlepas dari apakah beberapa metode yang disebutnya melempar pengecualian atau tidak, maka menelan pengecualian, apa pun itu, dan melakukan pekerjaannya, adalah hal yang benar untuk dilakukan. Penanganan kesalahan pada dasarnya adalah tentang menyelesaikan tugas setelah kesalahan. Itu yang penting, bukan kesalahan apa yang terjadi.
jmoreno
@ jmoreno: Apa yang membuat segalanya sulit adalah bahwa dalam banyak situasi akan ada banyak pengecualian, banyak di antaranya yang tidak diantisipasi oleh programmer, yang tidak akan menunjukkan masalah, dan beberapa pengecualian, beberapa di antaranya tidak diinginkan oleh programmer. terutama mengantisipasi, yang memang menunjukkan masalah, dan tidak ada cara sistematis yang jelas untuk membedakan mereka. Satu-satunya cara saya tahu untuk secara waras berurusan dengan itu adalah untuk memiliki pengecualian membatalkan hal-hal yang rusak, jadi jika mereka peduli mereka diperhatikan dan jika mereka tidak peduli mereka diabaikan.
supercat
-2

Menelan kesalahan semacam ini tidak terlalu bermanfaat bagi siapa pun. Ya, penelepon asli mungkin tidak peduli tentang pengecualian tetapi orang lain mungkin .

Lalu apa yang mereka lakukan? Mereka akan menambahkan kode untuk menangani pengecualian untuk melihat rasa pengecualian apa yang mereka dapatkan kembali. Besar. Tetapi jika pawang pengecualian dibiarkan masuk, penelepon asli tidak lagi mendapat null kembali dan ada yang rusak di tempat lain.

Bahkan jika Anda menyadari bahwa beberapa kode hulu dapat menimbulkan kesalahan dan dengan demikian mengembalikan nol, itu akan menjadi inert serius bagi Anda untuk tidak setidaknya mencoba untuk mencegah pengecualian dalam kode panggilan IMHO.

Robbie Dee
sumber
"sungguh-sungguh lembam" - apakah Anda mungkin berarti "benar-benar tidak kompeten"?
Nama Layar Esoteris
@EsotericScreenName Pilih satu ... ;-)
Robbie Dee
-3

Saya telah melihat contoh di mana perpustakaan pihak ketiga berpotensi memiliki metode yang berguna, kecuali bahwa mereka melempar pengecualian dalam beberapa kasus yang seharusnya bekerja untuk saya. Apa yang dapat saya?

  • Menerapkan dengan tepat apa yang saya butuhkan sendiri. Bisa jadi sulit pada tenggat waktu.
  • Ubah kode pihak ketiga. Tetapi kemudian saya harus mencari gabungan konflik dengan setiap peningkatan.
  • Tulis metode pembungkus untuk mengubah pengecualian menjadi pointer nol biasa, boolean false, atau apa pun.

Misalnya, metode perpustakaan

public foo findFoo() {...} 

mengembalikan foo pertama, tetapi jika tidak ada itu melempar pengecualian. Tapi yang saya butuhkan adalah metode untuk mengembalikan foo pertama atau nol. Jadi saya menulis yang ini:

public foo myFindFoo() {
    try {
        return findFoo() 
    } 
    catch (NoFooException ex) {
        return null;
    }
}

Tidak rapi. Namun terkadang solusi pragmatis.

om
sumber
1
Apa maksud Anda "melempar pengecualian di mana mereka seharusnya bekerja untuk Anda"?
corsiKa
@corsiKa, contoh yang muncul di benak saya adalah metode mencari melalui daftar atau struktur data serupa. Apakah mereka gagal ketika mereka tidak menemukan apa pun atau mereka mengembalikan nilai nol? Contoh lain adalah metode waitUntil yang gagal pada timeout daripada mengembalikan boolean false.
om