Pengecualian sebagai penegasan atau sebagai kesalahan?

10

Saya seorang programmer C profesional dan seorang hobbyist Obj-C programmer (OS X). Baru-baru ini saya tergoda untuk berekspansi ke C ++, karena sintaksisnya yang sangat kaya.

Sejauh ini pengkodean saya belum banyak berurusan dengan pengecualian. Objective-C memilikinya, tetapi kebijakan Apple cukup ketat:

Penting Anda harus mencadangkan penggunaan pengecualian untuk pemrograman atau kesalahan runtime yang tidak terduga seperti akses pengumpulan di luar batas, upaya untuk bermutasi objek yang tidak dapat diubah, mengirim pesan yang tidak valid, dan kehilangan koneksi ke server jendela.

C ++ tampaknya lebih suka menggunakan pengecualian lebih sering. Misalnya contoh wikipedia di RAII memberikan pengecualian jika file tidak dapat dibuka. Objective-C akan return nildengan kesalahan yang dikirim oleh param. Khususnya, tampaknya std :: ofstream dapat diatur dengan cara apa pun.

Di sini, di programmer, saya menemukan beberapa jawaban, baik yang menyatakan menggunakan pengecualian, bukan kode kesalahan atau sama sekali tidak menggunakan pengecualian . Yang pertama tampak lebih lazim.

Saya belum menemukan orang yang melakukan studi objektif untuk C ++. Sepertinya saya karena pointer jarang, saya harus pergi dengan flag kesalahan internal jika saya memilih untuk menghindari pengecualian. Akankah itu terlalu merepotkan untuk ditangani, atau mungkin akan bekerja lebih baik daripada pengecualian? Perbandingan kedua kasus akan menjadi jawaban terbaik.

Sunting: Meskipun tidak sepenuhnya relevan, saya mungkin harus menjelaskan apa nilitu. Secara teknis sama dengan NULL, tetapi masalahnya, tidak apa-apa untuk mengirim pesan nil. Jadi Anda bisa melakukan sesuatu seperti

NSError *err = nil;
id obj = [NSFileHandle fileHandleForReadingFromURL:myurl error:&err];

[obj retain];

bahkan jika panggilan pertama kembali nil. Dan seperti yang tidak pernah Anda lakukan *objdi Obj-C, tidak ada risiko dereference pointer NULL.

Per Johansson
sumber
Imho, akan lebih baik jika Anda akan menunjukkan sepotong kode Objective C bagaimana Anda bekerja dengan kesalahan di sana. Sepertinya, orang berbicara tentang sesuatu yang berbeda dari yang Anda tanyakan.
Gangnus
Di satu sisi saya kira saya mencari pembenaran untuk menggunakan pengecualian. Karena saya memiliki beberapa gagasan tentang bagaimana penerapannya, saya tahu seberapa mahal harganya. Tapi saya kira jika saya bisa mendapatkan argumen yang cukup baik untuk penggunaan mereka, saya akan pergi ke sana.
Per Johansson
Tampaknya, untuk C ++ Anda perlu pembenaran untuk tidak menggunakannya . Menurut reaksi di sini.
Gangnus
2
Mungkin, tapi sejauh ini belum ada yang menjelaskan mengapa mereka lebih baik (kecuali dibandingkan dengan kode kesalahan). Saya tidak suka konsep menggunakan pengecualian untuk hal-hal yang tidak luar biasa, tetapi lebih naluriah daripada berdasarkan fakta.
Per Johansson
"Saya tidak suka ... menggunakan pengecualian untuk hal-hal yang tidak luar biasa" - setuju.
Gangnus

Jawaban:

1

C ++ tampaknya lebih suka menggunakan pengecualian lebih sering.

Saya akan menyarankan sebenarnya kurang dari Objective-C dalam beberapa hal karena perpustakaan standar C ++ umumnya tidak akan membuang kesalahan programmer seperti akses out-of-bound urutan akses acak dalam bentuk desain kasus yang paling umum (dalam operator[], yaitu) atau mencoba dereference iterator yang tidak valid. Bahasa tidak melempar mengakses array di luar batas, atau mendereferensi pointer nol, atau semacamnya.

