Mengapa dicoba {...} akhirnya {...} bagus; coba {...} tangkap {} buruk?


201

Saya telah melihat orang mengatakan bahwa itu adalah bentuk yang buruk untuk menggunakan tangkapan tanpa argumen, terutama jika tangkapan itu tidak melakukan apa-apa:

StreamReader reader=new  StreamReader("myfile.txt");
try
{
  int i = 5 / 0;
}
catch   // No args, so it will catch any exception
{}
reader.Close();

Namun, ini dianggap bentuk yang baik:

StreamReader reader=new  StreamReader("myfile.txt");
try
{
  int i = 5 / 0;
}
finally   // Will execute despite any exception
{
  reader.Close();
}

Sejauh yang saya tahu, satu-satunya perbedaan antara menempatkan kode pembersihan di akhirnya blok dan menempatkan kode pembersihan setelah coba .. blok pengiriman adalah jika Anda memiliki pernyataan kembali di blok percobaan Anda (dalam hal itu, kode pembersihan pada akhirnya akan jalankan, tetapi kode setelah coba..catch tidak akan).

Jika tidak, apa yang istimewa tentang akhirnya?


7
Sebelum Anda Mencoba Menangkap harimau yang tidak dapat Anda tangani, Anda harus mendokumentasikan keinginan Terakhir Anda.

Topik pengecualian dalam dokumentasi dapat memberikan beberapa wawasan yang baik. Lihat juga contoh Blok Akhirnya .
Athafoud

Jawaban:


357

Perbedaan besar adalah bahwa try...catchakan menelan pengecualian, menyembunyikan fakta bahwa kesalahan terjadi. try..finallyakan menjalankan kode pembersihan Anda dan kemudian pengecualian akan terus berjalan, untuk ditangani oleh sesuatu yang tahu apa yang harus dilakukan dengannya.


11
Kode apa pun yang ditulis dengan enkapsulasi kemungkinan hanya akan mampu menangani pengecualian pada titik di mana kode itu dinaikkan. Cukup dengan mengembalikannya ke tumpukan panggilan dengan harapan putus asa bahwa sesuatu yang lain akan dapat menangani beberapa pengecualian arbitrase adalah resep untuk bencana.
David Arno

3
Dalam kebanyakan kasus, itu lebih jelas mengapa pengecualian tertentu akan terjadi dari level aplikasi (misalnya, pengaturan konfigurasi tertentu) daripada di level librray kelas.
Mark Cidade

88
David - Saya lebih suka program gagal cepat sehingga saya dapat dibuat sadar akan masalah daripada membiarkan program berjalan dalam keadaan yang tidak diketahui.
Erik Forbes

6
Jika program Anda berada dalam kondisi tidak dikenal setelah pengecualian maka Anda melakukan kesalahan kode.
Zan Lynx

41
@ Davidvidno, kode apa pun yang ditulis dengan enkapsulasi dalam pikiran hanya harus menangani pengecualian dalam ruang lingkup mereka. Hal lain harus diberikan kepada orang lain untuk ditangani. Jika saya memiliki aplikasi yang mendapat nama file dari pengguna kemudian membaca file, dan pembaca file saya mendapat pengecualian membuka file, itu harus melewatkannya (atau mengkonsumsi pengecualian dan melemparkan yang baru) sehingga aplikasi dapat mengatakan , hei - file tidak terbuka, mari minta pengguna untuk yang berbeda. Pembaca file seharusnya tidak dapat meminta pengguna atau melakukan tindakan lain sebagai tanggapan. Tujuannya hanya untuk membaca file.
iheanyi

62

"Akhirnya" adalah pernyataan "Sesuatu yang harus selalu Anda lakukan untuk memastikan keadaan program waras". Karena itu, selalu baik untuk memilikinya, jika ada kemungkinan pengecualian dapat membatalkan status program. Kompiler juga berusaha keras untuk memastikan bahwa kode Akhirnya Anda dijalankan.

"Catch" adalah pernyataan "Saya dapat memulihkan dari pengecualian ini". Anda seharusnya hanya pulih dari pengecualian yang benar-benar dapat Anda perbaiki - menangkap tanpa argumen mengatakan "Hei, saya bisa pulih dari apa pun!", Yang hampir selalu tidak benar.

