Penggunaan pengecualian secara otomatis dalam C ++

16

Pernyataan FAQ pengecualian isocpp.org menyatakan

Jangan gunakan lemparan untuk menunjukkan kesalahan pengkodean dalam penggunaan suatu fungsi. Gunakan assert atau mekanisme lain untuk mengirim proses ke debugger atau untuk menghentikan proses dan mengumpulkan crash dump untuk pengembang untuk debug.

Di sisi lain pustaka standar mendefinisikan std :: logic_error dan semua turunannya, yang bagi saya sepertinya mereka harus tangani, selain hal-hal lain, kesalahan pemrograman. Apakah meneruskan string kosong ke std :: stof (akan membuang invalid_argument) bukan kesalahan pemrograman? Apakah meneruskan string yang berisi karakter berbeda dari '1' / '0' ke std :: bitset (akan membuang invalid_argument) bukan kesalahan pemrograman? Apakah memanggil std :: bitset :: diset dengan indeks yang tidak valid (akan membuang out_of_range) bukan kesalahan pemrograman? Jika tidak, lalu apa kesalahan pemrograman yang akan diuji? Konstruktor berbasis string std :: bitset hanya ada sejak C ++ 11, jadi seharusnya dirancang dengan penggunaan pengecualian idiomatik. Di sisi lain saya sudah punya orang mengatakan kepada saya logic_error pada dasarnya tidak boleh digunakan sama sekali.

Aturan lain yang sering muncul dengan pengecualian adalah "hanya gunakan pengecualian dalam keadaan luar biasa". Tapi bagaimana fungsi perpustakaan seharusnya tahu keadaan mana yang luar biasa? Untuk beberapa program, tidak dapat membuka file mungkin luar biasa. Bagi yang lain, tidak dapat mengalokasikan memori mungkin tidak luar biasa. Dan ada 100-an kasus di antaranya. Tidak dapat membuat soket? Tidak dapat terhubung, atau menulis data ke soket atau file? Tidak dapat menguraikan input? Mungkin luar biasa, mungkin juga tidak. Fungsi itu sendiri pasti tidak dapat secara umum tahu, ia tidak tahu di mana konteks itu dipanggil.

Jadi, bagaimana saya harus memutuskan apakah saya harus menggunakan pengecualian atau tidak untuk fungsi tertentu? Tampak bagi saya bahwa satu-satunya cara yang benar-benar konsisten adalah menggunakannya untuk setiap dan semua penanganan kesalahan, atau untuk apa-apa. Dan jika saya menggunakan perpustakaan standar, pilihan itu dibuat untuk saya.

cooky451
sumber
6
Anda harus membaca entri FAQ itu dengan sangat hati-hati. Ini hanya berlaku untuk kesalahan pengkodean, bukan data yang tidak valid, mendereferensi objek nol, atau apa pun yang berkaitan dengan keburukan runtime umum. Secara umum, pernyataan adalah tentang mengidentifikasi hal-hal yang seharusnya tidak pernah terjadi. Untuk yang lainnya, ada pengecualian, kode kesalahan dan sebagainya.
Robert Harvey
1
@RobertHarvey definisi itu masih memiliki masalah yang sama - jika sesuatu dapat diselesaikan tanpa campur tangan manusia atau tidak hanya diketahui lapisan atas suatu program.
cooky451
1
Anda terpaku pada legalistik. Evaluasi pro dan kontra, dan putuskan sendiri. Juga, paragraf terakhir dalam pertanyaan Anda ... Saya sama sekali tidak menganggap itu sebagai bukti. Pemikiran Anda sangat hitam dan putih, ketika kebenaran mungkin lebih dekat ke beberapa warna abu-abu.
Robert Harvey
4
Sudahkah Anda mencoba melakukan penelitian apa pun sebelum mengajukan pertanyaan ini? C ++ idiom penanganan kesalahan hampir pasti dibahas secara mendetail di web. Satu referensi untuk satu entri FAQ tidak menghasilkan penelitian yang baik. Setelah Anda melakukan penelitian, Anda masih harus mengambil keputusan sendiri. Jangan mulai saya tentang bagaimana sekolah pemrograman kami tampaknya menciptakan robot pengkodean pola perangkat lunak yang tidak tahu cara berpikir untuk diri mereka sendiri.
Robert Harvey
2
Yang memberikan kepercayaan pada teori saya bahwa aturan seperti itu mungkin tidak benar-benar ada. Saya telah mengundang beberapa orang dari The C ++ Lounge untuk melihat apakah mereka dapat menjawab pertanyaan Anda, meskipun setiap kali saya masuk ke sana, saran mereka adalah "Berhenti menggunakan C ++, itu akan menggoreng otak Anda." Jadi terima saran mereka dengan risiko Anda sendiri.
Robert Harvey