Mengambil kesalahan programmer sebagian besar dari persamaan penanganan pengecualian sebenarnya menghilangkan kategori kesalahan yang sangat besar yang sering ditanggapi oleh bahasa lain throwing. C ++ cenderung assert(yang tidak dikompilasi dalam rilis / produksi build, hanya debug membangun) atau hanya kesalahan (sering crash) dalam kasus seperti itu, mungkin sebagian karena bahasa tidak ingin membebankan biaya pemeriksaan runtime seperti seperti yang diperlukan untuk mendeteksi kesalahan programmer tersebut kecuali programmer secara khusus ingin membayar biaya dengan menulis kode yang melakukan pemeriksaan sendiri.

Sutter bahkan mendorong untuk menghindari pengecualian dalam kasus seperti itu dalam C ++ Standar Pengkodean:

Kerugian utama menggunakan pengecualian untuk melaporkan kesalahan pemrograman adalah bahwa Anda tidak benar-benar ingin terjadi penumpukan gulungan ketika Anda ingin debugger diluncurkan pada baris yang tepat di mana pelanggaran terdeteksi, dengan status garis utuh. Singkatnya: Ada kesalahan yang Anda tahu mungkin terjadi (lihat Item 69 hingga 75). Untuk semua hal lain yang tidak boleh, dan itu kesalahan programmer jika itu ada assert.

Aturan itu tidak harus diatur dalam batu. Dalam beberapa kasus yang lebih kritis, mungkin lebih baik menggunakan, katakanlah, pembungkus dan standar pengkodean yang secara seragam mencatat di mana kesalahan pemrogram terjadi dan throwdi hadapan kesalahan pemrogram seperti mencoba untuk menghormati sesuatu yang tidak valid atau mengaksesnya di luar batas, karena mungkin terlalu mahal untuk gagal pulih dalam kasus-kasus tersebut jika perangkat lunak memiliki kesempatan. Namun secara keseluruhan penggunaan bahasa yang lebih umum cenderung mendukung tidak membuang kesalahan programmer.

Pengecualian Eksternal

Di mana saya melihat pengecualian yang paling sering didorong dalam C ++ (menurut komite standar, misalnya) adalah untuk "pengecualian eksternal", seperti dalam hasil yang tidak terduga di beberapa sumber eksternal di luar program. Contoh gagal mengalokasikan memori. Lain gagal untuk membuka file penting yang diperlukan untuk menjalankan perangkat lunak. Lainnya gagal terhubung ke server yang diperlukan. Lain adalah pengguna macet tombol batalkan untuk membatalkan operasi yang jalur eksekusi kasus umum mengharapkan berhasil tidak ada gangguan eksternal ini. Semua hal ini berada di luar kendali perangkat lunak langsung dan programmer yang menulisnya. Mereka adalah hasil yang tidak terduga dari sumber eksternal yang mencegah operasi (yang harus benar-benar dianggap sebagai transaksi yang tidak dapat dibagi dalam buku saya *) untuk dapat berhasil.

Transaksi

Saya sering mendorong melihat tryblok sebagai "transaksi" karena transaksi harus berhasil secara keseluruhan atau gagal secara keseluruhan. Jika kita mencoba melakukan sesuatu dan gagal di tengah jalan, maka setiap efek samping / mutasi yang dibuat ke keadaan program umumnya perlu digulung kembali untuk mengembalikan sistem ke keadaan yang valid seolah-olah transaksi tidak pernah dieksekusi sama sekali, sama seperti RDBMS yang gagal untuk memproses permintaan setengah jalan seharusnya tidak mengganggu integritas database. Jika Anda mengubah status program secara langsung dalam transaksi tersebut, maka Anda harus "membatalkan" itu saat menemukan kesalahan (dan di sini ruang lingkup penjaga dapat berguna dengan RAII).

