Saya mencoba meyakinkan pimpinan tim saya untuk mengizinkan menggunakan pengecualian dalam C ++ alih-alih mengembalikan bool isSuccessful
atau enum dengan kode kesalahan. Namun, saya tidak bisa melawan kritiknya ini.
Pertimbangkan perpustakaan ini:
class OpenFileException() : public std::runtime_error {
}
void B();
void C();
/** Does blah and blah. */
void B() {
// The developer of B() either forgot to handle C()'s exception or
// chooses not to handle it and let it go up the stack.
C();
};
/** Does blah blah.
*
* @raise OpenFileException When we failed to open the file. */
void C() {
throw new OpenFileException();
};
Pertimbangkan pengembang memanggil
B()
fungsi. Dia memeriksa dokumentasinya dan melihat bahwa itu tidak menghasilkan pengecualian, jadi dia tidak mencoba menangkap apa pun. Kode ini dapat menyebabkan crash program dalam produksi.Pertimbangkan pengembang memanggil
C()
fungsi. Dia tidak memeriksa dokumentasinya jadi tidak menangkap pengecualian. Panggilan tidak aman dan dapat menyebabkan crash program dalam produksi.
Tetapi jika kita memeriksa kesalahan dengan cara ini:
void old_C(myenum &return_code);
Pengembang yang menggunakan fungsi itu akan diperingatkan oleh kompiler jika dia tidak memberikan argumen itu, dan dia akan berkata "Aha, ini mengembalikan kode kesalahan yang harus saya periksa."
Bagaimana saya bisa menggunakan pengecualian dengan aman, sehingga ada semacam kontrak?
sumber
Either
/Result
monad untuk mengembalikan kesalahan dengan cara yang dapat dikomposisikan dengan tipe amanJawaban:
Ini adalah kritik yang sah terhadap pengecualian. Mereka sering kurang terlihat daripada penanganan kesalahan sederhana seperti mengembalikan kode. Dan tidak ada cara mudah untuk menegakkan "kontrak". Bagian dari intinya adalah untuk memungkinkan Anda membiarkan pengecualian ditangkap pada level yang lebih tinggi (jika Anda harus menangkap setiap pengecualian di setiap level, seberapa berbedanya dengan mengembalikan kode kesalahan, toh?). Dan ini berarti bahwa kode Anda dapat dipanggil oleh beberapa kode lain yang tidak menanganinya dengan tepat.
Pengecualian memang memiliki kelemahan; Anda harus membuat kasing berdasarkan biaya-manfaat.
Saya menemukan dua artikel ini bermanfaat: Perlunya pengecualian dan Semuanya salah dengan pengecualian. Juga, posting blog ini menawarkan pendapat banyak pakar tentang pengecualian, dengan fokus pada C ++ . Sementara pendapat para ahli tampaknya lebih condong pada pengecualian, itu jauh dari konsensus yang jelas.
Sedangkan untuk meyakinkan pimpinan tim Anda, ini mungkin bukan pertempuran yang tepat untuk dipilih. Terutama tidak dengan kode lama. Seperti disebutkan dalam tautan kedua di atas:
Menambahkan sedikit kode yang menggunakan pengecualian untuk proyek yang sebagian besar tidak mungkin tidak akan menjadi perbaikan. Tidak menggunakan pengecualian dalam kode yang ditulis dengan baik adalah jauh dari masalah bencana; itu mungkin tidak menjadi masalah sama sekali, tergantung pada aplikasi dan ahli yang Anda tanyakan. Anda harus memilih pertempuran Anda.
Ini mungkin bukan argumen yang akan saya habiskan - setidaknya tidak sampai proyek baru dimulai. Dan bahkan jika Anda memiliki proyek baru, apakah itu akan digunakan atau digunakan oleh kode warisan apa pun?
sumber
Secara harfiah ada banyak buku yang ditulis tentang hal ini, jadi jawaban apa pun akan menjadi ringkasan terbaik. Berikut adalah beberapa poin penting yang menurut saya layak untuk dibuat, berdasarkan pertanyaan Anda. Ini bukan daftar lengkap.
Pengecualian dimaksudkan untuk TIDAK ditangkap di semua tempat.
Selama ada penangan pengecualian umum di loop utama - tergantung pada jenis aplikasi (server web, layanan lokal, utilitas baris perintah ...) - Anda biasanya memiliki semua penangan pengecualian yang Anda butuhkan.
Dalam kode saya, hanya ada beberapa pernyataan tangkap - jika ada - di luar loop utama. Dan itu tampaknya menjadi pendekatan umum dalam C ++ modern.
Pengecualian dan kode pengembalian tidak saling eksklusif.
Anda seharusnya tidak menjadikan ini pendekatan semua-atau-tidak sama sekali. Pengecualian harus digunakan untuk situasi luar biasa. Hal-hal seperti "File konfigurasi tidak ditemukan," "Disk Full" atau apa pun yang tidak dapat ditangani secara lokal.
Kegagalan umum, seperti memeriksa apakah nama file yang diberikan oleh pengguna valid, bukan merupakan use case untuk pengecualian; Sebagai gantinya gunakan nilai balik.
Seperti yang Anda lihat dari contoh di atas, "file tidak ditemukan" dapat berupa pengecualian atau kode pengembalian, tergantung pada kasus penggunaan: "adalah bagian dari instalasi" versus "pengguna dapat membuat kesalahan ketik."
Jadi tidak ada aturan absolut. Pedoman kasarnya adalah: jika bisa ditangani secara lokal, jadikan itu nilai balik; jika Anda tidak bisa mengatasinya secara lokal, berikan pengecualian.
Pemeriksaan pengecualian secara statis tidak berguna.
Karena pengecualian tidak untuk ditangani secara lokal, biasanya tidak penting pengecualian mana yang dapat dilemparkan. Satu-satunya informasi yang berguna adalah apakah ada pengecualian yang dapat dilemparkan.
Java memiliki pemeriksaan statis, tetapi biasanya dianggap percobaan yang gagal, dan sebagian besar bahasa sejak - terutama C # - tidak memiliki jenis pemeriksaan statis. Ini adalah bacaan yang bagus tentang alasan mengapa C # tidak memilikinya.
Untuk itu, C ++ telah usang yang
throw(exceptionA, exceptionB)
mendukungnoexcept(true)
. Standarnya adalah bahwa suatu fungsi dapat melempar, sehingga pemrogram harus berharap bahwa kecuali dokumentasi secara eksplisit menjanjikan sebaliknya.Menulis pengecualian kode aman tidak ada hubungannya dengan menulis penangan pengecualian.
Saya lebih suka mengatakan bahwa menulis kode aman pengecualian adalah semua tentang bagaimana menghindari menulis penangan pengecualian!
Sebagian besar praktik terbaik bermaksud mengurangi jumlah penangan pengecualian. Menulis kode sekali dan menjalankannya secara otomatis - misalnya melalui RAII - menghasilkan lebih sedikit bug daripada menyalin-menempelkan kode yang sama di semua tempat.
sumber
IOException
s juga . Divisi yang diperiksa adalah arbitrer dan tidak berguna.Pemrogram C ++ tidak mencari spesifikasi pengecualian. Mereka mencari jaminan pengecualian.
Misalkan sepotong kode tidak membuang pengecualian. Asumsi apa yang dapat dibuat oleh programmer yang masih valid? Dari cara kode ditulis, apa yang dijamin oleh kode tersebut setelah pengecualian?
Atau mungkinkah sepotong kode tertentu dapat menjamin tidak pernah membuang (yaitu tidak ada proses OS yang dihentikan)?
Kata "roll back" sering muncul dalam diskusi tentang pengecualian. Mampu memutar kembali ke status yang valid (yang didokumentasikan secara eksplisit) adalah contoh jaminan pengecualian. Jika tidak ada jaminan pengecualian, program harus berakhir di tempat karena bahkan tidak dijamin bahwa kode apa pun yang dieksekusi setelah itu akan berfungsi sebagaimana dimaksud - katakanlah, jika memori rusak, setiap operasi lebih lanjut adalah perilaku yang secara teknis tidak ditentukan.
Berbagai teknik pemrograman C ++ mempromosikan jaminan pengecualian. RAII (manajemen sumber daya berbasis lingkup) menyediakan mekanisme untuk mengeksekusi kode pembersihan dan memastikan bahwa sumber daya dirilis dalam kasus normal dan luar biasa. Membuat salinan data sebelum melakukan modifikasi pada objek memungkinkan seseorang untuk mengembalikan keadaan objek itu jika operasi gagal. Dan seterusnya.
Jawaban untuk pertanyaan StackOverflow ini memberikan pandangan sekilas pada programmer C ++ untuk memahami semua mode kegagalan yang mungkin terjadi pada kode mereka dan mencoba menjaga validitas status program meskipun gagal. Analisis baris-demi-baris dari kode C ++ menjadi kebiasaan.
Ketika berkembang di C ++ (untuk penggunaan produksi), orang tidak mampu mengabaikan detail. Juga, biner gumpalan (non-open-source) adalah kutukan dari programmer C ++. Jika saya harus memanggil beberapa biner gumpalan, dan gumpalan gagal, maka rekayasa balik adalah apa yang akan dilakukan programmer C ++ selanjutnya.
Referensi: http://en.cppreference.com/w/cpp/language/exceptions#Exception_safety - lihat di bawah Exception Safety.
C ++ gagal dalam mengimplementasikan spesifikasi pengecualian. Analisis selanjutnya dalam bahasa lain mengatakan bahwa spesifikasi pengecualian tidak praktis.
Mengapa ini merupakan upaya yang gagal: untuk menegakkannya secara ketat, itu harus menjadi bagian dari sistem tipe. Tapi ternyata tidak. Kompiler tidak memeriksa spesifikasi pengecualian.
Mengapa C ++ memilih itu, dan mengapa pengalaman dari bahasa lain (Java) membuktikan bahwa spesifikasi pengecualian diperdebatkan: Ketika seseorang memodifikasi implementasi suatu fungsi (misalnya, perlu membuat panggilan ke fungsi yang berbeda yang mungkin melempar jenis baru dari pengecualian), penegakan spesifikasi pengecualian yang ketat berarti Anda harus memperbarui spesifikasi itu juga. Ini menyebar - Anda mungkin akhirnya harus memperbarui spesifikasi pengecualian untuk puluhan atau ratusan fungsi untuk apa itu perubahan sederhana. Hal-hal menjadi lebih buruk untuk kelas dasar abstrak (antarmuka C ++ yang setara). Jika spesifikasi pengecualian diberlakukan pada antarmuka, implementasi antarmuka tidak akan diizinkan untuk memanggil fungsi yang membuang berbagai jenis pengecualian.
Referensi: http://www.gotw.ca/publications/mill22.htm
Dimulai dengan C ++ 17,
[[nodiscard]]
atribut dapat digunakan pada nilai-nilai fungsi kembali (lihat: https://stackoverflow.com/questions/39327028/can-ac-function-be-declared-such-that-the-return-value -tidak dapat diabaikan ).Jadi jika saya membuat perubahan kode dan itu memperkenalkan kondisi kegagalan jenis baru (yaitu jenis pengecualian baru), apakah ini merupakan perubahan besar? Haruskah itu memaksa penelepon untuk memperbarui kode, atau setidaknya diperingatkan tentang perubahan?
Jika Anda menerima argumen bahwa pemrogram C ++ mencari jaminan pengecualian alih-alih spesifikasi pengecualian, maka jawabannya adalah bahwa jika jenis kegagalan yang baru tidak merusak salah satu pengecualian yang menjamin kode yang sebelumnya dijanjikan, itu bukan perubahan yang berarti.
sumber
std::exception
) dan pengecualian dasar Anda sendiri akan dibuang. Tetapi ketika diterapkan pada keseluruhan proyek, itu berarti setiap fungsi tunggal akan memiliki spesifikasi yang sama: noise.catch
didasarkan pada jenis objek pengecualian, ketika dalam banyak kasus yang penting bukanlah penyebab langsung dari pengecualian, melainkan apa yang tersirat tentang status sistem. Sayangnya, tipe pengecualian umumnya tidak mengatakan apa-apa tentang apakah itu menyebabkan kode secara tidak sengaja keluar dari sepotong kode dengan cara yang meninggalkan objek dalam keadaan sebagian diperbarui (dan dengan demikian tidak valid).Benar-benar aman. Dia tidak perlu menangkap pengecualian di setiap tempat, dia hanya bisa melempar mencoba / menangkap di tempat di mana dia benar-benar dapat melakukan sesuatu yang bermanfaat tentang hal itu. Ini hanya tidak aman jika ia membiarkannya keluar dari utas, tetapi biasanya tidak sulit untuk mencegah hal itu terjadi.
sumber
C()
, dan akibatnya tidak melindungi terhadap kode setelah panggilan dilewati. Jika kode itu diperlukan untuk menjamin bahwa beberapa invarian tetap benar, jaminan itu tidak berlaku pada pengecualian pertama yang keluarC()
. Tidak ada yang namanya perangkat lunak yang benar-benar pengecualian-aman, dan pastinya tidak di mana pengecualian tidak pernah diharapkan.C()
adalah kesalahan besar. Default dalam C ++ adalah bahwa fungsi apa pun dapat melempar - itu membutuhkan eksplisitnoexcept(true)
untuk memberitahu kompiler sebaliknya. Dengan tidak adanya janji ini, seorang programmer harus berasumsi bahwa suatu fungsi dapat melempar.C()
fungsi yang pecah di hadapan pengecualian. Ini benar-benar hal yang sangat tidak bijaksana untuk dilakukan, itu pasti akan menyebabkan banyak masalah.Jika Anda sedang membangun sistem kritis, pertimbangkan untuk mengikuti saran ketua tim Anda dan jangan gunakan pengecualian. Ini adalah AV Rule 208 dalam Standar Coding C ++ dari Joint Strike Fighter Air Vehicle . Di sisi lain, pedoman MISRA C ++ memiliki aturan yang sangat spesifik mengenai kapan pengecualian dapat dan tidak dapat digunakan jika Anda sedang membangun sistem perangkat lunak yang sesuai dengan MISRA.
Jika Anda membangun sistem kritis, kemungkinan Anda juga menjalankan alat analisis statis. Banyak alat analisis statis akan memperingatkan jika Anda tidak memeriksa nilai pengembalian suatu metode, membuat kasus penanganan kesalahan yang hilang mudah terlihat. Sepengetahuan saya, dukungan alat serupa untuk mendeteksi penanganan pengecualian yang tepat tidak sekuat.
Pada akhirnya, saya berpendapat bahwa desain berdasarkan kontrak dan pemrograman defensif, ditambah dengan analisis statis lebih aman untuk sistem perangkat lunak penting daripada pengecualian.
sumber