Jika dimungkinkan untuk pulih dari setiap pengecualian, maka itu akan benar-benar menjadi semantic quibble, tentang apa yang Anda nyatakan sebagai niat Anda. Namun, tidak, dan hampir pasti bingkai di atas Anda akan lebih siap untuk menangani pengecualian tertentu. Dengan demikian, gunakan akhirnya, jalankan kode pembersihan Anda secara gratis, tetapi tetap biarkan penangan yang lebih berpengetahuan luas menangani masalah ini.


1
Sentimen Anda tersebar luas, tetapi sayangnya mengabaikan kasus penting lainnya: secara tegas membatalkan objek yang invariannya tidak lagi berlaku. Pola umum adalah kode untuk mendapatkan kunci, membuat beberapa perubahan pada objek, dan melepaskan kunci. Jika pengecualian terjadi setelah melakukan beberapa tetapi tidak semua perubahan, objek mungkin dibiarkan dalam keadaan tidak valid. Meskipun alternatif IMHO yang lebih baik harus ada, saya tahu tidak ada pendekatan yang lebih baik daripada untuk menangkap pengecualian yang terjadi saat keadaan objek mungkin tidak valid, tegas menyatakan tidak valid, dan rethrow.
supercat

32

Karena ketika satu baris itu mengeluarkan pengecualian, Anda tidak akan mengetahuinya.

Dengan blok kode pertama, pengecualian hanya akan diserap , program akan terus mengeksekusi bahkan ketika keadaan program mungkin salah.

Dengan blok kedua, pengecualian akan dilemparkan dan gelembung tapi yang reader.Close()masih dijamin untuk menjalankan.

Jika pengecualian tidak diharapkan, maka jangan coba-coba..tangkap blok begitu saja, akan sulit untuk debug nanti ketika program masuk ke keadaan buruk dan Anda tidak tahu mengapa.


21

Akhirnya dieksekusi apa pun yang terjadi. Jadi, jika blok percobaan Anda berhasil itu akan mengeksekusi, jika blok try Anda gagal, maka ia akan mengeksekusi blok tangkap, dan kemudian blok akhirnya.

Juga, lebih baik untuk mencoba menggunakan konstruk berikut:

using (StreamReader reader=new  StreamReader("myfile.txt"))
{
}

Seperti pernyataan menggunakan secara otomatis dibungkus dalam coba / akhirnya dan aliran akan ditutup secara otomatis. (Anda perlu mencoba / menangkap pernyataan menggunakan jika Anda ingin benar-benar menangkap pengecualian).


5
Ini tidak benar. Menggunakan tidak membungkus kode dengan try / catch, seharusnya mengatakan try / akhirnya
pr0nin

8

Sementara 2 blok kode berikut ini setara, mereka tidak sama.

try
{
  int i = 1/0; 
}
catch
{
  reader.Close();
  throw;
}

try
{
  int i = 1/0;
}
finally
{
  reader.Close();
}
  1. 'akhirnya' adalah kode yang mengungkapkan maksud. Anda menyatakan kepada kompiler dan programmer lain bahwa kode ini perlu dijalankan tidak peduli apa.
  2. jika Anda memiliki beberapa blok tangkapan dan Anda memiliki kode pembersihan, Anda akhirnya perlu. Tanpa akhirnya, Anda akan menduplikasi kode pembersihan di setiap blok tangkap. (Prinsip KERING)

akhirnya balok spesial. CLR mengenali dan memperlakukan kode dengan sebuah blok akhirnya secara terpisah dari blok tangkap, dan CLR berusaha keras untuk menjamin bahwa blok akhirnya akan selalu dijalankan. Ini bukan hanya gula sintaksis dari kompiler.


5

Saya setuju dengan apa yang tampaknya menjadi konsensus di sini - 'tangkapan' kosong itu buruk karena menutupi apa pun pengecualian yang mungkin terjadi di blok uji coba.

Juga, dari sudut pandang keterbacaan, ketika saya melihat blok 'coba' saya berasumsi akan ada pernyataan 'menangkap' yang sesuai. Jika Anda hanya menggunakan 'coba' untuk memastikan sumber daya tidak dialokasikan di blok 'akhirnya', Anda dapat mempertimbangkan pernyataan 'menggunakan' :

using (StreamReader reader = new StreamReader('myfile.txt'))
{
    // do stuff here
} // reader.dispose() is called automatically

