Penyebaran pengecualian: Kapan saya harus menangkap pengecualian?

44

MethodA memanggil MethodB yang pada gilirannya memanggil MethodC.

Tidak ada penanganan eksepsi di MethodB atau MethodC. Tetapi ada penanganan pengecualian di MethodA.

Dalam MethodC terjadi pengecualian.

Sekarang, pengecualian itu menggelembung ke MethodA, yang menanganinya dengan tepat.

apa yang salah dengan ini?

Dalam pikiran saya, pada titik tertentu penelepon akan mengeksekusi MethodB atau MethodC, dan ketika pengecualian memang terjadi dalam metode tersebut, apa yang akan diperoleh dari penanganan pengecualian di dalam metode tersebut, yang pada dasarnya hanyalah blok coba / tangkap / akhirnya bukannya membiarkan mereka menggembung sampai ke callee?

Pernyataan atau konsensus seputar penanganan pengecualian adalah untuk membuang ketika eksekusi tidak dapat dilanjutkan hanya karena itu - pengecualian. Saya mengerti. Tapi mengapa tidak menangkap pengecualian lebih jauh dari rantai daripada mencoba / menangkap blok sepanjang jalan.

Saya mengerti ketika Anda perlu membebaskan sumber daya. Itu masalah yang sama sekali berbeda.

Daniel Frost
sumber
46
Menurut Anda mengapa konsensus adalah untuk memiliki rantai melewati tangkapan?
Caleth
Dengan IDE yang bagus dan gaya pengkodean yang tepat, Anda bisa tahu bahwa beberapa pengecualian dapat dilemparkan ketika sebuah metode dipanggil. Menanganinya atau membiarkannya diperbanyak adalah keputusan si penelepon. Saya tidak melihat masalah dengan ini.
Hieu Le
14
Jika metode tidak dapat menangani pengecualian, dan hanya memikirkan kembali itu, saya akan mengatakan bahwa itu adalah bau kode. Jika suatu metode tidak dapat menangani pengecualian, dan tidak perlu melakukan hal lain ketika pengecualian dilemparkan maka tidak perlu lagi ada try-catchblok.
Greg Burghardt
7
"Apa yang salah dengan ini ?" : tidak ada
Ewan
5
Penangkapan pass-through (yang tidak membungkus pengecualian dalam tipe berbeda atau semacamnya) mengalahkan seluruh tujuan pengecualian. Melempar pengecualian adalah mekanisme yang rumit, dan itu dibuat dengan sengaja. Jika tangkapan pass-through adalah kasus penggunaan yang dimaksudkan, maka yang Anda perlukan hanyalah menerapkan Result<T>tipe (tipe yang menyimpan hasil dari perhitungan, atau kesalahan), dan mengembalikannya dari fungsi melempar Anda yang sebaliknya. Menyebarkan kesalahan di tumpukan akan berarti membaca setiap nilai kembali, memeriksa apakah itu kesalahan, dan mengembalikan kesalahan jika demikian.
Alexander

Jawaban:

139

Sebagai prinsip umum, jangan menangkap pengecualian kecuali Anda tahu apa yang harus dilakukan dengan mereka. Jika MethodC melempar pengecualian, tetapi MethodB tidak memiliki cara yang berguna untuk menanganinya, maka MethodC akan memungkinkan pengecualian menyebar ke MethodA.

Satu-satunya alasan mengapa suatu metode harus memiliki mekanisme penangkapan dan rethrow adalah:

  • Anda ingin mengonversi satu pengecualian menjadi berbeda yang lebih bermakna bagi pemanggil di atas.
  • Anda ingin menambahkan informasi tambahan ke pengecualian.
  • Anda memerlukan klausa tangkapan untuk membersihkan sumber daya yang akan bocor tanpa satu.

