Saya selalu berkeyakinan bahwa jika suatu metode dapat melempar pengecualian maka itu ceroboh untuk tidak melindungi panggilan ini dengan blok percobaan yang bermakna.
Saya baru saja memposting ' Anda harus SELALU membungkus panggilan yang dapat dicoba, tangkap blok. 'untuk pertanyaan ini dan diberi tahu bahwa itu adalah' saran yang sangat buruk '- Saya ingin mengerti mengapa.
Jawaban:
Suatu metode seharusnya hanya menangkap pengecualian ketika bisa menanganinya dengan cara yang masuk akal.
Kalau tidak, meneruskannya, dengan harapan metode yang lebih tinggi dari tumpukan panggilan dapat memahaminya.
Seperti yang telah dicatat oleh orang lain, adalah praktik yang baik untuk memiliki penangan pengecualian yang tidak tertangani (dengan logging) di tingkat tertinggi dari tumpukan panggilan untuk memastikan bahwa setiap kesalahan fatal dicatat.
sumber
try
memblokir. Ada diskusi yang bagus di "Me C ++ Efektif" dari Scott Meyers.try
blok gratis di kompiler C modern apa pun, informasi itu berasal dari Nick. Saya juga tidak setuju tentang memiliki pengendali pengecualian tingkat atas karena Anda kehilangan informasi lokalitas (tempat sebenarnya di mana instruksi gagal).terminate
) . Ini lebih merupakan mekanisme keamanan. Juga,try/catch
lebih atau kurang gratis ketika tidak ada pengecualian. Ketika ada satu propagating, ia menghabiskan waktu setiap kali dilemparkan dan ditangkap, sehingga rantaitry/catch
yang hanya memikirkan kembali tidak ada biaya.Seperti yang dinyatakan oleh Mitch dan yang lainnya , Anda seharusnya tidak menangkap pengecualian bahwa Anda tidak berencana menangani dengan cara tertentu. Anda harus mempertimbangkan bagaimana aplikasi akan menangani pengecualian secara sistematis saat Anda mendesainnya. Ini biasanya mengarah ke memiliki lapisan penanganan kesalahan berdasarkan abstraksi - misalnya, Anda menangani semua kesalahan terkait SQL dalam kode akses data Anda sehingga bagian dari aplikasi yang berinteraksi dengan objek domain tidak terkena fakta bahwa ada adalah DB di bawah tenda di suatu tempat.
Ada beberapa bau kode terkait yang pasti ingin Anda hindari selain bau "tangkap semuanya di mana-mana" .
"catch, log, rethrow" : jika Anda ingin logging berbasis scoped, tulis kelas yang memancarkan pernyataan log dalam destruktornya ketika stack dibuka gulungannya karena pengecualian (ala
std::uncaught_exception()
). Yang perlu Anda lakukan hanyalah mendeklarasikan instance logging dalam lingkup yang Anda minati dan, voila, Anda telah login dan tidak perlutry
/catch
logika."catch, throw diterjemahkan" : ini biasanya menunjuk ke masalah abstraksi. Kecuali jika Anda menerapkan solusi gabungan di mana Anda menerjemahkan beberapa pengecualian spesifik menjadi satu yang lebih umum, Anda mungkin memiliki lapisan abstraksi yang tidak perlu ... dan jangan mengatakan bahwa "Saya mungkin membutuhkannya besok" .
"catch, cleanup, rethrow" : ini adalah salah satu dari kencing binatang peliharaan saya. Jika Anda melihat banyak dari ini, maka Anda harus menerapkan Resource Acquisition is inisialisasi teknik dan menempatkan bagian pembersihan di destruktor dari contoh objek petugas kebersihan .
Saya menganggap kode yang dikotori dengan
try
/catch
blok menjadi target yang baik untuk tinjauan kode dan refactoring. Ini menunjukkan bahwa penanganan pengecualian tidak dipahami dengan baik atau kode telah menjadi amba dan sangat membutuhkan refactoring.sumber
Logger
kelas yang mirip denganlog4j.Logger
yang menyertakan ID utas di setiap baris log dan mengeluarkan peringatan di destruktor ketika pengecualian aktif.Karena pertanyaan selanjutnya adalah "Saya sudah menangkap pengecualian, apa yang harus saya lakukan selanjutnya?" Apa yang akan kamu lakukan? Jika Anda tidak melakukan apa pun - itu menyembunyikan kesalahan dan program bisa "tidak berfungsi" tanpa ada peluang untuk menemukan apa yang terjadi. Anda perlu memahami apa yang sebenarnya akan Anda lakukan setelah Anda menangkap pengecualian dan hanya menangkap jika Anda tahu.
sumber
Anda tidak perlu menutupi setiap blok dengan try-catch karena try-catch masih dapat menangkap pengecualian yang tidak tertangani yang dilemparkan ke fungsi lebih jauh di tumpukan panggilan. Jadi alih-alih setiap fungsi memiliki try-catch, Anda dapat memilikinya di logika tingkat atas aplikasi Anda. Misalnya, mungkin ada
SaveDocument()
rutinitas tingkat atas, yang memanggil banyak metode yang memanggil metode lain, dll. Sub-metode ini tidak memerlukan tangkapan sendiri, karena jika mereka melempar, itu masih tertangkap olehSaveDocument()
tangkapan.Ini bagus karena tiga alasan: ini berguna karena Anda memiliki satu tempat untuk melaporkan kesalahan:
SaveDocument()
blok penangkap. Tidak perlu mengulangi ini di seluruh sub-metode, dan itulah yang Anda inginkan: satu tempat untuk memberi pengguna diagnostik yang berguna tentang sesuatu yang salah.Dua, simpan dibatalkan setiap kali ada pengecualian. Dengan setiap sub-metode coba-tangkap, jika pengecualian dilemparkan, Anda masuk ke blok tangkap metode itu, eksekusi meninggalkan fungsi, dan terus berjalan
SaveDocument()
. Jika ada yang salah, Anda mungkin ingin berhenti di sana.Tiga, semua sub-metode Anda dapat menganggap setiap panggilan berhasil . Jika panggilan gagal, eksekusi akan melompat ke blok tangkap dan kode selanjutnya tidak pernah dieksekusi. Ini dapat membuat kode Anda jauh lebih bersih. Misalnya, ini dengan kode kesalahan:
Begini caranya yang ditulis dengan pengecualian:
Sekarang jauh lebih jelas apa yang terjadi.
Catatan pengecualian kode aman bisa jadi lebih sulit untuk ditulis dengan cara lain: Anda tidak ingin membocorkan memori apa pun jika ada pengecualian. Pastikan Anda tahu tentang RAII , wadah STL, smart pointer, dan objek lain yang membebaskan sumber dayanya dalam destruktor, karena objek selalu dihancurkan sebelum pengecualian.
sumber
try
-catch
blok yang upaya untuk bendera setiap permutasi yang sedikit berbeda dari beberapa error dengan pesan yang sedikit berbeda, walaupun pada kenyataannya mereka semua harus berakhir sama: transaksi atau program gagal dan keluar! Jika terjadi kegagalan layak, saya yakin sebagian besar pengguna hanya ingin menyelamatkan apa yang mereka bisa atau, paling tidak, dibiarkan sendiri tanpa harus berurusan dengan 10 tingkat pesan tentang hal itu.Herb Sutter menulis tentang masalah ini di sini . Pasti layak dibaca.
Penggoda:
sumber
Seperti yang dinyatakan dalam jawaban lain, Anda seharusnya hanya menangkap pengecualian jika Anda bisa melakukan semacam penanganan kesalahan yang masuk akal untuk itu.
Misalnya, dalam pertanyaan yang memunculkan pertanyaan Anda, si penanya menanyakan apakah aman untuk mengabaikan pengecualian untuk
lexical_cast
dari bilangan bulat ke string. Para pemain seperti itu seharusnya tidak pernah gagal. Jika gagal, ada sesuatu yang salah dalam program. Apa yang bisa Anda lakukan untuk pulih dalam situasi itu? Mungkin yang terbaik adalah membiarkan program itu mati, karena ia dalam keadaan yang tidak bisa dipercaya. Jadi tidak menangani pengecualian mungkin merupakan hal paling aman untuk dilakukan.sumber
Jika Anda selalu menangani pengecualian segera di pemanggil metode yang dapat membuang pengecualian, maka pengecualian menjadi tidak berguna, dan Anda sebaiknya menggunakan kode kesalahan.
Inti dari pengecualian adalah bahwa mereka tidak perlu ditangani dalam setiap metode dalam rantai panggilan.
sumber
Saran terbaik yang pernah saya dengar adalah bahwa Anda seharusnya hanya menangkap pengecualian di titik-titik di mana Anda dapat dengan bijaksana melakukan sesuatu tentang kondisi luar biasa, dan bahwa "menangkap, mencatat, dan melepaskan" bukanlah strategi yang baik (jika kadang-kadang tidak dapat dihindari di perpustakaan).
sumber
Saya setuju dengan arahan dasar pertanyaan Anda untuk menangani sebanyak mungkin pengecualian di tingkat terendah.
Beberapa jawaban yang ada seperti "Anda tidak perlu menangani pengecualian. Orang lain akan melakukannya di tumpukan." Bagi pengalaman saya itu adalah alasan yang buruk untuk tidak memikirkan penanganan pengecualian pada bagian kode yang saat ini dikembangkan, membuat pengecualian menangani masalah orang lain atau lambat.
Masalah itu tumbuh secara dramatis dalam pengembangan terdistribusi, di mana Anda mungkin perlu memanggil metode yang diterapkan oleh rekan kerja. Dan kemudian Anda harus memeriksa rantai panggilan metode bersarang untuk mencari tahu mengapa dia melemparkan beberapa pengecualian pada Anda, yang bisa ditangani lebih mudah pada metode bersarang yang terdalam.
sumber
Saran yang pernah diberikan profesor sains komputer saya adalah: "Gunakan blok Coba dan Tangkap hanya jika tidak mungkin untuk menangani kesalahan menggunakan cara standar."
Sebagai contoh, dia memberi tahu kami bahwa jika suatu program mengalami masalah serius di tempat yang tidak memungkinkan untuk melakukan sesuatu seperti:
Maka Anda harus menggunakan coba, tangkap balok. Meskipun Anda dapat menggunakan pengecualian untuk menangani hal ini, umumnya tidak dianjurkan karena pengecualian adalah kinerja yang bijaksana.
sumber
Jika Anda ingin menguji hasil dari setiap fungsi, gunakan kode pengembalian.
Tujuan Pengecualian adalah agar Anda dapat sering menguji hasil KURANG. Idenya adalah untuk memisahkan kondisi yang luar biasa (tidak biasa, lebih jarang) dari kode Anda yang lebih biasa. Ini membuat kode biasa lebih bersih dan sederhana - tetapi masih dapat menangani kondisi luar biasa tersebut.
Dalam kode yang dirancang dengan baik, fungsi yang lebih dalam mungkin dilemparkan dan fungsi yang lebih tinggi mungkin tertangkap. Tetapi kuncinya adalah bahwa banyak fungsi "di antara" akan bebas dari beban menangani kondisi yang luar biasa sama sekali. Mereka hanya harus "pengecualian aman", yang tidak berarti mereka harus menangkap.
sumber
Saya ingin menambahkan diskusi ini bahwa, sejak C ++ 11 , itu membuat banyak akal, selama setiap
catch
blokrethrow
s pengecualian sampai titik itu bisa / harus ditangani. Dengan cara ini backtrace dapat dihasilkan . Karena itu saya percaya pendapat sebelumnya sebagian sudah usang.Gunakan
std::nested_exception
danstd::throw_with_nested
Dijelaskan pada StackOverflow di sini dan di sini cara mencapai ini.
Karena Anda bisa melakukan ini dengan kelas pengecualian apa pun, Anda dapat menambahkan banyak informasi ke backtrace tersebut! Anda juga dapat melihat MWE saya di GitHub , di mana backtrace akan terlihat seperti ini:
sumber
Saya diberi "kesempatan" untuk menyelamatkan beberapa proyek dan eksekutif menggantikan seluruh tim dev karena aplikasi memiliki terlalu banyak kesalahan dan pengguna sudah bosan dengan masalah dan kehabisan. Semua basis kode ini memiliki penanganan kesalahan terpusat di tingkat aplikasi seperti dijelaskan oleh jawaban terpilih teratas. Jika jawaban itu adalah praktik terbaik mengapa tidak berhasil dan membiarkan tim dev sebelumnya menyelesaikan masalah? Mungkin kadang tidak berhasil? Jawaban di atas tidak menyebutkan berapa lama dev menghabiskan waktu untuk memperbaiki satu masalah. Jika waktu untuk menyelesaikan masalah adalah metrik kunci, menginstruksikan kode dengan try..catch block adalah praktik yang lebih baik.
Bagaimana tim saya memperbaiki masalah tanpa secara signifikan mengubah UI? Sederhana, setiap metode diinstrumentasi dengan try..catch diblokir dan semuanya dicatat pada titik kegagalan dengan nama metode, nilai parameter metode disatukan menjadi string yang diteruskan bersama dengan pesan kesalahan, pesan kesalahan, nama aplikasi, tanggal, dan versi. Dengan informasi ini pengembang dapat menjalankan analitik pada kesalahan untuk mengidentifikasi pengecualian yang paling banyak terjadi! Atau namespace dengan jumlah kesalahan tertinggi. Itu juga dapat memvalidasi bahwa kesalahan yang terjadi dalam modul ditangani dengan benar dan tidak disebabkan oleh beberapa alasan.
Manfaat pro lainnya dari ini adalah pengembang dapat menetapkan satu break-point dalam metode logging kesalahan dan dengan satu break-point dan satu klik tombol debug "step out", mereka berada dalam metode yang gagal dengan akses penuh ke aktual objek pada titik kegagalan, mudah tersedia di jendela langsung. Itu membuatnya sangat mudah untuk debug dan memungkinkan menyeret eksekusi kembali ke awal metode untuk menduplikasi masalah untuk menemukan baris yang tepat. Apakah penanganan pengecualian terpusat memungkinkan pengembang untuk mereplikasi pengecualian dalam 30 detik? Tidak.
Pernyataan "Suatu metode seharusnya hanya menangkap pengecualian ketika dapat menanganinya dengan cara yang masuk akal." Ini menyiratkan bahwa pengembang dapat memprediksi atau akan menghadapi setiap kesalahan yang dapat terjadi sebelum rilis. Jika ini benar tingkat atas, penangan pengecualian aplikasi tidak akan diperlukan dan tidak akan ada pasar untuk Pencarian Elastis dan logstash.
Pendekatan ini juga memungkinkan pengembang menemukan dan memperbaiki masalah berselang dalam produksi! Apakah Anda ingin men-debug tanpa debugger dalam produksi? Atau apakah Anda lebih suka menerima telepon dan menerima email dari pengguna yang kesal? Ini memungkinkan Anda untuk memperbaiki masalah sebelum orang lain tahu dan tanpa harus mengirim email, IM, atau Slack dengan dukungan karena semua yang diperlukan untuk memperbaiki masalah ada di sana. 95% masalah tidak perlu direproduksi.
Agar berfungsi dengan benar, perlu dikombinasikan dengan logging terpusat yang dapat menangkap namespace / modul, nama kelas, metode, input, dan pesan kesalahan dan menyimpannya dalam database sehingga dapat digabungkan untuk menyoroti metode mana yang paling gagal sehingga dapat diperbaiki dulu.
Terkadang pengembang memilih untuk melemparkan pengecualian ke tumpukan dari blok tangkap tetapi pendekatan ini 100 kali lebih lambat dari kode normal yang tidak melempar. Menangkap dan melepaskan dengan logging lebih disukai.
Teknik ini digunakan untuk menstabilkan aplikasi dengan cepat yang gagal setiap jam bagi sebagian besar pengguna di perusahaan Fortune 500 yang dikembangkan oleh 12 Devs selama 2 tahun. Dengan menggunakan 3000 pengecualian ini, diidentifikasi, diperbaiki, diuji, dan digunakan dalam 4 bulan. Ini rata-rata untuk memperbaiki setiap 15 menit rata-rata selama 4 bulan.
Saya setuju bahwa tidak menyenangkan untuk mengetik semua yang diperlukan untuk instrumen kode dan saya lebih suka untuk tidak melihat kode berulang, tetapi menambahkan 4 baris kode untuk setiap metode tidak sia-sia dalam jangka panjang.
sumber
Selain saran di atas, secara pribadi saya menggunakan beberapa try + catch + throw; karena alasan berikut:
sumber
Saya merasa terdorong untuk menambahkan jawaban lain meskipun jawaban Mike Wheat merangkum poin utama dengan cukup baik. Saya memikirkannya seperti ini. Ketika Anda memiliki metode yang melakukan banyak hal, Anda mengalikan kompleksitas, bukan menambahkannya.
Dengan kata lain, metode yang dibungkus dengan try catch memiliki dua hasil yang mungkin. Anda memiliki hasil non-pengecualian dan hasil pengecualian. Ketika Anda berurusan dengan banyak metode, ini meledak secara eksponensial di luar pemahaman.
Secara eksponensial karena jika setiap metode bercabang dengan dua cara berbeda maka setiap kali Anda memanggil metode lain, Anda mengkuadratkan jumlah hasil potensial sebelumnya. Pada saat Anda telah memanggil lima metode, Anda mencapai 256 kemungkinan hasil minimal. Bandingkan ini dengan tidak melakukan try / catch di setiap metode tunggal dan Anda hanya memiliki satu jalur untuk diikuti.
Pada dasarnya itulah yang saya lihat. Anda mungkin tergoda untuk berpendapat bahwa segala jenis percabangan melakukan hal yang sama tetapi percobaan / tangkapan adalah kasus khusus karena keadaan aplikasi pada dasarnya menjadi tidak terdefinisi.
Jadi singkatnya, coba / tangkap membuat kode jauh lebih sulit untuk dipahami.
sumber
Anda tidak perlu menutupi setiap bagian dari kode Anda di dalamnya
try-catch
. Penggunaan utamatry-catch
blok ini adalah untuk penanganan kesalahan dan mendapat bug / pengecualian dalam program Anda. Beberapa penggunaantry-catch
-try-catch
blok.sumber
try
/catch
adalah completelly terpisah / orthogonal dari itu. Jika Anda ingin membuang objek dalam ruang lingkup yang lebih kecil, Anda bisa membuka yang baru{ Block likeThis; /* <- that object is destroyed here -> */ }
- tidak perlu membungkus ini ditry
/catch
kecuali Anda benar-benar perlucatch
sesuatu, tentu saja.