Banyak bahasa modern menyediakan fitur penanganan pengecualian yang kaya , tetapi bahasa pemrograman Apple Swift tidak menyediakan mekanisme penanganan pengecualian .
Tenggelam dalam pengecualian seperti saya, saya mengalami kesulitan membungkus pikiran saya di sekitar apa artinya ini. Swift memiliki asersi, dan tentu saja mengembalikan nilai; tetapi saya mengalami kesulitan membayangkan bagaimana cara berpikir saya yang berbasis pengecualian memetakan ke sebuah dunia tanpa pengecualian (dan, dalam hal ini, mengapa dunia seperti itu diinginkan ). Adakah hal-hal yang tidak dapat saya lakukan dalam bahasa seperti Swift yang bisa saya lakukan dengan pengecualian? Apakah saya mendapatkan sesuatu dengan kehilangan pengecualian?
Bagaimana misalnya saya bisa mengekspresikan sesuatu seperti
try:
operation_that_can_throw_ioerror()
except IOError:
handle_the_exception_somehow()
else:
# we don't want to catch the IOError if it's raised
another_operation_that_can_throw_ioerror()
finally:
something_we_always_need_to_do()
dalam bahasa (Swift, misalnya) yang tidak memiliki penanganan pengecualian?
panic
yang tidak persis sama. Selain apa yang dikatakan di sana, pengecualian tidak lebih dari cara yang canggih (tapi nyaman) untuk melakukanGOTO
, meskipun tidak ada yang menyebutnya seperti itu, untuk alasan yang jelas.Jawaban:
Dalam pemrograman tersemat, pengecualian secara tradisional tidak diizinkan, karena overhead tumpukan yang ingin Anda lakukan dianggap sebagai variabilitas yang tidak dapat diterima ketika mencoba mempertahankan kinerja waktu-nyata. Sementara smartphone secara teknis dapat dianggap sebagai platform waktu nyata, mereka cukup kuat sekarang di mana batasan lama sistem embedded tidak benar-benar berlaku lagi. Saya hanya membawanya demi ketelitian.
Pengecualian sering didukung dalam bahasa pemrograman fungsional, tetapi sangat jarang digunakan sehingga mungkin juga tidak. Salah satu alasannya adalah evaluasi malas, yang dilakukan sesekali bahkan dalam bahasa yang tidak malas secara default. Memiliki fungsi yang dieksekusi dengan tumpukan berbeda dari tempat yang harus dijalankan akan membuatnya sulit untuk menentukan di mana harus meletakkan penangan pengecualian Anda.
Alasan lain adalah fungsi kelas satu memungkinkan untuk konstruksi seperti opsi dan futures yang memberi Anda manfaat sintaksis pengecualian dengan lebih banyak fleksibilitas. Dengan kata lain, sisa bahasa cukup ekspresif sehingga pengecualian tidak membelikan Anda apa pun.
Saya tidak terbiasa dengan Swift, tetapi sedikit yang saya baca tentang penanganan kesalahannya menyarankan mereka dimaksudkan untuk penanganan kesalahan untuk mengikuti lebih banyak pola gaya fungsional. Saya telah melihat contoh kode dengan
success
danfailure
blok yang sangat mirip futures.Berikut ini contoh penggunaan
Future
dari tutorial Scala ini :Anda dapat melihatnya memiliki struktur yang kira-kira sama dengan contoh Anda menggunakan pengecualian. The
future
blok sepertitry
. TheonFailure
blok seperti handler pengecualian. Dalam Scala, seperti dalam kebanyakan bahasa fungsional,Future
diimplementasikan sepenuhnya menggunakan bahasa itu sendiri. Itu tidak memerlukan sintaks khusus seperti pengecualian. Itu berarti Anda dapat menentukan konstruksi serupa Anda sendiri. Mungkin menambahkantimeout
blok, misalnya, atau fungsi logging.Selain itu, Anda dapat meneruskan masa depan, mengembalikannya dari fungsi, menyimpannya dalam struktur data, atau apa pun. Ini nilai kelas satu. Anda tidak terbatas seperti pengecualian yang harus disebarkan langsung ke atas tumpukan.
Opsi menyelesaikan masalah penanganan kesalahan dengan cara yang sedikit berbeda, yang berfungsi lebih baik untuk beberapa kasus penggunaan. Anda tidak terjebak hanya dengan satu metode.
Itu adalah hal-hal yang Anda "dapatkan dengan kehilangan pengecualian."
sumber
Future
pada dasarnya adalah cara memeriksa nilai yang dikembalikan dari suatu fungsi, tanpa berhenti untuk menunggu. Seperti Swift, ini berbasis nilai kembali, tetapi tidak seperti Swift, respons terhadap nilai kembali dapat terjadi di lain waktu (sedikit seperti pengecualian). Baik?Future
benar, tapi saya pikir Anda mungkin salah menandai Swift. Lihat bagian pertama dari jawaban stackoverflow ini , misalnya.Either
akan menjadi contoh yang lebih baik IMHOPengecualian dapat membuat kode lebih sulit untuk dipikirkan. Meskipun mereka tidak sekuat goto, mereka dapat menyebabkan banyak masalah yang sama karena sifat non-lokal mereka. Misalnya, katakan Anda memiliki kode imperatif seperti ini:
Anda tidak bisa mengetahui apakah prosedur ini dapat memberikan pengecualian. Anda harus membaca dokumentasi masing-masing prosedur ini untuk mencari tahu. (Beberapa bahasa membuat ini sedikit lebih mudah dengan menambah jenis tanda tangan dengan informasi ini.) Kode di atas akan dikompilasi dengan baik terlepas dari apakah ada prosedur yang dilemparkan, sehingga sangat mudah untuk melupakan untuk menangani pengecualian.
Selain itu, bahkan jika tujuannya adalah untuk menyebarkan kembali pengecualian ke penelepon, orang sering perlu menambahkan kode tambahan untuk mencegah hal-hal tertinggal dalam keadaan tidak konsisten (misalnya jika pembuat kopi Anda rusak, Anda masih perlu membersihkan kekacauan dan kembali. cangkirnya!). Jadi, dalam banyak kasus kode yang menggunakan pengecualian akan terlihat sama rumitnya dengan kode yang tidak karena pembersihan tambahan yang diperlukan.
Pengecualian dapat ditiru dengan sistem tipe yang cukup kuat. Banyak bahasa yang menghindari pengecualian menggunakan nilai balik untuk mendapatkan perilaku yang sama. Ini mirip dengan bagaimana hal itu dilakukan dalam C, tetapi sistem tipe modern biasanya membuatnya lebih elegan dan juga lebih sulit untuk melupakan kondisi kesalahan. Mereka juga dapat memberikan gula sintaksis untuk membuat hal-hal yang kurang rumit, kadang-kadang hampir sebersih dengan pengecualian.
Secara khusus, dengan menanamkan penanganan kesalahan ke dalam sistem tipe daripada menerapkan sebagai fitur terpisah memungkinkan "pengecualian" digunakan untuk hal-hal lain yang bahkan tidak terkait dengan kesalahan. (Sudah diketahui bahwa penanganan pengecualian sebenarnya adalah properti dari monad.)
sumber
goto
: Yanggoto
dibatasi untuk satu fungsi, yang merupakan rentang yang cukup kecil asalkan fungsi benar-benar kecil, pengecualian bertindak lebih seperti beberapa tanda silanggoto
dancome from
(lihat en.wikipedia.org/wiki/INTERCAL ;-)). Ini cukup banyak dapat menghubungkan dua bagian kode, mungkin melompati kode beberapa fungsi ketiga. Satu-satunya hal yang tidak bisa dilakukan, yanggoto
bisa, akan kembali.Ada beberapa jawaban bagus di sini, tapi saya pikir satu alasan penting belum cukup ditekankan: Ketika pengecualian terjadi, objek dapat dibiarkan dalam keadaan tidak valid. Jika Anda dapat "menangkap" suatu pengecualian, maka kode handler pengecualian Anda akan dapat mengakses dan bekerja dengan objek-objek yang tidak valid. Itu akan menjadi sangat salah kecuali kode untuk objek-objek itu ditulis dengan sempurna, yang sangat, sangat sulit dilakukan.
Misalnya, bayangkan menerapkan Vector. Jika seseorang instantiate Vector Anda dengan satu set objek, tetapi pengecualian terjadi selama inisialisasi (mungkin, katakanlah, saat menyalin objek Anda ke memori yang baru dialokasikan), sangat sulit untuk benar kode implementasi vektor sedemikian rupa sehingga tidak ada memori bocor. Makalah singkat oleh Stroustroup ini membahas contoh Vektor .
Dan itu hanyalah puncak gunung es. Bagaimana jika, misalnya, Anda telah menyalin beberapa elemen, tetapi tidak semua elemen? Untuk menerapkan wadah seperti Vektor dengan benar, Anda hampir harus membuat setiap tindakan yang Anda ambil reversibel, sehingga seluruh operasi bersifat atomik (seperti transaksi basis data). Ini rumit, dan sebagian besar aplikasi salah. Dan bahkan ketika itu dilakukan dengan benar, itu sangat mempersulit proses penerapan wadah.
Jadi beberapa bahasa modern telah memutuskan itu tidak layak. Karat, misalnya, memang memiliki pengecualian, tetapi tidak dapat "ditangkap", jadi tidak ada cara bagi kode untuk berinteraksi dengan objek dalam keadaan tidak valid.
sumber
Satu hal yang saya awalnya terkejut tentang bahasa Rust adalah tidak mendukung pengecualian tangkapan. Anda bisa melempar pengecualian, tetapi hanya runtime yang bisa menangkapnya saat tugas (pikirkan utas, tetapi tidak selalu utas OS terpisah) mati; jika Anda memulai tugas sendiri, maka Anda dapat bertanya apakah itu keluar secara normal atau apakah itu
fail!()
diedit.Karena itu tidak
fail
sering terlalu idiomatis . Beberapa kasus di mana hal itu terjadi adalah, misalnya, di harness pengujian (yang tidak tahu seperti apa kode pengguna), sebagai tingkat atas dari kompiler (kebanyakan garpu kompiler sebagai gantinya), atau ketika meminta panggilan balik pada input pengguna.Sebaliknya, idiom umum adalah dengan menggunakan satu
Result
template yang secara eksplisit melewatkan kesalahan yang harus ditangani. Ini dibuat secara signifikan lebih mudah dengan paratry!
makro , yang dapat membungkus ekspresi apapun bahwa hasil sebuah Hasil dan menghasilkan lengan sukses jika ada satu, atau kembali awal dari fungsi.sumber
assert
, tetapi tidakcatch
?Menurut pendapat saya, pengecualian adalah alat penting untuk mendeteksi kesalahan kode pada saat dijalankan. Baik dalam pengujian dan dalam produksi. Buat pesan mereka cukup jelas sehingga dikombinasikan dengan jejak tumpukan Anda dapat mengetahui apa yang terjadi dari log.
Pengecualian sebagian besar merupakan alat pengembangan dan cara untuk mendapatkan laporan kesalahan yang wajar dari produksi dalam kasus yang tidak terduga.
Terlepas dari pemisahan kekhawatiran (jalan bahagia dengan hanya kesalahan yang diharapkan vs. jatuh hingga mencapai beberapa penangan generik untuk kesalahan tak terduga) menjadi hal yang baik, membuat kode Anda lebih mudah dibaca dan dipelihara, sebenarnya tidak mungkin untuk menyiapkan kode Anda untuk semua kemungkinan kasus yang tak terduga, bahkan dengan membengkaknya dengan kode penanganan kesalahan untuk menyelesaikan pembacaan tidak terbaca.
Itu sebenarnya arti dari "tak terduga".
Btw., Apa yang diharapkan dan apa yang tidak adalah keputusan yang hanya dapat dibuat di situs panggilan. Itulah sebabnya pengecualian yang diperiksa di Jawa tidak berhasil - keputusan dibuat pada saat mengembangkan API, ketika sama sekali tidak jelas apa yang diharapkan atau tidak terduga.
Contoh sederhana: API peta hash dapat memiliki dua metode:
dan
yang pertama melempar pengecualian jika tidak ditemukan, yang terakhir memberi Anda nilai opsional. Dalam beberapa kasus, yang terakhir lebih masuk akal, tetapi dalam kasus lain, kode Anda sinmply harus mengharapkan ada nilai untuk kunci yang diberikan, jadi jika tidak ada, itu adalah kesalahan yang tidak dapat diperbaiki oleh kode ini karena dasar asumsi telah gagal. Dalam hal ini sebenarnya perilaku yang diinginkan untuk keluar dari jalur kode dan turun ke beberapa penangan generik jika panggilan gagal.
Kode seharusnya tidak mencoba menangani asumsi dasar yang gagal.
Kecuali dengan memeriksa mereka dan melemparkan pengecualian yang bisa dibaca dengan baik, tentu saja.
Mengecualikan pengecualian bukanlah kejahatan tetapi menangkapnya mungkin. Jangan mencoba memperbaiki kesalahan yang tidak terduga. Tangkap pengecualian di beberapa tempat di mana Anda ingin melanjutkan loop atau operasi, catat, mungkin melaporkan kesalahan yang tidak diketahui, dan hanya itu.
Menangkap balok di semua tempat adalah ide yang sangat buruk.
Rancang API Anda dengan cara yang memudahkan Anda mengekspresikan niat Anda, yaitu menyatakan apakah Anda mengharapkan kasus tertentu, seperti kunci tidak ditemukan, atau tidak. Pengguna API Anda kemudian dapat memilih panggilan lempar untuk kasus yang benar-benar tidak terduga.
Saya kira alasan orang membenci pengecualian dan melangkah terlalu jauh dengan menghilangkan alat penting ini untuk otomatisasi penanganan kesalahan dan pemisahan kekhawatiran yang lebih baik dari bahasa baru adalah pengalaman buruk.
Itu, dan beberapa kesalahpahaman tentang apa yang sebenarnya baik untuk mereka.
Mensimulasi mereka dengan melakukan SEMUANYA melalui pengikatan monadik membuat kode Anda kurang mudah dibaca dan dikelola, dan Anda berakhir tanpa jejak tumpukan, yang membuat pendekatan ini CURAH.
Penanganan kesalahan gaya fungsional sangat bagus untuk kasus kesalahan yang diharapkan.
Biarkan penanganan pengecualian secara otomatis menangani yang lainnya, itulah gunanya :)
sumber
Swift menggunakan prinsip yang sama di sini sebagai Objective-C, hanya lebih konsekuensinya. Di Objective-C, pengecualian mengindikasikan kesalahan pemrograman. Mereka tidak ditangani kecuali dengan alat pelaporan kerusakan. "Pengecualian penanganan" dilakukan dengan memperbaiki kode. (Ada beberapa pengecualian ahem. Misalnya dalam komunikasi antar proses. Tapi itu cukup langka dan banyak orang tidak pernah mengalami itu. Dan Objective-C sebenarnya telah mencoba / menangkap / akhirnya / melempar, tetapi Anda jarang menggunakannya). Swift baru saja menghilangkan kemungkinan untuk menangkap pengecualian.
Swift memiliki fitur yang tampak seperti penanganan pengecualian tetapi hanya penanganan kesalahan yang ditegakkan. Secara historis, Objective-C memiliki pola penanganan kesalahan yang cukup luas: Suatu metode akan mengembalikan BOOL (YA untuk sukses) atau referensi objek (nil untuk kegagalan, bukan nihil untuk sukses), dan memiliki parameter "pointer ke NSError *" yang akan digunakan untuk menyimpan referensi NSError. Swift secara otomatis mengubah panggilan ke metode seperti itu menjadi sesuatu yang tampak seperti penanganan pengecualian.
Secara umum, fungsi Swift dapat dengan mudah mengembalikan alternatif, seperti hasil jika fungsi berfungsi dengan baik dan kesalahan jika gagal; yang membuat penanganan kesalahan jauh lebih mudah. Tetapi jawaban untuk pertanyaan awal: Desainer Swift jelas merasa bahwa menciptakan bahasa yang aman, dan menulis kode yang berhasil dalam bahasa seperti itu, lebih mudah jika bahasa tersebut tidak memiliki pengecualian.
sumber
Dalam C Anda akan berakhir dengan sesuatu seperti di atas.
Tidak, tidak ada apa-apa. Anda akhirnya menangani kode hasil alih-alih pengecualian.
Pengecualian memungkinkan Anda untuk mengatur ulang kode Anda sehingga penanganan kesalahan terpisah dari kode jalur bahagia Anda, tetapi hanya itu saja.
sumber
...throw_ioerror()
mengembalikan kesalahan daripada melemparkan pengecualian?Result<T, E>
enum, yang bisa berupaOk<T>
, atauErr<E>
, denganT
menjadi tipe yang Anda inginkan jika ada, danE
menjadi tipe yang mewakili kesalahan. Pencocokan pola dan beberapa metode tertentu menyederhanakan penanganan keberhasilan dan kegagalan. Singkatnya, jangan menganggap kurangnya pengecualian secara otomatis berarti kode kesalahan.Selain jawaban Charlie:
Contoh-contoh penanganan pengecualian yang dinyatakan yang Anda lihat di banyak manual dan buku ini, terlihat sangat cerdas hanya pada contoh yang sangat kecil.
Bahkan jika Anda mengesampingkan argumen tentang keadaan objek yang tidak valid, mereka selalu menimbulkan rasa sakit yang sangat besar ketika berhadapan dengan aplikasi besar.
Misalnya, ketika Anda harus berurusan dengan IO, menggunakan beberapa kriptografi, Anda mungkin memiliki 20 jenis pengecualian yang dapat dibuang dari 50 metode. Bayangkan jumlah kode penanganan pengecualian yang Anda perlukan. Penanganan pengecualian akan membutuhkan kode beberapa kali lebih banyak daripada kode itu sendiri.
Pada kenyataannya Anda tahu kapan pengecualian tidak dapat muncul dan Anda tidak perlu menulis begitu banyak penanganan pengecualian, jadi Anda hanya menggunakan beberapa solusi untuk mengabaikan pengecualian yang dinyatakan. Dalam praktik saya, hanya sekitar 5% dari pengecualian yang dinyatakan perlu ditangani dalam kode untuk memiliki aplikasi yang andal.
sumber