Kalau tidak, menangkap pengecualian di tingkat yang salah cenderung menghasilkan kode yang gagal secara diam-diam tanpa memberikan umpan balik yang berguna ke kode panggilan (dan akhirnya pengguna perangkat lunak). Alternatif menangkap pengecualian dan kemudian segera memikirkan kembali tidak ada gunanya.

Simon B
sumber
28
@GregBurghardt jika bahasa Anda memiliki sesuatu yang serupa try ... finally ..., kemudian gunakan itu, bukan catch & rethrow
Caleth
19
"menangkap pengecualian dan kemudian segera memikirkan kembali itu tidak ada gunanya" Tergantung pada bahasa dan bagaimana Anda melakukannya, ini dapat secara aktif berbahaya bagi basis kode. Seringkali orang yang mencoba ini menghapus banyak informasi tentang pengecualian seperti stacktrace asli. Saya sudah berurusan dengan kode di mana penelepon mendapat pengecualian yang sepenuhnya menyesatkan tentang apa yang terjadi dan di mana.
JimmyJames
7
"jangan menangkap pengecualian kecuali Anda tahu apa yang harus dilakukan dengan mereka". Kedengarannya masuk akal pada pandangan pertama, tetapi menyebabkan masalah di kemudian hari. Apa yang Anda lakukan di sini adalah membocorkan detail implementasi kepada penelepon Anda. Bayangkan Anda menggunakan ORM tertentu dalam implementasi Anda untuk memuat data. Jika Anda tidak menangkap pengecualian spesifik ORM itu tetapi membiarkannya muncul, Anda tidak dapat mengganti lapisan data Anda tanpa memutus kompatibilitas dengan pengguna yang ada. Itu salah satu kasus yang lebih jelas, tetapi bisa menjadi sangat berbahaya dan sulit ditangkap.
Voo
11
@Voo Dalam contoh Anda lakukan tahu apa yang harus dilakukan dengan itu. Bungkus dalam pengecualian yang terdokumentasi khusus untuk kode Anda, misalnya LoadDataExceptiondan sertakan detail pengecualian asli sesuai dengan fitur bahasa Anda, sehingga pengelola masa depan dapat melihat akar penyebabnya tanpa harus melampirkan debugger dan mencari tahu cara mereproduksi masalah.
Colin Young
14
@Vo Anda sepertinya telah melewatkan alasan "Anda ingin mengonversi satu pengecualian ke yang berbeda yang lebih bermakna bagi penelepon di atas" untuk skenario tangkapan / rethrow.
jpmc26
21

Apa yang salah dengan ini ?

Sama sekali tidak ada.

Sekarang, pengecualian itu menggelembung ke MethodA, yang menanganinya dengan tepat.

"menanganinya dengan tepat" adalah bagian yang penting. Itulah inti dari Penanganan Eksepsi Terstruktur.

Jika kode Anda dapat melakukan sesuatu yang "berguna" dengan Pengecualian, lakukan saja. Jika tidak, maka biarkan saja.

. . . mengapa tidak menangkap pengecualian lebih jauh dari rantai daripada mencoba / menangkap blok sepanjang jalan.

Itulah yang seharusnya Anda lakukan. Jika Anda membaca kode yang memiliki handler / rethrowers "all down", maka Anda [mungkin] membaca beberapa kode yang sangat buruk.

Sayangnya, beberapa Pengembang hanya melihat catch block sebagai kode "boiler-plate" yang mereka masukkan (tidak ada permainan kata-kata) untuk setiap metode yang mereka tulis, sering kali karena mereka tidak benar-benar "mendapatkan" Penanganan Pengecualian dan berpikir mereka harus menambahkan sesuatu sehingga bahwa Pengecualian tidak "melarikan diri" dan mematikan program mereka.

Bagian dari kesulitan di sini adalah bahwa, sebagian besar waktu, masalah ini bahkan tidak akan diperhatikan, karena Pengecualian tidak selalu dilemparkan tetapi, ketika itu terjadi , program ini akan membuang banyak sekali waktu dan upaya secara bertahap membuka tumpukan panggilan untuk bangun ke suatu tempat yang benar-benar melakukan sesuatu yang berguna dengan Pengecualian.