Anda dapat menggunakan pernyataan 'menggunakan' dengan objek apa pun yang mengimplementasikan IDisposable. Metode dispose objek () akan dipanggil secara otomatis di akhir blok.


4

Menggunakan Try..Catch..Finally , jika metode Anda tahu cara menangani pengecualian secara lokal. Pengecualian terjadi di Coba, Ditangani dalam Tangkapan dan setelah itu pembersihan dilakukan pada Akhirnya.

Dalam kasus jika metode Anda tidak tahu bagaimana menangani pengecualian tetapi perlu pembersihan setelah penggunaan terjadi Try..Finally

Dengan ini pengecualian disebarkan ke metode panggilan dan ditangani jika ada pernyataan Catch yang sesuai dalam metode panggilan. Jika tidak ada penangan pengecualian dalam metode saat ini atau metode panggilan apa pun maka aplikasi crash.

Dengan Try..Finallydipastikan bahwa pembersihan lokal dilakukan sebelum menyebarkan pengecualian ke metode panggilan.


1
Sebagai dasar dari jawaban ini, itu benar-benar yang terbaik. Adalah baik untuk menjadi terbiasa mencoba / menangkap / akhirnya, bahkan jika salah satu dari dua yang terakhir dibiarkan kosong. Ada keadaan SANGAT LANGKA di mana blok tangkap mungkin ada dan kosong, tetapi setidaknya jika Anda selalu menulis coba / tangkap / akhirnya, Anda akan melihat blok kosong saat Anda membaca kode dengan teliti. Memiliki blok yang kosong akhirnya membantu dengan cara yang sama. Jika Anda perlu pembersihan nanti, atau perlu men-debug negara pada saat pengecualian, ini sangat membantu.
Jesse Williams

3

Coba .. akhirnya blokir akan tetap ada pengecualian yang dimunculkan. Yang finallydilakukan adalah memastikan bahwa kode pembersihan dijalankan sebelum pengecualian dilemparkan.

Coba..catch dengan tangkapan kosong akan benar-benar mengkonsumsi pengecualian dan menyembunyikan fakta bahwa itu terjadi. Pembaca akan ditutup, tetapi tidak ada yang tahu jika hal yang benar terjadi. Bagaimana jika niat Anda adalah menulis i ke file? Dalam hal ini, Anda tidak akan membuatnya ke bagian kode dan myfile.txt akan kosong. Apakah semua metode hilir menangani ini dengan benar? Ketika Anda melihat file kosong, apakah Anda dapat menebak dengan benar bahwa file itu kosong karena ada pengecualian? Lebih baik membuang pengecualian dan biarkan diketahui bahwa Anda melakukan sesuatu yang salah.

Alasan lain adalah try..catch yang dilakukan seperti ini sama sekali tidak benar. Apa yang Anda katakan dengan melakukan ini adalah, "Tidak peduli apa yang terjadi, saya dapat mengatasinya." Bagaimana StackOverflowException, bisakah Anda membersihkan setelah itu? Bagaimana dengan OutOfMemoryException? Secara umum, Anda hanya harus menangani pengecualian yang Anda harapkan dan tahu cara menanganinya.


2

Jika Anda tidak tahu jenis pengecualian untuk menangkap atau apa yang harus dilakukan dengan itu, tidak ada gunanya memiliki pernyataan menangkap. Anda harus membiarkannya untuk penelepon yang lebih tinggi yang mungkin memiliki lebih banyak informasi tentang situasi untuk mengetahui apa yang harus dilakukan.

Anda harus tetap memiliki pernyataan akhirnya di sana jika ada pengecualian, sehingga Anda dapat membersihkan sumber daya sebelum pengecualian itu dilemparkan ke penelepon.


2

Dari perspektif keterbacaan, itu lebih eksplisit memberitahu pembaca kode masa depan "hal-hal ini di sini adalah penting, perlu dilakukan tidak peduli apa yang terjadi." Ini bagus.

Juga, pernyataan tangkapan kosong cenderung memiliki "bau" tertentu kepada mereka. Mereka mungkin merupakan tanda bahwa pengembang tidak memikirkan berbagai pengecualian yang dapat terjadi dan cara menanganinya.


2

Akhirnya adalah opsional - tidak ada alasan untuk memiliki blok "Akhirnya" jika tidak ada sumber daya untuk dibersihkan.


2

Diambil dari: sini