Alternatif yang jauh lebih sederhana adalah jangan mengubah status program semula; Anda dapat mengubah salinannya dan kemudian, jika berhasil, tukar salinan dengan yang asli (pastikan swap tidak dapat dilemparkan). Jika gagal, buang salinannya. Ini juga berlaku bahkan jika Anda tidak menggunakan pengecualian untuk penanganan kesalahan secara umum. Pola pikir "transaksional" adalah kunci untuk pemulihan yang tepat jika mutasi keadaan program telah terjadi sebelum menemukan kesalahan. Entah berhasil secara keseluruhan atau gagal secara keseluruhan. Itu tidak setengah berhasil membuat mutasinya.

Ini adalah salah satu topik yang paling jarang dibahas ketika saya melihat programmer bertanya tentang bagaimana melakukan penanganan kesalahan atau pengecualian dengan benar, namun itu adalah yang paling sulit dari mereka semua untuk mendapatkan yang benar dalam setiap perangkat lunak yang ingin secara langsung mengubah keadaan program di banyak operasinya. Kemurnian dan kekekalan dapat membantu di sini untuk mencapai keselamatan pengecualian seperti halnya mereka membantu keselamatan benang, sebagai efek samping mutasi / eksternal yang tidak terjadi tidak perlu digulung kembali.

Performa

Faktor penuntun lain dalam apakah menggunakan pengecualian atau tidak adalah kinerja, dan saya tidak bermaksud dengan cara yang obsesif, mencubit, kontraproduktif. Banyak kompiler C ++ mengimplementasikan apa yang disebut "Zero-Cost Exception Handling".

Ia menawarkan overhead nol runtime untuk eksekusi bebas kesalahan, yang bahkan melampaui penanganan error nilai-kembali C. Sebagai trade-off, penyebaran pengecualian memiliki overhead yang besar.

Menurut apa yang telah saya baca tentang hal itu, itu membuat jalur eksekusi kasus umum Anda tidak memerlukan overhead (bahkan overhead yang biasanya menyertai penanganan dan penyebaran kode kesalahan gaya-C), dengan imbalan sangat mengurangi biaya menuju jalur luar biasa ( yang artinya throwingsekarang lebih mahal dari sebelumnya).

"Mahal" agak sulit untuk dikuantifikasi tetapi, sebagai permulaan, Anda mungkin tidak ingin melempar jutaan kali dalam beberapa putaran ketat. Desain semacam ini mengasumsikan bahwa pengecualian tidak terjadi kiri dan kanan sepanjang waktu.

Non-Kesalahan

Dan titik kinerja itu membawa saya ke non-error, yang secara mengejutkan kabur jika kita melihat semua jenis bahasa lain. Tapi saya akan mengatakan, mengingat desain EH nol biaya yang disebutkan di atas, bahwa Anda hampir pasti tidak mau throwmenanggapi kunci yang tidak ditemukan dalam satu set. Karena tidak hanya itu bisa dibilang bukan kesalahan (orang yang mencari kunci mungkin telah membangun set dan berharap akan mencari kunci yang tidak selalu ada), tetapi itu akan sangat mahal dalam konteks itu.

Misalnya, fungsi persimpangan set mungkin ingin mengulang dua set dan mencari kunci yang sama. Jika gagal menemukan kunci threw, Anda akan mengulangi dan mungkin menemukan pengecualian di setengah atau lebih dari iterasi:

Set<int> set_intersection(const Set<int>& a, const Set<int>& b)
{
     Set<int> intersection;
     for (int key: a)
     {
          try
          {
              b.find(key);
              intersection.insert(other_key);
          }
          catch (const KeyNotFoundException&)
          {
              // Do nothing.
          }
     }
     return intersection;
}