Phill W.
sumber
7
Yang lebih buruk adalah ketika aplikasi menangkap pengecualian, dan kemudian mencatatnya (di mana semoga itu tidak hanya duduk di sana selamanya) dan mencoba untuk melanjutkan seperti biasa, bahkan ketika itu benar-benar tidak bisa.
Solomon Ucko
1
@SolomonUcko: Ya, itu tergantung. Jika misalnya Anda sedang menulis server RPC sederhana, dan pengecualian pengecualian ditangani hingga loop peristiwa utama, satu-satunya pilihan yang masuk akal adalah untuk mencatatnya, mengirim kesalahan RPC ke rekan jauh, dan melanjutkan memproses peristiwa. Alternatif lain adalah dengan mematikan seluruh program, yang akan mendorong kacang SRE Anda ketika server mati dalam produksi.
Kevin
@Kevin Dalam hal itu, harus ada satu catchdi tingkat tertinggi yang memungkinkan untuk mencatat kesalahan dan mengembalikan respons kesalahan. Tidak ada catchbalok yang ditaburkan di mana-mana. Jika Anda merasa tidak ingin mencantumkan setiap pengecualian yang mungkin diperiksa (dalam bahasa seperti Java), cukup bungkus dalam, RuntimeExceptionalih - alih mencatatnya di sana, mencoba melanjutkan, dan mengalami lebih banyak kesalahan atau bahkan kerentanan.
Solomon Ucko
8

Anda harus membuat perbedaan antara Perpustakaan dan Aplikasi.

Perpustakaan dapat membuang pengecualian tanpa tertangkap secara bebas

Saat Anda mendesain perpustakaan, pada titik tertentu Anda harus memikirkan apa yang salah. Parameter bisa berada dalam kisaran yang salah atau null, sumber daya eksternal tidak tersedia, dll.

Pustaka Anda paling sering tidak memiliki cara untuk menghadapinya dengan cara yang masuk akal . Satu-satunya solusi yang masuk akal adalah dengan melemparkan Pengecualian yang sesuai dan membiarkan pengembang Aplikasi menanganinya.

Aplikasi harus selalu pada beberapa titik menangkap pengecualian

Ketika Pengecualian ditangkap, saya suka mengategorikannya sebagai Kesalahan atau Kesalahan Fatal . Kesalahan reguler berarti bahwa satu operasi dalam Aplikasi saya gagal. Misalnya, dokumen terbuka tidak dapat disimpan, karena tujuannya tidak dapat ditulis. Satu-satunya pemikiran yang masuk akal untuk dilakukan Aplikasi adalah memberi tahu pengguna bahwa operasi tidak dapat diselesaikan dengan sukses, memberikan informasi yang dapat dibaca manusia sehubungan dengan masalah tersebut dan kemudian membiarkan pengguna memutuskan apa yang harus dilakukan selanjutnya.

Sebuah Kesalahan Fatal adalah kesalahan logika Aplikasi utama tidak dapat pulih dari. Sebagai contoh, jika driver perangkat grafis crash dalam permainan video, tidak ada cara bagi Aplikasi untuk "dengan anggun" menginformasikan pengguna. Dalam hal ini, file log harus ditulis dan, jika mungkin, pengguna harus diberitahu dengan cara apa pun.

Bahkan dalam kasus yang parah, Aplikasi harus menangani Pengecualian ini dengan cara yang berarti. Ini mungkin termasuk menulis file Log, mengirimkan Laporan Kecelakaan, dll. Tidak ada alasan bagi Aplikasi untuk tidak menanggapi Pengecualian dalam beberapa cara.

