Bagi saya, penanganan pengecualian dapat menjadi hal yang bagus jika semuanya sesuai dengan RAII dan Anda mencadangkan pengecualian untuk jalur yang benar-benar luar biasa (mis: file korup sedang dibaca) dan Anda tidak perlu membuang batas-batas modul dan seluruh tim Anda ada di dalamnya. ......
Zero-Cost EH
Sebagian besar kompiler hari ini menerapkan penanganan pengecualian tanpa biaya yang dapat membuatnya lebih murah daripada secara manual bercabang pada kondisi kesalahan dalam jalur eksekusi reguler, meskipun sebagai gantinya membuat jalur luar biasa sangat mahal (ada juga beberapa kode mengasapi darinya, meskipun mungkin tidak lebih daripada jika Anda benar-benar menangani setiap kemungkinan kesalahan secara manual). Fakta bahwa melempar sangat mahal seharusnya tidak menjadi masalah meskipun jika pengecualian digunakan seperti yang dimaksudkan dalam C ++ (untuk keadaan yang benar-benar luar biasa yang seharusnya tidak terjadi dalam kondisi normal).
Pembalikan Efek Samping
Yang mengatakan, membuat semuanya sesuai dengan RAII jauh lebih mudah diucapkan daripada dilakukan. Sangat mudah untuk sumber daya lokal yang disimpan dalam fungsi untuk membungkusnya menjadi objek C ++ dengan destruktor yang membersihkan diri mereka sendiri, tetapi tidak mudah untuk menulis logika undo / rollback untuk setiap efek samping yang dapat terjadi pada seluruh perangkat lunak. Pertimbangkan betapa sulitnya untuk menulis sebuah wadah seperti std::vector
yang merupakan urutan akses acak paling sederhana untuk menjadi pengecualian-aman. Sekarang gandakan kesulitan itu di semua struktur data dari seluruh perangkat lunak berskala besar.
Sebagai contoh dasar, pertimbangkan pengecualian yang ditemui dalam proses memasukkan item ke grafik adegan. Untuk memulihkan dengan benar dari pengecualian itu, Anda mungkin perlu membatalkan perubahan dalam grafik adegan dan mengembalikan sistem kembali ke keadaan seolah-olah operasi tidak pernah terjadi. Itu berarti menghapus anak-anak yang dimasukkan ke grafik adegan secara otomatis saat menemukan pengecualian, mungkin dari penjaga lingkup.
Melakukan ini secara menyeluruh dalam perangkat lunak yang menyebabkan banyak efek samping dan berurusan dengan banyak kondisi persisten jauh lebih mudah dikatakan daripada dilakukan. Seringkali saya pikir solusi pragmatis adalah tidak repot dengan itu demi mendapatkan barang yang dikirim. Dengan basis kode yang tepat yang memiliki semacam sistem pusat undo dan struktur data yang persisten dan minimnya fungsi yang menyebabkan efek samping, Anda mungkin dapat mencapai pengecualian-keamanan di seluruh papan ... tetapi banyak hal-hal yang Anda butuhkan jika semua yang akan Anda lakukan adalah mencoba membuat kode Anda aman di mana-mana.
Ada sesuatu yang salah secara praktis di sana karena pembalikan efek samping secara konseptual adalah masalah yang sulit, tetapi itu menjadi lebih sulit di C ++. Sebenarnya lebih mudah terkadang membalikkan efek samping dalam C sebagai respons terhadap kode kesalahan daripada melakukannya sebagai respons terhadap pengecualian dalam C ++. Salah satu alasannya adalah karena titik tertentu dari fungsi apa pun berpotensi throw
dalam C ++. Jika Anda mencoba untuk menulis sebuah wadah generik yang bekerja dengan tipe T
, Anda bahkan tidak bisa mengantisipasi di mana titik keluar itu, karena apa pun yang terlibat T
bisa melempar. Bahkan membandingkan satu T
dengan yang lain untuk kesetaraan bisa melempar. Kode Anda harus menangani setiap kemungkinan , dan jumlah kemungkinan berlipat ganda secara eksponensial dalam C ++ sedangkan dalam C, jumlahnya jauh lebih sedikit.
Kurangnya Standar
Masalah lain dari penanganan pengecualian adalah kurangnya standar pusat. Ada beberapa seperti semoga semua orang setuju bahwa destruktor tidak boleh melempar karena rollback seharusnya tidak pernah gagal, tetapi itu hanya mencakup kasus patologis yang umumnya akan merusak perangkat lunak jika Anda melanggar aturan.
Seharusnya ada beberapa standar yang lebih masuk akal seperti tidak pernah membuang dari operator pembanding (semua fungsi yang secara logika tidak dapat diubah tidak boleh dilempar), tidak pernah membuang dari pemindah kode, dll. Jaminan semacam itu akan membuat jauh lebih mudah untuk menulis kode pengecualian-aman, tapi kami tidak punya jaminan seperti itu. Kita harus pergi dengan aturan bahwa segala sesuatu bisa dilemparkan - jika tidak sekarang, maka mungkin di masa depan.
Lebih buruk lagi, orang-orang dari latar belakang bahasa yang berbeda memandang pengecualian dengan sangat berbeda. Dalam Python, mereka benar-benar memiliki filosofi "lompatan sebelum Anda melihat" yang melibatkan pemicu pengecualian dalam jalur eksekusi reguler! Ketika Anda kemudian mendapatkan pengembang seperti itu menulis kode C ++, Anda bisa melihat pengecualian dilemparkan ke kiri dan kanan untuk hal-hal yang tidak benar-benar luar biasa, dan menangani semuanya bisa menjadi mimpi buruk jika Anda mencoba menulis kode generik, misalnya
Penanganan Kesalahan
Penanganan kesalahan memiliki kelemahan yang harus Anda periksa untuk setiap kemungkinan kesalahan yang bisa terjadi. Tapi itu memang memiliki satu keuntungan yang kadang-kadang Anda dapat semacam memeriksa kesalahan sedikit terlambat di belakang, seperti glGetError
, untuk secara signifikan mengurangi titik keluar dalam suatu fungsi serta membuatnya eksplisit. Kadang-kadang Anda dapat terus menjalankan kode sedikit lebih lama sebelum memeriksa dan menyebarkan dan memulihkan dari kesalahan, dan kadang-kadang itu benar-benar bisa lebih mudah daripada penanganan pengecualian (terutama dengan pembalikan efek samping). Tapi Anda mungkin tidak terlalu repot dengan kesalahan baik dalam permainan, mungkin hanya mematikan permainan dengan pesan jika Anda menemukan hal-hal seperti file yang rusak, kehabisan memori, dll.
Bagaimana hal itu berkaitan dengan pengembangan perpustakaan yang digunakan melalui banyak proyek.
Bagian buruk dari pengecualian dalam konteks DLL adalah bahwa Anda tidak dapat dengan aman membuang pengecualian dari satu modul ke modul lainnya kecuali Anda dapat menjamin bahwa mereka dibangun oleh kompilator yang sama, gunakan pengaturan yang sama, dll. Jadi jika Anda menulis seperti arsitektur plugin dimaksudkan agar orang dapat digunakan dari semua jenis kompiler dan mungkin bahkan dari berbagai bahasa seperti Lua, C, C #, Java, dll., seringkali pengecualian mulai menjadi gangguan besar karena Anda harus menelan setiap pengecualian dan menerjemahkannya ke kesalahan kode tetap di semua tempat.
Apa yang dilakukan untuk menggunakan perpustakaan pihak ketiga.
Jika itu adalah dylib, maka mereka tidak dapat melempar pengecualian dengan aman karena alasan yang disebutkan di atas. Mereka harus menggunakan kode kesalahan yang juga berarti Anda, menggunakan perpustakaan, harus terus-menerus memeriksa kode kesalahan. Mereka dapat membungkus dylib mereka dengan lilitan pembungkus C ++ yang terhubung secara statis yang Anda buat sendiri yang menerjemahkan kode kesalahan dari dylib ke dalam pengecualian yang dilemparkan (pada dasarnya melemparkan dari biner ke biner Anda). Secara umum saya pikir sebagian besar lib pihak ketiga bahkan tidak perlu repot dan hanya menempel pada kode kesalahan jika mereka menggunakan dylibs / shared libs.
Bagaimana pengaruhnya terhadap pengujian unit, pengujian integrasi, dll?
Saya biasanya tidak menemukan tim yang menguji jalur kesalahan / luar biasa dari kode mereka. Saya biasa melakukannya dan benar-benar memiliki kode yang dapat pulih dengan baik bad_alloc
karena saya ingin membuat kode saya sangat kuat, tetapi tidak terlalu membantu ketika saya satu-satunya yang melakukannya dalam sebuah tim. Pada akhirnya saya berhenti mengganggu. Saya membayangkan untuk perangkat lunak mission-critical yang mungkin diperiksa seluruh tim untuk memastikan kode mereka pulih dengan baik dari pengecualian dan kesalahan dalam semua kasus yang mungkin, tetapi itu banyak waktu untuk berinvestasi. Waktu masuk akal dalam perangkat lunak mission-critical tetapi mungkin bukan permainan.
Cinta / Benci
Pengecualian juga merupakan fitur cinta / benci saya dari C ++ - salah satu fitur yang paling berdampak besar dalam bahasa menjadikan RAII lebih seperti persyaratan daripada kenyamanan, karena itu mengubah cara Anda harus menulis kode secara terbalik dari, katakanlah , C untuk menangani fakta bahwa setiap baris kode tertentu dapat menjadi titik keluar implisit untuk suatu fungsi. Terkadang saya hampir berharap bahasa tersebut tidak memiliki pengecualian. Tetapi ada saat-saat ketika saya menemukan pengecualian benar-benar berguna, tetapi mereka terlalu banyak faktor yang mendominasi bagi saya apakah saya memilih untuk menulis sepotong kode dalam C atau C ++.
Mereka akan secara eksponensial lebih berguna bagi saya jika komite standar benar-benar fokus pada penetapan standar ABI yang memungkinkan pengecualian untuk secara aman dilemparkan ke seluruh modul, setidaknya dari kode C ++ ke C ++. Akan luar biasa jika Anda bisa dengan aman menggunakan berbagai bahasa, meskipun itu semacam mimpi pipa. Hal lain yang akan membuat pengecualian secara eksponensial lebih bermanfaat bagi saya adalah jika noexcept
fungsi benar-benar menyebabkan kesalahan kompiler jika mereka mencoba memanggil fungsi apa pun yang bisa melempar, bukan hanya memanggil std::terminate
untuk menemukan pengecualian. Itu di sebelah tidak berguna (mungkin lebih baik menyebutnya icantexcept
bukan noexcept
). Akan jauh lebih mudah untuk memastikan bahwa semua destruktor aman dari pengecualian, misalnya, jikanoexcept
sebenarnya menyebabkan kesalahan kompilator jika destructor melakukan apa saja yang bisa melempar. Jika itu terlalu mahal untuk ditentukan secara menyeluruh pada waktu kompilasi, maka buat saja noexcept
fungsinya (yang seharusnya dilakukan oleh semua destruktor) hanya diperbolehkan untuk memanggil noexcept
fungsi / operator, dan menjadikannya kesalahan kompiler untuk membuang dari salah satu dari mereka.