Jawaban:

15

Pertama, saya merasa berkewajiban untuk menunjukkan bahwa std::exceptiondan anak-anaknya dirancang sejak lama. Ada sejumlah bagian yang mungkin (hampir pasti) akan berbeda jika sedang dirancang hari ini.

Jangan salah paham: ada bagian dari desain yang bekerja dengan cukup baik, dan merupakan contoh yang cukup bagus tentang cara mendesain hierarki pengecualian untuk C ++ (mis. Kenyataan bahwa, tidak seperti kebanyakan kelas lain, mereka semua berbagi akar yang sama).

Melihat secara khusus logic_error, kami memiliki sedikit teka-teki. Di satu sisi, jika Anda memiliki pilihan yang masuk akal dalam hal ini, saran yang Anda kutip adalah benar: umumnya lebih baik gagal secepat dan sesering mungkin sehingga dapat dibantah dan diperbaiki.

Baik atau buruk, sulit untuk mendefinisikan perpustakaan standar di sekitar apa yang seharusnya Anda lakukan secara umum . Jika ia menetapkan ini untuk keluar dari program (misalnya, panggilan abort()) ketika diberi input yang salah, itu akan selalu terjadi untuk keadaan itu - dan sebenarnya ada beberapa keadaan di mana ini mungkin bukan hal yang benar untuk dilakukan , setidaknya dalam kode yang digunakan.

Itu akan berlaku dalam kode dengan persyaratan real-time (setidaknya lunak), dan penalti minimal untuk output yang salah. Misalnya, pertimbangkan program obrolan. Jika itu decoding beberapa data suara, dan mendapat beberapa input yang salah, kemungkinan pengguna akan jauh lebih bahagia untuk hidup dengan milidetik statis dalam output daripada program yang baru saja dimatikan sepenuhnya. Demikian juga saat melakukan pemutaran video, mungkin lebih dapat diterima untuk hidup dengan menghasilkan nilai yang salah untuk beberapa piksel untuk satu atau dua bingkai daripada membiarkan program keluar karena aliran input rusak.

Adapun apakah akan menggunakan pengecualian untuk melaporkan jenis kesalahan tertentu: Anda benar - operasi yang sama mungkin memenuhi syarat sebagai pengecualian atau tidak, tergantung pada bagaimana itu digunakan.

Di sisi lain, Anda juga salah - menggunakan perpustakaan standar tidak (memaksakan) keputusan itu pada Anda. Dalam hal membuka file, biasanya Anda menggunakan iostream. Iostreams juga bukan desain terbaru dan terhebat, tetapi dalam hal ini mereka melakukan sesuatu dengan benar: mereka membiarkan Anda mengatur mode kesalahan, sehingga Anda dapat mengontrol apakah gagal membuka file dengan hasil pengecualian dilemparkan atau tidak. Jadi, jika Anda memiliki file yang benar-benar diperlukan untuk aplikasi Anda, dan gagal membukanya berarti Anda harus mengambil tindakan perbaikan yang serius, maka Anda dapat meminta pengecualian jika gagal membuka file itu. Untuk sebagian besar file, yang akan Anda coba buka, jika tidak ada atau tidak dapat diakses, mereka hanya akan gagal (ini defaultnya).

Adapun cara Anda memutuskan: Saya tidak berpikir ada jawaban yang mudah. Baik atau buruk, "keadaan luar biasa" tidak selalu mudah untuk diukur. Meskipun ada beberapa kasus yang mudah untuk diputuskan harus luar biasa, ada (dan mungkin selalu akan) kasus-kasus yang terbuka untuk dipertanyakan, atau membutuhkan pengetahuan tentang konteks yang berada di luar domain fungsi yang ada. Untuk kasus-kasus seperti itu, setidaknya mungkin layak untuk mempertimbangkan desain yang kira-kira mirip dengan bagian iostreams ini, di mana pengguna dapat memutuskan apakah kegagalan menghasilkan pengecualian yang dilemparkan atau tidak. Atau, sangat mungkin untuk memiliki dua set fungsi yang terpisah (atau kelas, dll.), Yang salah satunya akan melempar pengecualian untuk menunjukkan kegagalan, yang lainnya menggunakan cara lain. Jika Anda pergi rute itu,