Memunculkan dan menangkap pengecualian tidak boleh terjadi secara rutin sebagai bagian dari keberhasilan eksekusi suatu metode. Saat mengembangkan pustaka kelas, kode klien harus diberi kesempatan untuk menguji kondisi kesalahan sebelum melakukan operasi yang dapat menghasilkan pengecualian yang muncul. Misalnya, System.IO.FileStream menyediakan properti CanRead yang dapat diperiksa sebelum memanggil metode Baca, mencegah pengecualian potensial yang muncul, seperti yang diilustrasikan dalam cuplikan kode berikut:

Dim str As Stream = GetStream () If (str.CanRead) Kemudian 'kode untuk membaca stream End If

Keputusan apakah akan memeriksa keadaan suatu objek sebelum menerapkan metode tertentu yang dapat menimbulkan pengecualian tergantung pada keadaan objek yang diharapkan. Jika objek FileStream dibuat menggunakan jalur file yang seharusnya ada dan konstruktor yang harus mengembalikan file dalam mode baca, memeriksa properti CanRead tidak diperlukan; ketidakmampuan membaca FileStream akan menjadi pelanggaran terhadap perilaku yang diharapkan dari pemanggilan metode yang dilakukan, dan pengecualian harus dimunculkan. Sebaliknya, jika suatu metode didokumentasikan sebagai mengembalikan referensi FileStream yang mungkin atau mungkin tidak dapat dibaca, disarankan memeriksa properti CanRead sebelum mencoba membaca data.

Untuk mengilustrasikan dampak kinerja yang menggunakan teknik pengkodean "jalankan hingga pengecualian" dapat menyebabkan, kinerja para pemain, yang melempar InvalidCastException jika pemain gagal, dibandingkan dengan operator C #, yang mengembalikan nol jika pemain gagal. Kinerja kedua teknik identik untuk kasus di mana gips valid (lihat Uji 8.05), tetapi untuk kasus di mana gips tidak valid, dan menggunakan gips menyebabkan pengecualian, menggunakan gips 600 kali lebih lambat daripada menggunakan gips. sebagai operator (lihat Uji 8.06). Dampak kinerja tinggi dari teknik melempar pengecualian termasuk biaya pengalokasian, melempar, dan menangkap pengecualian dan biaya pengumpulan sampah berikutnya dari objek pengecualian, yang berarti dampak seketika melempar pengecualian tidak setinggi ini. Karena lebih banyak pengecualian dilemparkan,


2
Scott - jika teks yang Anda kutip di atas adalah di balik paywall expertsexchange.com, Anda mungkin tidak boleh mempostingnya di sini. Saya mungkin salah dalam hal ini tetapi saya berani bertaruh itu bukan ide yang baik.
Onorio Catenacci

2

Merupakan praktik yang buruk untuk menambahkan klausa tangkapan hanya untuk memikirkan kembali pengecualian.


2

Jika Anda akan membaca C # untuk programmer Anda akan mengerti, bahwa blok akhirnya dirancang untuk mengoptimalkan aplikasi dan mencegah kebocoran memori.

CLR tidak sepenuhnya menghilangkan kebocoran ... kebocoran memori dapat terjadi jika program secara tidak sengaja menyimpan referensi ke objek yang tidak diinginkan

Misalnya ketika Anda membuka koneksi file atau database, mesin Anda akan mengalokasikan memori untuk memenuhi transaksi itu, dan memori itu akan disimpan tidak kecuali perintah disposed atau close dijalankan. tetapi jika selama transaksi, kesalahan terjadi, perintah proses akan dihentikan tidak kecuali itu di dalam try.. finally..blok.

catchberbeda dari finallydalam arti bahwa, tangkapan dirancang untuk memberi Anda cara untuk menangani / mengelola atau menafsirkan kesalahan itu sendiri. Anggap saja sebagai orang yang memberi tahu Anda, "Hei, saya menangkap beberapa orang jahat, apa yang Anda ingin saya lakukan terhadap mereka?" sementarafinally dirancang untuk memastikan bahwa sumber daya Anda ditempatkan dengan benar. Pikirkan itu dari seseorang bahwa apakah ada beberapa orang jahat dia akan memastikan bahwa properti Anda masih aman.

Dan Anda harus membiarkan keduanya bekerja bersama untuk kebaikan.

sebagai contoh:

try
{
  StreamReader reader=new  StreamReader("myfile.txt");
  //do other stuff
}
catch(Exception ex){
 // Create log, or show notification
 generic.Createlog("Error", ex.message);
}
finally   // Will execute despite any exception
{
  reader.Close();
}

1

Dengan akhirnya, Anda dapat membersihkan sumber daya, bahkan jika pernyataan tangkapan Anda melempar pengecualian ke program panggilan. Dengan contoh Anda yang berisi pernyataan tangkapan kosong, ada sedikit perbedaan. Namun, jika dalam tangkapan Anda, Anda melakukan beberapa pemrosesan dan melempar kesalahan, atau bahkan hanya tidak memiliki tangkapan sama sekali, akhirnya akan tetap dijalankan.


1

Sebagai contoh, praktik yang buruk untuk menangkap pengecualian yang tidak perlu Anda tangani. Lihat Bab 5 tentang .Net Performance dari Meningkatkan .NET Application dan Skalability . Catatan, Anda mungkin harus memuat aliran di dalam blok try, dengan cara itu, Anda dapat menangkap pengecualian terkait jika gagal. Membuat aliran di luar blok coba mengalahkan tujuannya.


0

Di antara banyak alasan, pengecualian sangat lambat untuk dieksekusi. Anda dapat dengan mudah melumpuhkan waktu eksekusi Anda jika ini sering terjadi.


0

Masalah dengan blok coba / tangkap yang menangkap semua pengecualian adalah bahwa program Anda sekarang dalam kondisi tak tentu jika terjadi pengecualian yang tidak diketahui. Ini sepenuhnya bertentangan dengan aturan puasa gagal - Anda tidak ingin program Anda berlanjut jika pengecualian terjadi. Coba / tangkap di atas bahkan akan menangkap OutOfMemoryExceptions, tapi itu jelas suatu keadaan bahwa program Anda tidak akan berjalan.

Coba / akhirnya blok memungkinkan Anda untuk menjalankan kode pembersihan sementara masih gagal cepat. Untuk sebagian besar keadaan, Anda hanya ingin menangkap semua pengecualian di tingkat global, sehingga Anda dapat mencatatnya, dan kemudian keluar.


0

Perbedaan efektif antara contoh Anda dapat diabaikan selama tidak ada pengecualian yang dilemparkan.

Namun, jika pengecualian dilemparkan saat berada di klausa 'coba', contoh pertama akan menelannya sepenuhnya. Contoh kedua akan menaikkan pengecualian ke langkah berikutnya menumpuk tumpukan panggilan, jadi perbedaan dalam contoh yang disebutkan adalah bahwa satu sepenuhnya mengaburkan pengecualian (contoh pertama), dan yang lainnya (contoh kedua) menyimpan informasi pengecualian untuk potensi penanganan nanti saat masih mengeksekusi konten dalam klausa 'akhirnya'.

Jika, misalnya, Anda harus meletakkan kode di klausa 'tangkap' dari contoh pertama yang melemparkan pengecualian (baik yang awalnya dinaikkan, atau yang baru), kode pembersihan pembaca tidak akan pernah dijalankan. Akhirnya dieksekusi terlepas dari apa yang terjadi pada klausa 'tangkap'.

Jadi, perbedaan utama antara 'tangkap' dan 'akhirnya' adalah bahwa isi dari blok 'akhirnya' (dengan beberapa pengecualian langka) dapat dianggap dijamin untuk dieksekusi, bahkan dalam menghadapi pengecualian yang tidak terduga, sementara kode apa pun yang mengikuti klausa 'tangkap' (tetapi di luar klausa 'akhirnya') tidak akan membawa jaminan semacam itu.

Secara kebetulan, Stream dan StreamReader keduanya mengimplementasikan IDisposable, dan dapat dibungkus dengan blok 'using'. Blok 'using' adalah padanan semantik dari try / akhirnya (no 'catch'), jadi contoh Anda dapat dengan lebih singkat dinyatakan sebagai:

using (StreamReader reader = new  StreamReader("myfile.txt"))
{
  int i = 5 / 0;
}

... yang akan menutup dan membuang instance StreamReader saat keluar dari cakupan. Semoga ini membantu.


0

coba {...} catch {} tidak selalu buruk. Ini bukan pola yang umum, tetapi saya cenderung menggunakannya ketika saya perlu mematikan sumber daya apa pun yang terjadi, seperti menutup soket terbuka (mungkin) di ujung utas.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.