Contoh di atas benar-benar konyol dan dilebih-lebihkan, tetapi saya telah melihat, dalam kode produksi, beberapa orang yang berasal dari bahasa lain menggunakan pengecualian dalam C ++ agak seperti ini, dan saya pikir itu adalah pernyataan yang cukup praktis bahwa ini bukan penggunaan pengecualian yang tepat. dalam C ++. Petunjuk lain di atas adalah bahwa Anda akan melihat catchblok sama sekali tidak ada hubungannya dan hanya ditulis untuk secara paksa mengabaikan pengecualian semacam itu, dan itu biasanya sebuah petunjuk (meskipun bukan penjamin) bahwa pengecualian mungkin tidak digunakan dengan sangat tepat di C ++.

Untuk jenis kasus tersebut, beberapa jenis nilai pengembalian yang menunjukkan kegagalan (mulai dari kembali falseke iterator yang tidak valid nullptratau apa pun yang masuk akal dalam konteks) biasanya jauh lebih sesuai, dan juga sering lebih praktis dan produktif karena jenis kesalahan yang tidak salah. kasing biasanya tidak meminta beberapa proses tumpukan untuk mencapai catchsitus analog .

Pertanyaan

Saya harus menggunakan flag kesalahan internal jika saya memilih untuk menghindari pengecualian. Akankah itu terlalu merepotkan untuk ditangani, atau mungkin akan bekerja lebih baik daripada pengecualian? Perbandingan kedua kasus akan menjadi jawaban terbaik.

Menghindari pengecualian langsung di C ++ tampaknya sangat kontraproduktif bagi saya, kecuali jika Anda bekerja di beberapa sistem tertanam atau jenis kasus tertentu yang melarang penggunaannya (dalam hal ini Anda juga harus keluar dari cara Anda untuk menghindari semua fungsi perpustakaan dan bahasa yang sebaliknya throw, ingin menggunakan secara ketat nothrow new).

Jika Anda benar-benar harus menghindari pengecualian untuk alasan apa pun (mis: bekerja melintasi batas C API dari modul yang C API Anda ekspor), banyak yang mungkin tidak setuju dengan saya, tetapi saya benar-benar menyarankan menggunakan penangan kesalahan global / status seperti OpenGL dengan glGetError(). Anda dapat membuatnya menggunakan penyimpanan thread-lokal untuk memiliki status kesalahan unik per utas.

Alasan saya untuk itu adalah bahwa saya tidak terbiasa melihat tim di lingkungan produksi memeriksa semua kemungkinan kesalahan, sayangnya, ketika kode kesalahan dikembalikan. Jika mereka teliti, beberapa API C dapat mengalami kesalahan dengan hampir setiap panggilan API C tunggal, dan pemeriksaan menyeluruh akan membutuhkan sesuatu seperti:

if ((err = ApiCall(...)) != success)
{
     // Handle error
}

... dengan hampir setiap baris kode yang memohon API yang membutuhkan pemeriksaan semacam itu. Namun saya belum beruntung bisa bekerja dengan tim yang begitu teliti. Mereka sering mengabaikan kesalahan seperti itu setengah, kadang-kadang bahkan sebagian besar waktu. Itu adalah daya tarik terbesar bagi saya untuk pengecualian. Jika kami membungkus API ini dan membuatnya seragam throwsaat menemukan kesalahan, pengecualian tidak mungkin diabaikan , dan menurut saya, dan pengalaman, di situlah letak keunggulan pengecualian.

Tetapi jika pengecualian tidak dapat digunakan, maka status kesalahan global, per-utas setidaknya memiliki keuntungan (yang sangat besar dibandingkan dengan mengembalikan kode kesalahan kepada saya) yang mungkin memiliki kesempatan untuk menangkap kesalahan yang lama sedikit lebih lambat daripada ketika itu terjadi di beberapa basis kode ceroboh bukannya langsung hilang itu dan meninggalkan kita benar-benar lupa tentang apa yang terjadi. Kesalahan mungkin terjadi beberapa baris sebelumnya, atau dalam panggilan fungsi sebelumnya, tetapi asalkan perangkat lunak belum crash, kita mungkin dapat mulai bekerja mundur dan mencari tahu di mana dan mengapa itu terjadi.