Jerry Coffin
sumber
9

Konstruktor berbasis string std :: bitset hanya ada sejak C ++ 11, jadi seharusnya dirancang dengan penggunaan pengecualian idiomatik. Di sisi lain saya sudah punya orang mengatakan kepada saya logic_error pada dasarnya tidak boleh digunakan sama sekali.

Anda mungkin tidak mempercayainya, tetapi, beberapa coders C ++ yang berbeda tidak setuju. Itu sebabnya FAQ mengatakan satu hal tetapi perpustakaan standar tidak setuju.

Pendukung FAQ mogok karena itu akan lebih mudah untuk debug. Jika Anda mogok dan mendapatkan dump inti, Anda akan memiliki status aplikasi yang tepat. Jika Anda melempar pengecualian, Anda akan kehilangan banyak kondisi itu.

Pustaka standar mengambil teori bahwa memberi koder kemampuan untuk menangkap dan kemungkinan menangani kesalahan lebih penting daripada debuggabilitas.

Mungkin luar biasa, mungkin juga tidak. Fungsi itu sendiri pasti tidak dapat secara umum tahu, ia tidak tahu di mana konteks itu dipanggil.

Idenya di sini adalah bahwa jika fungsi Anda tidak tahu apakah situasinya luar biasa atau tidak, itu tidak boleh membuang pengecualian. Seharusnya mengembalikan status kesalahan melalui beberapa mekanisme lain. Setelah mencapai titik di program di mana ia tahu negara luar biasa, maka ia harus membuang pengecualian.

Tapi ini punya masalah sendiri. Jika keadaan kesalahan dikembalikan dari suatu fungsi, Anda mungkin tidak ingat untuk memeriksanya dan kesalahan akan berlalu dengan diam-diam. Hal ini menyebabkan beberapa orang meninggalkan pengecualian adalah aturan yang luar biasa demi membuang pengecualian untuk segala bentuk kesalahan.

Secara keseluruhan, kuncinya adalah bahwa orang yang berbeda memiliki ide yang berbeda tentang kapan harus melempar pengecualian. Anda tidak akan menemukan ide kohesif tunggal. Meskipun beberapa orang akan secara dogmatis menyatakan bahwa ini atau itu adalah cara yang tepat untuk menangani pengecualian, tidak ada satu pun teori yang disepakati.

Anda bisa melempar pengecualian:

  1. Tidak pernah
  2. Dimana mana
  3. Hanya pada kesalahan programmer
  4. Tidak pernah pada kesalahan programmer
  5. Hanya selama kegagalan non-rutin (luar biasa)

dan temukan seseorang di internet yang setuju dengan Anda. Anda harus mengadopsi gaya yang sesuai untuk Anda.