MechMK1
sumber
Memang, jika Anda memiliki perpustakaan untuk sesuatu seperti operasi penulisan disk atau, katakanlah, manipulasi perangkat keras lainnya, Anda dapat berakhir dalam segala macam peristiwa yang tidak terduga. Bagaimana jika hard drive ditarik keluar saat menulis? Apa CD drive keluar saat membaca? Itu di luar kendali Anda dan saat Anda bisa melakukan sesuatu (misalnya, berpura-pura itu sukses), sering kali praktik yang baik untuk membuang pengecualian untuk pengguna perpustakaan dan membiarkan mereka memutuskan. Mungkin HDDPluggedOutDuringWritingExceptiondapat ditangani dan tidak fatal untuk aplikasi tersebut. Program dapat memutuskan apa yang harus dilakukan dengan itu.
VLAZ
1
@VLAZ Apa yang fatal vs non-fatal adalah sesuatu yang harus diputuskan oleh aplikasi. Perpustakaan harus memberi tahu apa yang terjadi. Aplikasi harus memutuskan bagaimana bereaksi terhadapnya.
MechMK1
0

Apa yang salah dengan pola yang Anda gambarkan adalah bahwa metode A tidak akan dapat membedakan antara tiga skenario:

  1. Metode B gagal dengan cara yang diantisipasi.

  2. Metode C gagal dengan cara yang tidak diantisipasi oleh metode B, tetapi sementara metode B sedang melakukan operasi yang bisa ditinggalkan dengan aman.

  3. Metode C gagal dengan cara yang tidak diantisipasi oleh metode B, tetapi sementara metode B sedang melakukan operasi yang menempatkan hal-hal dalam keadaan tidak koheren yang semestinya sementara yang gagal dibersihkan oleh B karena kegagalan C.

Satu-satunya cara metode A akan dapat membedakan skenario-skenario itu adalah jika pengecualian yang dilemparkan dari B mencakup informasi yang cukup untuk tujuan itu, atau jika tumpukan yang membuka gulungan untuk metode B menyebabkan objek dibiarkan dalam keadaan tidak valid secara eksplisit . Sayangnya, sebagian besar kerangka kerja pengecualian membuat kedua pola ini canggung, memaksa pemrogram untuk membuat keputusan desain yang "kurang jahat".

supercat
sumber
2
Skenario 2 dan 3 adalah bug dalam metode B. Metode A seharusnya tidak mencoba untuk memperbaikinya.
Sjoerd
@ Soerd: Bagaimana metode B seharusnya mengantisipasi semua cara di mana metode C mungkin gagal?
supercat
Dengan pola terkenal seperti melakukan semua operasi yang mungkin melempar ke variabel temp, kemudian dengan operasi yang tidak bisa melempar - misalnya menukar - menukar yang lama dengan negara baru. Pola lain adalah mendefinisikan operasi yang dapat diulang dengan aman, sehingga Anda dapat mencoba kembali operasi tanpa takut berantakan. Ada banyak buku tentang menulis 'pengecualian kode aman', jadi saya tidak bisa menceritakan semuanya di sini.
Sjoerd
Ini adalah poin yang bagus untuk tidak menggunakan pengecualian sama sekali (yang akan menjadi keputusan yang sangat baik). Tapi saya kira, itu tidak benar-benar menjawab pertanyaan, karena OP tampaknya berniat untuk menggunakan pengecualian, dan hanya bertanya di mana tangkapan seharusnya.
cmaster
@ Sojerd Metode B menjadi jauh lebih mudah untuk dipikirkan jika pengecualian dilarang oleh bahasa. Karena dalam kasus itu, Anda benar-benar melihat semua jalur aliran kontrol melalui B, dan Anda tidak perlu menebak operator mana yang mungkin kelebihan beban dengan cara melempar (C ++) untuk menghindari skenario 3. Kami membayar banyak dalam hal kode kejelasan dan keamanan agar malas mengembalikan kesalahan dengan "hanya" melempar pengecualian. Karena, pada akhirnya, penanganan kesalahan adalah bagian penting dari kode.
cmaster