Sepertinya saya karena pointer jarang, saya harus pergi dengan flag kesalahan internal jika saya memilih untuk menghindari pengecualian.

Saya tidak akan selalu mengatakan pointer jarang. Bahkan ada metode sekarang di C ++ 11 dan seterusnya untuk mendapatkan pointer data yang mendasari wadah, dan nullptrkata kunci baru . Biasanya dianggap tidak bijaksana untuk menggunakan pointer mentah untuk memiliki / mengelola memori jika Anda dapat menggunakan sesuatu seperti unique_ptrsebagai gantinya mengingat betapa pentingnya untuk menyesuaikan RAII dengan adanya pengecualian. Tetapi pointer mentah yang tidak memiliki / mengelola memori tidak selalu dianggap begitu buruk (bahkan dari orang-orang seperti Sutter dan Stroustrup) dan kadang-kadang sangat praktis sebagai cara untuk menunjukkan sesuatu (bersama dengan indeks yang menunjuk pada sesuatu).

Mereka bisa dibilang tidak kalah aman dari iterator penampung standar (setidaknya dalam rilis, iterator yang tidak ada diperiksa) yang tidak akan mendeteksi jika Anda mencoba men-dereferensi mereka setelah tidak valid. C ++ masih tanpa malu-malu sedikit bahasa yang berbahaya, saya katakan, kecuali penggunaan spesifik Anda ingin membungkus semuanya dan menyembunyikan bahkan pointer mentah yang tidak memiliki. Hampir kritis dengan pengecualian bahwa sumber daya sesuai dengan RAII (yang umumnya datang tanpa biaya runtime), tetapi selain itu tidak selalu mencoba menjadi bahasa paling aman untuk digunakan dalam menghindari biaya yang tidak secara eksplisit diinginkan oleh pengembang dalam ditukar dengan sesuatu yang lain. Penggunaan yang disarankan tidak mencoba untuk melindungi Anda dari hal-hal seperti pointer menggantung dan iterator yang tidak valid, jadi untuk berbicara (kalau tidak kita akan didorong untuk menggunakanshared_ptrsemua tempat, yang Stroustrup sangat menentang). Ini mencoba melindungi Anda dari kegagalan untuk benar-benar membebaskan / melepaskan / menghancurkan / membuka / membersihkan sumber daya ketika sesuatu throws.

Energi Naga
sumber
14

Begini masalahnya: Karena sejarah dan fleksibilitas C ++ yang unik, Anda dapat menemukan seseorang yang secara virtual menyatakan segala pendapat tentang fitur apa pun yang Anda inginkan. Namun, secara umum, semakin banyak yang Anda lakukan terlihat seperti C, semakin buruk ide itu.

Salah satu alasan mengapa C ++ jauh lebih longgar dalam hal pengecualian adalah karena Anda tidak bisa tepat return nilkapan pun Anda menginginkannya. Tidak ada yang namanya nildalam sebagian besar kasus dan jenis.

Tapi inilah fakta sederhana. Pengecualian melakukan pekerjaan mereka secara otomatis. Saat Anda melempar pengecualian, RAII mengambil alih dan semuanya ditangani oleh kompiler. Ketika Anda menggunakan kode kesalahan, Anda harus ingat untuk memeriksanya. Ini secara inheren membuat pengecualian secara signifikan lebih aman daripada kode kesalahan - mereka memeriksa sendiri, seolah-olah. Selain itu, mereka lebih ekspresif. Lempar pengecualian dan Anda bisa keluar string yang memberi tahu Anda apa kesalahannya, dan bahkan dapat berisi informasi spesifik, seperti "Parameter X buruk yang memiliki nilai Y bukan Z". Dapatkan kode kesalahan 0xDEADBEEF dan apa, tepatnya, yang salah? Aku berharap dokumentasi yang lengkap dan up-to-date, dan bahkan jika Anda mendapatkan "Bad parameter", itu tidak akan memberitahu Anda yangparameter, nilai apa yang dimilikinya, dan nilai apa yang seharusnya dimiliki. Jika Anda menangkap dengan referensi, mereka juga bisa polimorfik. Akhirnya, pengecualian dapat dilemparkan dari tempat-tempat di mana kode kesalahan tidak pernah bisa, seperti konstruktor. Dan bagaimana dengan algoritma generik? Bagaimana std::for_eachcara menangani kode kesalahan Anda? Pro-tip: Tidak.