Winston Ewert
sumber
Mungkin perlu dicatat bahwa saran untuk hanya menggunakan pengecualian ketika keadaan benar - benar luar biasa telah dipromosikan secara luas oleh orang yang mengajar tentang bahasa di mana pengecualian memiliki kinerja yang buruk. C ++ bukan salah satu dari bahasa-bahasa itu.
Jules
1
@ Jules - sekarang (kinerja) tentu layak mendapatkan jawaban sendiri di mana Anda mendukung klaim Anda. C ++ kinerja pengecualian adalah tentu masalah, mungkin lebih, mungkin kurang dari tempat lain, tetapi menyatakan "C ++ adalah bukan salah satu bahasa [di mana pengecualian memiliki kinerja yang buruk]" tentu bisa diperdebatkan.
Martin Ba
1
@ MartinBa - dibandingkan dengan, katakanlah, Java, kinerja C ++ exception adalah urutan besarnya lebih cepat. Tingkatan yang dicapai menunjukkan kinerja melempar pengecualian hingga 1 level sekitar 50x lebih lambat daripada menangani nilai balik di C ++, dibandingkan lebih dari 1000x lebih lambat di Jawa. Nasihat yang ditulis untuk Java dalam hal ini tidak boleh diterapkan ke C ++ tanpa pemikiran tambahan karena ada lebih dari satu urutan perbedaan besar dalam kinerja antara keduanya. Mungkin saya seharusnya menulis "kinerja yang sangat buruk" daripada "kinerja yang buruk".
Jules
1
@ Jules - terima kasih untuk angka-angka ini. (sumber apa saja?) Saya dapat mempercayainya, karena Java (dan C #) perlu menangkap jejak stack, yang tentu saja terlihat sangat mahal. Saya masih berpikir tanggapan awal Anda agak menyesatkan, karena bahkan penurunan 50x cukup berat, saya kira, esp. dalam bahasa yang berorientasi pada kinerja seperti C ++.
Martin Ba
2

Banyak jawaban baik lainnya telah ditulis, saya hanya ingin menambahkan poin singkat.

Jawaban tradisional, terutama ketika FAQ ISO C ++ ditulis, terutama membandingkan "Pengecualian C ++" vs. "kode pengembalian gaya-C". Opsi ketiga, "kembalikan beberapa jenis nilai komposit, misalnya a structatau union, atau saat ini, boost::variantatau (yang diusulkan) std::expected, tidak dipertimbangkan.

Sebelum ke C ++ 11 opsi "return a composite type" biasanya sangat lemah. Karena, tidak ada semantik yang bergerak sehingga menyalin sesuatu masuk dan keluar dari suatu struktur berpotensi sangat mahal. Sangat penting pada saat itu dalam bahasa untuk menata kode Anda ke RVO untuk mendapatkan kinerja terbaik. Pengecualian seperti cara mudah untuk secara efektif mengembalikan tipe komposit, padahal sebaliknya itu akan sangat sulit.

IMO, setelah C ++ 11, opsi ini "mengembalikan serikat terdiskriminasi", mirip dengan idiom yang Result<T, E>digunakan di Rust saat ini, harus lebih disukai dalam kode C ++. Kadang-kadang itu benar-benar gaya yang lebih sederhana dan lebih nyaman untuk menunjukkan kesalahan. Dengan pengecualian, selalu ada kemungkinan ini bahwa fungsi yang tidak melempar sebelum tiba-tiba bisa mulai melempar setelah refactor, dan programmer tidak selalu mendokumentasikan hal-hal seperti itu dengan baik. Ketika kesalahan diindikasikan sebagai bagian dari nilai balik dalam serikat terdiskriminasi, itu sangat mengurangi kemungkinan bahwa programmer hanya akan mengabaikan kode kesalahan, yang merupakan kritik biasa dari penanganan kesalahan gaya-C.

Biasanya Result<T, E>bekerja seperti boost seperti opsional. Anda dapat menguji, menggunakan operator bool, apakah itu nilai atau kesalahan. Dan kemudian gunakan katakan operator *untuk mengakses nilai, atau fungsi "get" lainnya. Biasanya akses itu tidak dicentang, untuk kecepatan. Tapi Anda bisa membuatnya sehingga dalam membangun debug, akses menjadi diperiksa dan pernyataan memastikan bahwa sebenarnya ada nilai dan bukan kesalahan. Dengan cara ini siapa pun yang tidak memeriksa kesalahan dengan benar akan mendapatkan penegasan yang keras daripada beberapa masalah yang lebih berbahaya.

Keuntungan tambahan adalah bahwa, tidak seperti pengecualian di mana jika tidak tertangkap hanya terbang tumpukan jarak sewenang-wenang, dengan gaya ini, ketika suatu fungsi mulai menandakan kesalahan di mana tidak sebelumnya, Anda tidak dapat mengkompilasi kecuali kode diubah untuk menanganinya. Ini membuat masalah lebih keras - masalah tradisional "pengecualian tanpa tertangkap" menjadi lebih seperti kesalahan waktu kompilasi daripada kesalahan runtime.

Saya sudah menjadi penggemar berat gaya ini. Biasanya, saya saat ini menggunakan ini atau pengecualian. Tapi saya mencoba membatasi pengecualian untuk masalah besar. Untuk sesuatu seperti kesalahan parse, saya mencoba kembali expected<T>misalnya. Hal-hal seperti std::stoidan boost::lexical_castyang melempar pengecualian C ++ dalam hal masalah yang relatif kecil "string tidak dapat dikonversi ke angka" tampaknya sangat tidak enak bagi saya saat ini.

Chris Beck
sumber
1
std::expectedmasih proposal yang tidak diterima, kan?
Martin Ba
Anda benar, saya kira itu belum diterima. Tetapi ada beberapa implementasi open source yang beredar, dan saya sudah menggulirkan saya sendiri beberapa kali. Ini kurang rumit daripada melakukan jenis varian karena hanya ada dua kemungkinan keadaan. Pertimbangan desain utama adalah seperti, apa antarmuka yang tepat yang Anda inginkan, dan apakah Anda ingin seperti yang Andrescu harapkan <T> di mana objek kesalahan seharusnya menjadi exception_ptr, atau apakah Anda hanya ingin menggunakan beberapa jenis struktur atau sesuatu seperti itu.
Chris Beck
Pembicaraan Andrei Alexandrescu ada di sini: channel9.msdn.com/Shows/Going+Deep/… Dia menunjukkan secara terperinci bagaimana membangun kelas seperti ini dan pertimbangan apa yang mungkin Anda miliki.
Chris Beck
Usulan [[nodiscard]] attributeakan membantu untuk pendekatan penanganan kesalahan ini karena memastikan bahwa Anda tidak mengabaikan hasil kesalahan secara tidak sengaja.
CodesInChaos
- Ya saya tahu pembicaraan AA. Saya menemukan desain yang cukup aneh karena untuk membukanya ( except_ptr) Anda harus melempar pengecualian secara internal. Secara pribadi saya pikir alat seperti itu harus bekerja sepenuhnya independen dari eksekusi. Hanya sebuah komentar.
Martin Ba
1

Ini adalah masalah yang sangat subyektif, karena merupakan bagian dari desain. Dan karena desain pada dasarnya seni, saya lebih suka membahas hal-hal ini daripada berdebat (saya tidak mengatakan Anda berdebat).

Bagi saya, kasus luar biasa ada dua macam - kasus yang berhubungan dengan sumber daya dan kasus yang berhubungan dengan operasi kritis. Apa yang dapat dianggap kritis tergantung pada masalah yang dihadapi, dan, dalam banyak kasus, pada sudut pandang programmer.

Kegagalan untuk memperoleh sumber daya adalah kandidat teratas untuk melempar pengecualian. Sumber daya dapat berupa memori, file, koneksi jaringan atau apa pun berdasarkan masalah dan platform Anda. Sekarang, apakah kegagalan untuk merilis sumber daya memerlukan pengecualian? Nah, itu lagi tergantung. Saya belum melakukan apa pun di mana melepaskan memori gagal, jadi saya tidak yakin tentang skenario itu. Namun, menghapus file sebagai bagian dari rilis sumber daya bisa gagal, dan gagal bagi saya, dan kegagalan itu biasanya dikaitkan dengan proses lain setelah membuatnya dibuka di aplikasi multi-proses. Saya kira sumber daya lain bisa gagal selama rilis seperti file, dan biasanya cacat desain yang menyebabkan masalah ini, jadi memperbaikinya akan lebih baik daripada melempar pengecualian.

Kemudian datang memperbarui sumber daya. Poin ini, setidaknya bagi saya, terkait erat dengan aspek operasi kritis aplikasi. Bayangkan sebuah Employeekelas dengan fungsi UpdateDetails(std::string&)yang memodifikasi detail berdasarkan string yang dipisahkan koma. Mirip dengan pelepasan memori yang gagal, saya merasa sulit untuk membayangkan penugasan nilai variabel anggota gagal karena kurangnya pengalaman dalam domain tersebut di mana ini mungkin terjadi. Namun, fungsi seperti UpdateDetailsAndUpdateFile(std::string&)yang sesuai dengan namanya diharapkan gagal. Inilah yang saya sebut operasi kritis.

Sekarang, Anda harus melihat apakah yang disebut operasi kritis itu menjamin pengecualian untuk dilempar. Maksud saya, apakah memperbarui file terjadi pada akhirnya, seperti pada destruktor, atau apakah itu hanya panggilan paranoid yang dibuat setelah setiap pembaruan? Apakah ada mekanisme mundur yang menulis objek tidak tertulis secara teratur? Apa yang saya katakan adalah, Anda harus mengevaluasi kekritisan operasi.

Jelas, ada banyak operasi kritis yang tidak terkait dengan sumber daya. Jika UpdateDetails()diberi data yang salah, itu tidak akan memperbarui detail dan kegagalan harus diketahui, jadi Anda akan melemparkan pengecualian di sini. Tapi bayangkan fungsi seperti GiveRaise(). Sekarang, jika karyawan tersebut beruntung memiliki bos berambut runcing dan tidak akan mendapatkan kenaikan gaji (dalam hal pemrograman, nilai beberapa variabel menghentikan ini terjadi), fungsi dasarnya gagal. Apakah Anda akan melemparkan pengecualian di sini? Apa yang saya katakan adalah, Anda harus mengevaluasi perlunya pengecualian.

Bagi saya, konsistensi dalam hal pendekatan desain saya daripada kegunaan kelas saya. Maksud saya adalah, saya tidak berpikir dalam hal 'semua fungsi Get harus melakukan ini dan semua fungsi Pembaruan harus untuk ini' tetapi melihat apakah fungsi tertentu menarik ide tertentu dalam pendekatan saya. Pada permukaannya, kelas-kelas bisa terlihat agak 'serampangan' tetapi setiap kali pengguna (kebanyakan kolega dari tim lain) berteriak-teriak atau bertanya tentang hal itu, saya akan menjelaskan dan mereka tampak puas.

Saya melihat banyak orang yang pada dasarnya mengganti nilai kembali dengan pengecualian karena mereka menggunakan C ++ dan bukan C, dan itu memberikan 'pemisahan yang bagus dari penanganan kesalahan' dll. Dan mendesak saya untuk berhenti 'mencampur' bahasa dll. Saya biasanya menjauh dari orang-orang seperti itu.

vin
sumber
1

Pertama, seperti orang lain telah menyatakan, hal-hal yang tidak yang jelas dipotong di C ++, IMHO terutama karena persyaratan dan pembatasan yang agak lebih bervariasi dalam C ++ dari bahasa lain, esp. C # dan Java, yang memiliki masalah pengecualian "mirip".

Saya akan mengungkap contoh std :: stof:

meneruskan string kosong ke std :: stof (akan membuang invalid_argument) bukan kesalahan pemrograman

Dasar kontrak , seperti yang saya lihat, dari fungsi ini adalah bahwa ia mencoba untuk mengkonversi argumen itu untuk pelampung, dan setiap kegagalan untuk melakukannya dilaporkan oleh pengecualian. Kedua pengecualian yang mungkin berasal dari logic_errortetapi tidak dalam arti kesalahan programer tetapi dalam arti "input tidak dapat, pernah dikonversi menjadi float".

Di sini, dapat dikatakan bahwa a logic_errordigunakan untuk menunjukkan bahwa, mengingat bahwa (runtime) input, selalu merupakan kesalahan untuk mencoba mengubahnya - tetapi tugas fungsi untuk menentukan itu dan memberi tahu Anda (melalui pengecualian).

Catatan: Dalam pandangan itu, a runtime_error dapat dilihat sebagai sesuatu yang, diberikan input yang sama ke suatu fungsi, secara teoritis dapat berhasil untuk berbagai proses. (mis. operasi file, akses DB, dll.)

Catatan Sisi Lebih Lanjut: Pustaka regex C ++ memilih untuk menurunkan kesalahan itu dari runtime_errormeskipun ada kasus di mana ia dapat diklasifikasikan sama seperti di sini (pola regex tidak valid).

Ini hanya menunjukkan, IMHO, bahwa pengelompokan ke dalam logic_atau runtime_kesalahan cukup kabur di C ++ dan tidak terlalu banyak membantu dalam kasus umum (*) - jika Anda perlu menangani kesalahan tertentu, Anda mungkin perlu menangkap lebih rendah dari keduanya.

(*): Itu bukan untuk mengatakan bahwa sepotong kode tidak boleh konsisten, tetapi apakah Anda melempar runtime_atau logic_atau custom_sesuatu benar-benar tidak begitu penting, saya pikir.


Untuk mengomentari keduanya stofdan bitset:

Kedua fungsi mengambil string sebagai argumen, dan dalam kedua kasus itu adalah:

  • non-sepele untuk memeriksa penelepon apakah string yang diberikan valid (mis. kasus terburuk Anda harus mereplikasi logika fungsi; dalam kasus bitset, tidak segera jelas apakah string kosong itu valid, jadi biarkan aktor memutuskan)
  • Sudah merupakan tanggung jawab fungsi untuk "mem-parsing" string, sehingga harus memvalidasi string, sehingga masuk akal bahwa ia melaporkan kesalahan untuk "menggunakan" string secara seragam (dan dalam kedua kasus ini merupakan pengecualian) .

aturan yang sering muncul dengan pengecualian adalah "hanya gunakan pengecualian dalam keadaan luar biasa". Tapi bagaimana fungsi perpustakaan seharusnya tahu keadaan mana yang luar biasa?

Pernyataan ini memiliki, IMHO, dua akar:

Performa : Jika suatu fungsi dipanggil dalam jalur kritis, dan case "luar biasa" tidak luar biasa, yaitu sejumlah besar lintasan akan melibatkan melempar pengecualian, lalu membayar setiap waktu untuk pengecualian-mesin-gulungan tidak masuk akal , dan mungkin terlalu lambat.

Lokalitas penanganan kesalahan : Jika fungsi dipanggil dan pengecualian segera tertangkap dan diproses, maka ada gunanya melemparkan pengecualian, seperti penanganan kesalahan akan lebih verbose dengan yang catchdari dengan if.

Contoh:

float readOrDefault;
try {
  readOrDefault = stof(...);
} catch(std::exception&) {
  // discard execption, just use default value
  readOrDefault = 3.14f; // 3.14 is the default value if cannot be read
}

Di sinilah fungsi seperti TryParsevs. Parseikut bermain: Satu versi untuk saat kode lokal mengharapkan string yang diuraikan menjadi valid, satu versi ketika kode lokal mengasumsikan bahwa sebenarnya diharapkan (yaitu tidak luar biasa) bahwa parsing akan gagal.

Memang, stofhanya (didefinisikan sebagai) pembungkus strtof, jadi jika Anda tidak ingin pengecualian, gunakan yang itu.


Jadi, bagaimana saya harus memutuskan apakah saya harus menggunakan pengecualian atau tidak untuk fungsi tertentu?

IMHO, Anda memiliki dua kasus:

  • Fungsi "Perpustakaan" (sering digunakan kembali dalam konteks yang berbeda): Anda pada dasarnya tidak dapat memutuskan. Mungkin memberikan kedua versi, mungkin yang melaporkan kesalahan dan yang bungkus yang mengubah kesalahan yang dikembalikan menjadi pengecualian.

  • Fungsi "Aplikasi" (khusus untuk gumpalan kode aplikasi, dapat digunakan kembali beberapa, tetapi dibatasi oleh gaya penanganan kesalahan aplikasi, dll.): Di sini, harus sering dipotong cukup jelas. Jika jalur kode yang memanggil fungsi menangani pengecualian dengan cara yang waras dan bermanfaat, gunakan pengecualian untuk melaporkan kesalahan apa pun (tetapi lihat di bawah) . Jika kode aplikasi lebih mudah dibaca dan ditulis untuk gaya pengembalian kesalahan, tentu saja gunakan itu.

Tentu saja akan ada tempat di antara - gunakan saja apa yang perlu dan ingat YAGNI.


Terakhir, saya pikir saya harus kembali ke pernyataan FAQ,

Jangan gunakan lemparan untuk menunjukkan kesalahan pengkodean dalam penggunaan suatu fungsi. Gunakan penegasan atau mekanisme lain untuk mengirim proses ke debugger atau untuk menghentikan proses ...

Saya berlangganan ini untuk semua kesalahan yang merupakan indikasi jelas bahwa ada sesuatu yang sangat kacau atau kode panggilan jelas tidak tahu apa yang dilakukannya.

Tetapi ketika ini tepat sering aplikasi yang sangat spesifik, maka lihat domain perpustakaan di atas vs domain aplikasi.

Ini kembali pada pertanyaan apakah dan bagaimana memvalidasi prakondisi panggilan , tapi saya tidak akan membahasnya, jawabnya sudah terlalu lama :-)

Martin Ba
sumber