Pertanyaan saya adalah apa yang paling disukai pengembang untuk penanganan kesalahan, Pengecualian atau Kode Pengembalian Kesalahan. Harap spesifiklah bahasa (atau rumpun bahasa) dan mengapa Anda lebih memilih salah satu daripada yang lain.
Saya menanyakan ini karena penasaran. Secara pribadi saya lebih suka Error Return Codes karena mereka kurang eksplosif dan tidak memaksa kode pengguna untuk membayar penalti kinerja pengecualian jika mereka tidak mau.
update: terima kasih atas semua jawaban! Saya harus mengatakan bahwa meskipun saya tidak suka alur kode yang tidak dapat diprediksi dengan pengecualian. Jawaban tentang kode kembali (dan kakak mereka menangani) memang menambahkan banyak Kebisingan ke kode.
language-agnostic
exception
Robert Gould
sumber
sumber
Jawaban:
Untuk beberapa bahasa (yaitu C ++) Kebocoran sumber daya seharusnya tidak menjadi alasan
C ++ didasarkan pada RAII.
Jika Anda memiliki kode yang bisa gagal, dikembalikan atau dibuang (yaitu, sebagian besar kode normal), maka Anda harus membungkus penunjuk Anda di dalam penunjuk cerdas (dengan asumsi Anda memiliki alasan yang sangat bagus untuk tidak membuat objek Anda di tumpukan).
Kode pengembalian lebih bertele-tele
Mereka bertele-tele, dan cenderung berkembang menjadi seperti:
Pada akhirnya, kode Anda adalah kumpulan instruksi yang teridentifikasi (saya melihat kode semacam ini dalam kode produksi).
Kode ini dapat dengan baik diterjemahkan menjadi:
Yang memisahkan kode dan pemrosesan kesalahan dengan rapi, yang bisa menjadi hal yang baik .
Kode pengembalian lebih rapuh
Jika tidak ada peringatan yang tidak jelas dari satu kompilator (lihat komentar "phjr"), mereka dapat dengan mudah diabaikan.
Dengan contoh di atas, asumsikan daripada seseorang lupa menangani kemungkinan kesalahannya (ini terjadi ...). Kesalahan diabaikan saat "dikembalikan", dan mungkin akan meledak nanti (yaitu penunjuk NULL). Masalah yang sama tidak akan terjadi dengan pengecualian.
Kesalahan tidak akan diabaikan. Terkadang, Anda ingin agar tidak meledak ... Jadi, Anda harus memilih dengan hati-hati.
Kode Pengembalian terkadang harus diterjemahkan
Katakanlah kita memiliki fungsi berikut:
Apa jenis kembalinya doTryToDoSomethingWithAllThisMess jika salah satu fungsi yang dipanggilnya gagal?
Kode Pengembalian bukanlah solusi universal
Operator tidak dapat mengembalikan kode kesalahan. Konstruktor C ++ juga tidak bisa.
Return Codes berarti Anda tidak dapat merangkai ekspresi
Akibat wajar dari poin di atas. Bagaimana jika saya ingin menulis:
Saya tidak bisa, karena nilai pengembalian sudah digunakan (dan terkadang, tidak bisa diubah). Jadi nilai kembali menjadi parameter pertama, dikirim sebagai referensi ... Atau tidak.
Pengecualian diketik
Anda dapat mengirim kelas yang berbeda untuk setiap jenis pengecualian. Pengecualian sumber daya (mis. Kehabisan memori) harus ringan, tetapi hal lain bisa seberat yang diperlukan (saya suka Java Exception memberi saya seluruh tumpukan).
Setiap tangkapan kemudian dapat dikhususkan.
Jangan pernah menggunakan tangkapan (...) tanpa melempar ulang
Biasanya, Anda tidak boleh menyembunyikan kesalahan. Jika Anda tidak membuang kembali, setidaknya, catat kesalahan dalam file, buka kotak pesan, apa pun ...
Pengecualiannya adalah ... NUKE
Masalah dengan pengecualian adalah bahwa menggunakannya secara berlebihan akan menghasilkan kode yang penuh dengan percobaan / tangkapan. Tapi masalahnya ada di tempat lain: Siapa yang mencoba / menangkap kodenya menggunakan kontainer STL? Namun, kontainer tersebut dapat mengirimkan pengecualian.
Tentu saja, di C ++, jangan pernah membiarkan pengecualian keluar dari destruktor.
Pengecualian adalah ... sinkron
Pastikan untuk menangkapnya sebelum mereka mengeluarkan utas Anda, atau menyebar di dalam loop pesan Windows Anda.
Solusinya bisa mencampurkannya?
Jadi saya kira solusinya adalah membuang ketika sesuatu tidak seharusnya terjadi. Dan ketika sesuatu bisa terjadi, maka gunakan kode kembali atau parameter untuk memungkinkan pengguna bereaksi terhadapnya.
Jadi, satu-satunya pertanyaan adalah "apa yang seharusnya tidak terjadi?"
Itu tergantung pada kontrak fungsi Anda. Jika fungsi menerima pointer, tetapi menentukan pointer harus non-NULL, maka boleh saja untuk membuat pengecualian saat pengguna mengirim pointer NULL (pertanyaannya adalah, dalam C ++, ketika pembuat fungsi tidak menggunakan referensi sebagai gantinya petunjuk, tapi ...)
Solusi lain adalah menunjukkan kesalahan
Terkadang, masalah Anda adalah Anda tidak menginginkan kesalahan. Menggunakan pengecualian atau kode pengembalian kesalahan itu keren, tapi ... Anda ingin mengetahuinya.
Dalam pekerjaan saya, kami menggunakan semacam "Assert". Ini akan, tergantung pada nilai file konfigurasi, tidak peduli opsi kompilasi debug / rilis:
Dalam pengembangan dan pengujian, ini memungkinkan pengguna untuk menunjukkan masalah dengan tepat ketika terdeteksi, dan bukan setelahnya (ketika beberapa kode peduli tentang nilai yang dikembalikan, atau di dalam tangkapan).
Mudah untuk ditambahkan ke kode lama. Sebagai contoh:
mengarahkan semacam kode yang mirip dengan:
(Saya memiliki makro serupa yang hanya aktif di debug).
Perhatikan bahwa pada produksi, file konfigurasi tidak ada, jadi klien tidak pernah melihat hasil makro ini ... Tetapi mudah untuk mengaktifkannya bila diperlukan.
Kesimpulan
Ketika Anda membuat kode menggunakan kode kembali, Anda mempersiapkan diri untuk kegagalan, dan berharap benteng pengujian Anda cukup aman.
Ketika Anda membuat kode menggunakan pengecualian, Anda tahu bahwa kode Anda bisa gagal, dan biasanya menempatkan tangkapan balasan di posisi strategis yang dipilih dalam kode Anda. Tapi biasanya, kode Anda lebih banyak tentang "apa yang harus dilakukan" lalu "apa yang saya khawatirkan akan terjadi".
Tetapi ketika Anda membuat kode sama sekali, Anda harus menggunakan alat terbaik yang Anda inginkan, dan terkadang, itu adalah "Jangan pernah menyembunyikan kesalahan, dan tunjukkan secepat mungkin". Makro yang saya bicarakan di atas mengikuti filosofi ini.
sumber
if( !doSomething() ) { puts( "ERROR - doSomething failed" ) ; return ; // or react to failure of doSomething } if( !doSomethingElse() ) { // react to failure of doSomethingElse() }
doSomething(); doSomethingElse(); ...
lebih baik karena jika saya perlu menambahkan if / while / etc. pernyataan untuk tujuan eksekusi normal, saya tidak ingin mereka dicampur dengan if / while / etc. pernyataan ditambahkan untuk tujuan luar biasa ... Dan karena aturan sebenarnya tentang menggunakan pengecualian adalah melempar , bukan menangkap , pernyataan coba / tangkap biasanya tidak invasif.Saya menggunakan keduanya sebenarnya.
Saya menggunakan kode pengembalian jika itu diketahui, kemungkinan kesalahan. Jika itu skenario yang saya tahu bisa, dan akan terjadi, maka ada kode yang dikirim kembali.
Pengecualian hanya digunakan untuk hal-hal yang TIDAK SAYA harapkan.
sumber
Menurut Bab 7 berjudul "Pengecualian" dalam Panduan Desain Kerangka Kerja: Konvensi, Idiom, dan Pola untuk Pustaka .NET yang Dapat Digunakan Kembali , banyak alasan yang diberikan mengapa menggunakan pengecualian atas nilai yang dikembalikan diperlukan untuk kerangka kerja OO seperti C #.
Mungkin inilah alasan yang paling kuat (halaman 179):
"Pengecualian terintegrasi dengan baik dengan bahasa berorientasi objek. Bahasa berorientasi objek cenderung memberlakukan batasan pada tanda tangan anggota yang tidak dikenakan oleh fungsi dalam bahasa non-OO. Misalnya, dalam kasus konstruktor, operator kelebihan beban, dan properti, pengembang tidak memiliki pilihan dalam nilai pengembalian. Oleh karena itu, tidak mungkin untuk menstandarisasi pelaporan kesalahan berbasis nilai-kembali untuk kerangka kerja berorientasi objek. Metode pelaporan kesalahan, seperti pengecualian, yang berada di luar jalur tanda tangan metode adalah satu-satunya pilihan. "
sumber
Preferensi saya (dalam C ++ dan Python) adalah menggunakan pengecualian. Fasilitas yang disediakan bahasa membuatnya menjadi proses yang terdefinisi dengan baik untuk menaikkan, menangkap, dan (jika perlu) membuang kembali pengecualian, membuat model mudah dilihat dan digunakan. Secara konseptual, ini lebih bersih daripada kode yang dikembalikan, dalam pengecualian spesifik itu dapat ditentukan oleh namanya, dan memiliki informasi tambahan yang menyertainya. Dengan kode pengembalian, Anda dibatasi hanya pada nilai kesalahan (kecuali Anda ingin mendefinisikan objek ReturnStatus atau sesuatu).
Kecuali kode yang Anda tulis sangat menentukan waktu, overhead yang terkait dengan pelepasan tumpukan tidak cukup signifikan untuk dikhawatirkan.
sumber
Pengecualian hanya boleh dikembalikan jika terjadi sesuatu yang tidak Anda duga.
Titik pengecualian lainnya, secara historis, adalah bahwa kode yang dikembalikan secara inheren merupakan hak milik, terkadang 0 dapat dikembalikan dari fungsi C untuk menunjukkan keberhasilan, terkadang -1, atau salah satu dari mereka untuk gagal dengan 1 untuk kesuksesan. Bahkan ketika mereka dicacah, pencacahan bisa menjadi ambigu.
Pengecualian juga dapat memberikan lebih banyak informasi, dan secara khusus menguraikan dengan baik 'Sesuatu yang Salah, inilah yang, pelacakan tumpukan dan beberapa informasi pendukung untuk konteks'
Karena itu, kode pengembalian yang disebutkan dengan baik dapat berguna untuk serangkaian hasil yang diketahui, 'berikut ini hasil dari fungsi, dan berjalan seperti ini'
sumber
Di Java, saya menggunakan (dengan urutan berikut):
Desain demi kontrak (memastikan prasyarat terpenuhi sebelum mencoba apa pun yang mungkin gagal). Ini menangkap banyak hal dan saya mengembalikan kode kesalahan untuk ini.
Mengembalikan kode kesalahan saat memproses pekerjaan (dan melakukan rollback jika diperlukan).
Pengecualian, tetapi ini hanya digunakan untuk hal-hal yang tidak terduga.
sumber
assert
yang tidak akan menemukan masalah dalam kode produksi kecuali Anda menjalankan dengan pernyataan di sepanjang waktu. Saya cenderung melihat pernyataan sebagai cara untuk menangkap masalah selama pengembangan, tetapi hanya untuk hal-hal yang akan menegaskan atau tidak menegaskan secara konsisten (seperti hal-hal dengan konstanta, bukan hal-hal dengan variabel atau apa pun yang dapat berubah saat runtime).Kode yang dikembalikan gagal dalam tes " Lubang Kesuksesan " bagi saya hampir setiap saat.
Mengenai Kinerja:
sumber
Sebuah nasihat hebat yang saya dapatkan dari The Pragmatic Programmer adalah sesuatu yang sejalan dengan "program Anda harus dapat melakukan semua fungsi utamanya tanpa menggunakan pengecualian sama sekali".
sumber
Saya menulis posting blog tentang ini beberapa waktu yang lalu.
Overhead kinerja dari melakukan pengecualian seharusnya tidak memainkan peran apa pun dalam keputusan Anda. Jika Anda melakukannya dengan benar, bagaimanapun juga, pengecualian adalah pengecualian .
sumber
Saya tidak suka kode balik karena menyebabkan pola berikut menjamur di seluruh kode Anda
CRetType obReturn = CODE_SUCCESS; obReturn = CallMyFunctionWhichReturnsCodes(); if (obReturn == CODE_BLOW_UP) { // bail out goto FunctionExit; }
Segera pemanggilan metode yang terdiri dari 4 pemanggilan fungsi membengkak dengan 12 baris penanganan kesalahan .. Beberapa di antaranya tidak akan pernah terjadi. Jika dan kasus sakelar berlimpah.
Pengecualian lebih bersih jika Anda menggunakannya dengan baik ... untuk menandakan peristiwa luar biasa .. setelah itu jalur eksekusi tidak dapat dilanjutkan. Mereka seringkali lebih deskriptif dan informasi daripada kode kesalahan.
Jika Anda memiliki beberapa status setelah panggilan metode yang harus ditangani secara berbeda (dan bukan kasus luar biasa), gunakan kode kesalahan atau parameter keluar. Meskipun Personaly, saya menganggap ini langka ..
Saya telah mencari sedikit tentang argumen 'penalti kinerja' .. lebih banyak di dunia C ++ / COM tetapi dalam bahasa yang lebih baru, saya pikir perbedaannya tidak terlalu banyak. Bagaimanapun, ketika sesuatu meledak, masalah kinerja dialihkan ke backburner :)
sumber
Dengan pengecualian lingkungan runtime atau compiler yang layak, tidak ada hukuman yang signifikan. Ini kurang lebih seperti pernyataan GOTO yang melompat ke penangan pengecualian. Selain itu, memiliki pengecualian yang ditangkap oleh lingkungan runtime (seperti JVM) membantu mengisolasi dan memperbaiki bug jauh lebih mudah. Saya akan menggunakan NullPointerException di Java daripada segfault di C setiap hari.
sumber
finally
blok dan penghancur untuk objek yang dialokasikan oleh tumpukan.Saya memiliki seperangkat aturan sederhana:
1) Gunakan kode balasan untuk hal-hal yang Anda harapkan akan ditanggapi oleh penelepon langsung Anda.
2) Gunakan pengecualian untuk kesalahan yang lebih luas cakupannya, dan mungkin wajar diharapkan untuk ditangani oleh sesuatu yang banyak tingkat di atas pemanggil sehingga kesadaran kesalahan tidak harus meresap melalui banyak lapisan, membuat kode lebih kompleks.
Di Java saya hanya pernah menggunakan pengecualian yang tidak dicentang, pengecualian yang dicentang akhirnya hanya menjadi bentuk lain dari kode pengembalian dan menurut pengalaman saya dualitas dari apa yang mungkin "dikembalikan" oleh panggilan metode umumnya lebih merupakan penghalang daripada bantuan.
sumber
Saya menggunakan Pengecualian di python baik dalam keadaan Luar Biasa, maupun non-Luar Biasa.
Seringkali menyenangkan dapat menggunakan Exception untuk menunjukkan "permintaan tidak dapat dilakukan", sebagai lawan mengembalikan nilai Error. Ini berarti Anda / selalu / tahu bahwa nilai yang dikembalikan adalah tipe yang benar, bukan secara arbitarily None atau NotFoundSingleton atau semacamnya. Berikut adalah contoh bagus di mana saya lebih suka menggunakan penangan pengecualian daripada kondisional pada nilai yang dikembalikan.
Efek sampingnya adalah ketika datastore.fetch (obj_id) dijalankan, Anda tidak perlu memeriksa apakah nilai kembaliannya adalah None, Anda langsung mendapatkan kesalahan itu secara gratis. Ini berlawanan dengan argumen, "program Anda harus dapat menjalankan semua fungsi utamanya tanpa menggunakan pengecualian sama sekali".
Berikut adalah contoh lain di mana pengecualian 'sangat berguna', untuk menulis kode untuk menangani sistem file yang tidak tunduk pada kondisi balapan.
Satu panggilan sistem, bukan dua, tidak ada kondisi balapan. Ini adalah contoh yang buruk karena jelas ini akan gagal dengan OSError dalam lebih banyak keadaan daripada direktori yang tidak ada, tetapi ini adalah solusi yang 'cukup baik' untuk banyak situasi yang dikontrol dengan ketat.
sumber
Saya percaya kode kembali menambah kebisingan kode. Misalnya, saya selalu membenci tampilan kode COM / ATL karena kode yang dikembalikan. Harus ada pemeriksaan HRESULT untuk setiap baris kode. Saya menganggap kode pengembalian kesalahan adalah salah satu keputusan buruk yang dibuat oleh arsitek COM. Ini membuat sulit untuk melakukan pengelompokan kode secara logis, sehingga peninjauan kode menjadi sulit.
Saya tidak yakin tentang perbandingan kinerja ketika ada pemeriksaan eksplisit untuk kode pengembalian setiap baris.
sumber
Saya lebih suka menggunakan pengecualian untuk penanganan kesalahan dan mengembalikan nilai (atau parameter) sebagai hasil normal dari suatu fungsi. Ini memberikan skema penanganan kesalahan yang mudah dan konsisten dan jika dilakukan dengan benar itu membuat kode tampak lebih bersih.
sumber
Salah satu perbedaan besar adalah bahwa pengecualian memaksa Anda untuk menangani kesalahan, sedangkan kode pengembalian kesalahan bisa tidak dicentang.
Kesalahan mengembalikan kode, jika sering digunakan, juga dapat menyebabkan kode yang sangat jelek dengan banyak tes if yang mirip dengan formulir ini:
Secara pribadi saya lebih suka menggunakan pengecualian untuk kesalahan yang HARUS atau HARUS ditindaklanjuti oleh kode panggilan, dan hanya menggunakan kode kesalahan untuk "kegagalan yang diharapkan" di mana mengembalikan sesuatu benar-benar valid dan mungkin.
sumber
Ada banyak alasan untuk memilih Pengecualian daripada kode pengembalian:
sumber
Pengecualian bukan untuk penanganan kesalahan, IMO. Pengecualian hanya itu; peristiwa luar biasa yang tidak Anda duga. Gunakan dengan hati-hati kataku.
Kode kesalahan bisa saja OK, tetapi mengembalikan 404 atau 200 dari metode itu buruk, IMO. Gunakan enums (.Net) sebagai gantinya, yang membuat kode lebih mudah dibaca dan lebih mudah digunakan untuk pengembang lain. Anda juga tidak perlu mempertahankan tabel atas angka dan deskripsi.
Juga; pola coba-tangkap-akhirnya adalah anti-pola dalam buku saya. Coba-akhirnya bisa bagus, coba-tangkap juga bisa bagus tapi coba-tangkap-akhirnya tidak pernah bagus. try-akhirnya sering kali dapat diganti dengan pernyataan "using" (IDispose pattern), yang merupakan IMO yang lebih baik. Dan Coba-tangkap di mana Anda benar-benar menangkap pengecualian yang dapat Anda tangani itu baik, atau jika Anda melakukan ini:
Jadi selama Anda membiarkan pengecualian terus menggelembung, tidak apa-apa. Contoh lainnya adalah ini:
Di sini saya benar-benar menangani pengecualian; apa yang saya lakukan di luar uji coba ketika metode pembaruan gagal adalah cerita lain, tetapi saya pikir maksud saya telah dibuat. :)
Mengapa kemudian coba-tangkap-akhirnya menjadi anti-pola? Inilah alasannya:
Apa yang terjadi jika objek db telah ditutup? Pengecualian baru dilemparkan dan itu harus ditangani! Ini lebih baik:
Atau, jika objek db tidak mengimplementasikan IDisposable lakukan ini:
Itu juga 2 sen saya! :)
sumber
Saya hanya menggunakan pengecualian, tidak ada kode pengembalian. Saya berbicara tentang Java di sini.
Aturan umum yang saya ikuti adalah jika saya memiliki metode yang dipanggil
doFoo()
maka diikuti bahwa jika tidak "melakukan foo", sebagaimana adanya, maka sesuatu yang luar biasa telah terjadi dan Exception harus dilemparkan.sumber
Satu hal yang saya takuti tentang pengecualian adalah bahwa memberikan pengecualian akan mengacaukan aliran kode. Misalnya jika Anda melakukannya
Atau lebih buruk lagi bagaimana jika saya menghapus sesuatu yang seharusnya tidak saya miliki, tetapi terlempar sebelum saya melakukan pembersihan selanjutnya. Melempar memberi banyak beban pada IMHO pengguna yang buruk.
sumber
Aturan umum saya dalam argumen pengecualian vs. kode pengembalian:
sumber
Saya tidak menemukan kode pengembalian kurang jelek dari pada pengecualian. Dengan pengecualian, Anda memiliki
try{} catch() {} finally {}
tempat seperti dengan kode pengembalian yang Anda milikiif(){}
. Saya dulu takut akan pengecualian karena alasan yang diberikan di pos; Anda tidak tahu apakah penunjuk perlu dibersihkan, apa yang Anda miliki. Tapi saya pikir Anda memiliki masalah yang sama dalam hal kode pengembalian. Anda tidak mengetahui status parameter kecuali Anda mengetahui beberapa detail tentang fungsi / metode yang dimaksud.Terlepas dari itu, Anda harus menangani kesalahan jika memungkinkan. Anda dapat dengan mudah membiarkan pengecualian menyebar ke tingkat atas seperti mengabaikan kode yang dikembalikan dan membiarkan program segfault.
Saya menyukai gagasan mengembalikan nilai (pencacahan?) Untuk hasil dan pengecualian untuk kasus luar biasa.
sumber
Untuk bahasa seperti Java, saya akan menggunakan Exception karena kompilator memberikan kesalahan waktu kompilasi jika pengecualian tidak ditangani. Ini memaksa fungsi pemanggil untuk menangani / membuang pengecualian.
Untuk Python, saya lebih bingung. Tidak ada kompilator sehingga pemanggil mungkin tidak menangani pengecualian yang dilemparkan oleh fungsi yang mengarah ke pengecualian waktu proses. Jika Anda menggunakan kode pengembalian, Anda mungkin memiliki perilaku yang tidak terduga jika tidak ditangani dengan benar dan jika Anda menggunakan pengecualian, Anda mungkin mendapatkan pengecualian waktu proses.
sumber
Saya biasanya lebih suka kode pengembalian karena mereka membiarkan pemanggil memutuskan apakah kegagalannya luar biasa .
Pendekatan ini tipikal dalam bahasa Elixir.
Orang-orang menyebutkan bahwa kode yang dikembalikan dapat menyebabkan Anda memiliki banyak
if
pernyataan bersarang , tetapi itu dapat ditangani dengan sintaks yang lebih baik. Di Elixir,with
pernyataan tersebut memungkinkan kita dengan mudah memisahkan serangkaian nilai kembalian jalur bahagia dari kegagalan apa pun.Elixir masih memiliki fungsi yang memunculkan pengecualian. Kembali ke contoh pertama saya, saya dapat melakukan salah satu dari ini untuk mengajukan pengecualian jika file tidak dapat ditulis.
Jika saya, sebagai penelepon, tahu bahwa saya ingin memunculkan kesalahan jika penulisan gagal, saya dapat memilih untuk menelepon
File.write!
daripadaFile.write
. Atau saya dapat memilih untuk meneleponFile.write
dan menangani setiap kemungkinan alasan kegagalan secara berbeda.Tentu saja selalu mungkin untuk
rescue
pengecualian jika kita mau. Tetapi dibandingkan dengan menangani nilai pengembalian yang informatif, rasanya canggung bagi saya. Jika saya tahu bahwa panggilan fungsi bisa gagal atau bahkan harus gagal, kegagalannya bukanlah kasus yang luar biasa.sumber