Pengecualian jauh lebih unggul dari kode kesalahan dalam segala hal. Pertanyaan sebenarnya adalah dalam pengecualian vs asersi.

Ini masalahnya. Tidak ada yang dapat mengetahui sebelumnya apa prasyarat yang dimiliki program Anda untuk beroperasi, kondisi kegagalan apa yang tidak biasa, yang dapat diperiksa sebelumnya, dan mana yang merupakan bug. Ini umumnya berarti bahwa Anda tidak dapat memutuskan sebelumnya apakah kegagalan yang diberikan harus merupakan pernyataan atau pengecualian tanpa mengetahui logika program. Selain itu, operasi yang dapat dilanjutkan ketika salah satu sub-operasi gagal adalah pengecualian , bukan aturan.

Menurut pendapat saya, ada pengecualian untuk ditangkap. Belum tentu langsung, tapi akhirnya. Pengecualian adalah masalah yang Anda harapkan program dapat pulih dari beberapa titik. Tetapi operasi yang dimaksud tidak pernah bisa pulih dari masalah yang memerlukan pengecualian.

Kegagalan asersi selalu fatal, kesalahan yang tidak dapat dipulihkan. Memori rusak, hal semacam itu.

Jadi, ketika Anda tidak bisa membuka file, apakah itu pernyataan atau pengecualian? Nah, di perpustakaan umum, maka ada banyak skenario di mana kesalahan dapat ditangani, misalnya, memuat file konfigurasi, Anda mungkin hanya menggunakan default yang sudah dibuat sebelumnya, jadi saya akan mengatakan pengecualian.

Sebagai catatan kaki, saya ingin menyebutkan ada beberapa "Null Object Pattern" yang terjadi. Pola ini mengerikan . Dalam sepuluh tahun, itu akan menjadi Singleton berikutnya. Jumlah kasus di mana Anda dapat menghasilkan objek nol yang sesuai adalah sangat kecil .

DeadMG
sumber
Alternatif belum tentu int sederhana. Saya akan lebih cenderung menggunakan sesuatu yang mirip dengan NSError yang saya gunakan dari Obj-C.
Per Johansson
Dia membandingkan bukan dengan C, tetapi dengan Tujuan C. Ini adalah perbedaan besar . Dan kode kesalahannya menjelaskan garis. Al yang Anda katakan itu benar, hanya saja tidak ada jawaban untuk pertanyaan ini. Bukan berarti tersinggung.
Gangnus
Siapa pun yang menggunakan Objective-C tentu saja akan memberi tahu Anda bahwa Anda benar-benar salah.
gnasher729
5

Pengecualian ditemukan karena suatu alasan, untuk menghindari agar semua kode Anda terlihat seperti ini:

bool success = function1(&result1, &err);
if (!success) {
    error_handler(err);
    return;
}

success = function2(&result2, &err);
if (!success) {
    error_handler(err);
    return;
}

Sebagai gantinya, Anda mendapatkan sesuatu yang lebih mirip dengan yang berikut, dengan satu penangan pengecualian naik mainatau berlokasi dengan mudah:

result1 = function1();
result2 = function2();

Beberapa orang mengklaim manfaat kinerja dengan pendekatan tidak terkecuali, tetapi menurut pendapat saya, keterbacaan lebih penting daripada keuntungan kinerja kecil, terutama ketika Anda memasukkan waktu eksekusi untuk semua if (!success)pelat yang harus Anda taburkan di mana-mana, atau risiko lebih sulit untuk melakukan debug segfault jika Anda tidak t memasukkannya, dan mempertimbangkan kemungkinan pengecualian terjadi relatif jarang.

Saya tidak tahu mengapa Apple melarang penggunaan pengecualian. Jika mereka mencoba untuk menghindari penyebaran pengecualian yang tidak ditangani, yang Anda benar-benar capai adalah orang yang menggunakan null pointer untuk menunjukkan pengecualian, sehingga kesalahan programmer menghasilkan pengecualian null pointer daripada file yang jauh lebih berguna tidak ditemukan pengecualian atau apa pun.

Karl Bielefeldt
sumber
1
Ini akan lebih masuk akal jika kode tanpa pengecualian benar-benar terlihat seperti itu, tetapi biasanya tidak. Pola ini muncul ketika error_handler tidak pernah kembali, tetapi jarang sebaliknya.
Per Johansson
1

Dalam posting yang Anda referensi, (pengecualian vs kode kesalahan), saya pikir ada diskusi yang agak berbeda. Pertanyaannya tampaknya apakah Anda memiliki daftar global kode kesalahan #define, mungkin lengkap dengan nama-nama seperti ERR001_FILE_NOT_WRITEABLE (atau disingkat, jika Anda beruntung). Dan, saya pikir poin utama dalam utas itu adalah bahwa jika Anda memprogram dalam bahasa polimorfik, menggunakan instance objek, konstruksi prosedural seperti itu tidak diperlukan. Pengecualian dapat mengungkapkan kesalahan apa yang terjadi hanya berdasarkan jenisnya, dan mereka dapat merangkum informasi seperti pesan apa yang akan dicetak (dan hal-hal lain juga). Jadi, saya akan mengambil percakapan itu sebagai salah satu tentang apakah Anda harus memprogram secara prosedural dalam bahasa berorientasi objek atau tidak.

Tapi, percakapan kapan dan berapa banyak bergantung pada pengecualian untuk menangani situasi yang muncul dalam kode adalah berbeda. Ketika pengecualian dilemparkan dan ditangkap, Anda memperkenalkan paradigma aliran kontrol yang sama sekali berbeda dari tumpukan panggilan tradisional. Melempar pengecualian pada dasarnya adalah pernyataan kebagian yang membuat Anda keluar dari tumpukan panggilan dan mengirim Anda ke beberapa lokasi yang tidak ditentukan (di mana pun kode klien Anda memutuskan untuk menangani pengecualian Anda). Ini membuat logika pengecualian sangat sulit untuk dipikirkan .

Maka, ada sentimen seperti yang diungkapkan oleh jheriko bahwa ini harus diminimalkan. Artinya, katakanlah Anda sedang membaca file yang mungkin atau mungkin tidak ada pada disk. Jheriko dan Apple dan mereka yang berpikir seperti mereka (termasuk saya sendiri) akan berpendapat bahwa melempar pengecualian tidak tepat - tidak adanya file itu tidak luar biasa , seperti yang diharapkan. Pengecualian tidak seharusnya menggantikan aliran kontrol normal. Sangat mudah untuk mengembalikan metode ReadFile () Anda, katakanlah, boolean, dan agar kode klien melihat dari nilai balik yang salah bahwa file tersebut tidak ditemukan. Kode klien kemudian dapat memberi tahu file pengguna tidak ditemukan, atau menangani kasus itu dengan tenang, atau apa pun yang ingin dilakukan.

Ketika Anda melemparkan pengecualian, Anda memaksa klien Anda. Anda memberi mereka kode yang akan, kadang-kadang, merenggut mereka dari tumpukan panggilan normal dan memaksa mereka untuk menulis kode tambahan untuk mencegah aplikasi mereka mogok. Beban yang begitu kuat dan menggelegar hanya akan dipaksakan pada mereka jika benar-benar diperlukan pada saat runtime, atau untuk kepentingan filosofi "gagal awal". Jadi, dalam kasus sebelumnya, Anda dapat melempar pengecualian jika tujuan aplikasi Anda adalah untuk memantau beberapa operasi jaringan dan seseorang mencabut kabel jaringan (Anda menandakan kegagalan besar). Dalam kasus terakhir, Anda dapat melempar pengecualian jika metode Anda mengharapkan parameter non-nol dan Anda diberikan null (Anda memberi sinyal bahwa Anda sedang digunakan secara tidak tepat).

Adapun apa yang harus dilakukan alih-alih pengecualian di dunia berorientasi objek, Anda memiliki opsi di luar konstruksi kode kesalahan prosedural. Salah satu yang segera muncul dalam pikiran adalah bahwa Anda dapat membuat objek yang disebut OperationResult atau semacamnya. Metode dapat mengembalikan OperationResult dan objek itu dapat memiliki informasi tentang apakah operasi berhasil atau tidak, dan informasi apa pun yang Anda inginkan (pesan status, misalnya, tetapi juga, yang lebih halus, dapat merangkum strategi untuk pemulihan kesalahan ). Mengembalikan ini alih-alih melemparkan pengecualian memungkinkan polimorfisme, mempertahankan aliran kontrol, dan membuat proses debug jauh lebih sederhana.

Erik Dietrich
sumber
Saya setuju dengan Anda, tapi itu alasan yang paling tidak membuat saya mengajukan pertanyaan.
Per Johansson
0

Ada beberapa bab dalam Clean Code yang membahas tentang hal ini - minus sudut pandang Apple.

Ide dasarnya adalah bahwa Anda tidak pernah mengembalikan nol dari fungsi Anda, karena itu:

  • Menambahkan banyak kode duplikat
  • Membuat kode Anda berantakan - Yaitu: Menjadi lebih sulit untuk dibaca
  • Sering dapat mengakibatkan kesalahan nil-pointer - atau akses pelanggaran tergantung pada pemrograman "agama" Anda

Gagasan lain yang menyertainya adalah bahwa Anda menggunakan pengecualian karena hal ini membantu untuk mengurangi kekacauan dalam kode Anda, dan bukannya perlu membuat serangkaian kode kesalahan yang akhirnya menjadi sulit untuk dilacak pengelolaan dan pemeliharaannya (terutama di beberapa modul dan platform) dan pelanggan sulit untuk menguraikan, Anda malah membuat pesan yang bermakna yang mengidentifikasi sifat pasti dari masalah, membuat debugging lebih sederhana, dan dapat mengurangi kode penanganan kesalahan Anda menjadi sesuatu yang sederhana seperti try..catchpernyataan dasar .

Kembali di hari-hari pengembangan COM saya, saya punya proyek di mana setiap kegagalan menimbulkan pengecualian, dan setiap pengecualian diperlukan untuk menggunakan id kode kesalahan yang unik berdasarkan perpanjangan pengecualian standar windows COM. Itu adalah penahanan dari proyek sebelumnya di mana kode kesalahan sebelumnya telah digunakan, tetapi perusahaan ingin pergi semua berorientasi objek dan melompat pada kereta musik COM tanpa mengubah cara mereka melakukan banyak hal. Hanya butuh 4 bulan bagi mereka untuk kehabisan nomor kode kesalahan dan jatuh ke saya untuk refactor traktat besar kode untuk menggunakan pengecualian. Lebih baik selamatkan usaha Anda dari depan dan gunakan saja pengecualian dengan bijaksana.

S.Robins
sumber
Terima kasih, tetapi saya tidak mempertimbangkan untuk mengembalikan NULL. Sepertinya tidak mungkin dalam menghadapi konstruktor. Seperti yang saya sebutkan saya mungkin harus menggunakan flag kesalahan pada objek.
Per Johansson