Beberapa bahasa mengklaim tidak memiliki "pengecualian runtime" sebagai keunggulan yang jelas dari bahasa lain yang memilikinya.
Saya bingung tentang masalah ini.
Pengecualian runtime hanyalah alat, sejauh yang saya tahu, dan bila digunakan dengan baik:
- Anda dapat mengomunikasikan status "kotor" (melempar data yang tidak terduga)
- menambahkan tumpukan Anda dapat menunjuk ke rantai kesalahan
- Anda dapat membedakan antara kekacauan (mis. mengembalikan nilai kosong pada input yang tidak valid) dan penggunaan yang tidak aman yang membutuhkan perhatian pengembang (mis. melempar pengecualian pada input yang tidak valid)
- Anda dapat menambahkan detail ke kesalahan Anda dengan pesan pengecualian yang memberikan rincian bermanfaat lebih lanjut membantu upaya debugging (secara teoritis)
Di sisi lain saya merasa sangat sulit untuk men-debug perangkat lunak yang "menelan" pengecualian. Misalnya
try {
myFailingCode();
} catch {
// no logs, no crashes, just a dirty state
}
Jadi pertanyaannya adalah: apa keuntungan teoretis yang kuat dari "tidak ada pengecualian runtime"?
Contoh
Dalam praktiknya tidak ada kesalahan runtime. Tidak nol. Tidak terdefinisi bukan fungsi.
Jawaban:
Pengecualian memiliki semantik yang sangat terbatas. Mereka harus ditangani tepat di mana mereka dilemparkan, atau di tumpukan panggilan langsung ke atas, dan tidak ada indikasi kepada programmer pada waktu kompilasi jika Anda lupa melakukannya.
Bandingkan ini dengan Elm di mana kesalahan dikodekan sebagai Hasil atau Maybes , yang keduanya nilai . Itu berarti Anda mendapatkan kesalahan kompiler jika Anda tidak menangani kesalahan. Anda dapat menyimpannya dalam variabel atau bahkan koleksi untuk menunda penanganannya ke waktu yang nyaman. Anda dapat membuat fungsi untuk menangani kesalahan dengan cara khusus aplikasi alih-alih mengulangi blok try-catch yang sangat mirip di semua tempat. Anda dapat mengaitkannya ke dalam perhitungan yang hanya berhasil jika semua bagiannya berhasil, dan mereka tidak harus dijejalkan ke dalam satu blok uji coba. Anda tidak dibatasi oleh sintaks bawaan.
Ini tidak seperti "menelan pengecualian." Itu membuat kondisi kesalahan eksplisit dalam sistem tipe dan menyediakan semantik alternatif yang jauh lebih fleksibel untuk menanganinya.
Perhatikan contoh berikut. Anda dapat menempelkan ini ke http://elm-lang.org/try jika Anda ingin melihatnya beraksi.
Perhatikan
String.toInt
dicalculate
fungsi memiliki kemungkinan gagal. Di Jawa, ini berpotensi melempar pengecualian runtime. Saat membaca input pengguna, ia memiliki peluang yang cukup bagus. Elm malah memaksa saya untuk menghadapinya dengan mengembalikanResult
, tapi perhatikan saya tidak harus menghadapinya segera. Saya dapat menggandakan input dan mengonversinya menjadi string, kemudian memeriksa input yang buruk dalamgetDefault
fungsi. Tempat ini jauh lebih cocok untuk pemeriksaan daripada titik di mana kesalahan terjadi atau naik di tumpukan panggilan.Cara kompiler memaksa tangan kita juga jauh lebih bagus daripada pengecualian Java yang diperiksa. Anda perlu menggunakan fungsi yang sangat spesifik
Result.withDefault
untuk mengekstraksi nilai yang Anda inginkan. Meskipun secara teknis Anda bisa menyalahgunakan mekanisme semacam itu, tidak ada gunanya. Karena Anda dapat menunda keputusan sampai Anda tahu pesan default / error yang baik untuk dimasukkan, tidak ada alasan untuk tidak menggunakannya.sumber
That means you get a compiler error if you don't handle the error.
- Nah, itulah alasan di balik Pengecualian Diperiksa di Jawa, tapi kita semua tahu seberapa baik itu berhasil.Maybe
,Either
, dll Elm terlihat seperti itu mengambil halaman dari bahasa seperti ML, OCaml atau Haskell.x = some_func()
, saya tidak perlu melakukan apa pun kecuali saya ingin memeriksa nilainyax
, dalam hal ini saya dapat memeriksa apakah saya memiliki kesalahan atau nilai "valid"; Selain itu, ini adalah kesalahan jenis statis untuk mencoba menggunakan satu di tempat yang lain, jadi saya tidak bisa melakukannya. Jika tipe Elm berfungsi seperti bahasa fungsional lainnya, saya sebenarnya dapat melakukan hal-hal seperti menulis nilai dari fungsi yang berbeda bahkan sebelum saya tahu apakah itu kesalahan atau tidak! Ini adalah tipikal dari bahasa FP.Untuk memahami pernyataan ini, pertama-tama kita harus memahami apa yang dibeli oleh sistem tipe statis. Pada dasarnya, apa yang diberikan oleh sistem tipe statis adalah jaminan: jika tipe program memeriksa, kelas perilaku runtime tertentu tidak dapat terjadi.
Kedengarannya menyenangkan. Ya, pemeriksa tipe mirip dengan pemeriksa teorema. (Sebenarnya, menurut Curry-Howard-Isomorphism, mereka adalah hal yang sama.) Satu hal yang sangat aneh tentang teorema adalah ketika Anda membuktikan teorema, Anda membuktikan dengan tepat apa yang dikatakan teorema, tidak lebih. (Itu misalnya, mengapa, ketika seseorang mengatakan "Saya telah membuktikan program ini benar", Anda harus selalu bertanya "tolong jelaskan 'benar'".) Hal yang sama berlaku untuk sistem ketik. Ketika kita mengatakan "suatu program adalah tipe-aman", yang kami maksud bukanlah bahwa tidak ada kesalahan yang mungkin terjadi. Kita hanya bisa mengatakan bahwa kesalahan yang dijanjikan oleh sistem tipe kita untuk mencegah tidak terjadi.
Jadi, program dapat memiliki banyak perilaku runtime yang tak terhingga. Dari mereka, banyak yang tak terhingga berguna, tetapi juga yang tak terhingga banyak yang "salah" (untuk berbagai definisi "benar"). Suatu sistem tipe statis memungkinkan kita untuk membuktikan bahwa suatu himpunan terbatas yang pasti dan pasti dari banyak perilaku runtime yang tak terhingga tidak dapat terjadi.
Perbedaan antara sistem tipe yang berbeda pada dasarnya adalah di mana, berapa banyak, dan seberapa kompleks perilaku runtime yang mereka buktikan tidak terjadi. Sistem tipe lemah seperti Java hanya dapat membuktikan hal-hal yang sangat mendasar. Sebagai contoh, Java dapat membuktikan bahwa metode yang diketikkan sebagai mengembalikan
String
tidak dapat mengembalikan aList
. Tapi, misalnya, dapat tidak membuktikan bahwa metode ini tidak akan tidak kembali. Itu juga tidak dapat membuktikan bahwa metode ini tidak akan memberikan pengecualian. Dan itu tidak dapat membuktikan bahwa itu tidak akan mengembalikan yang salahString
- ada yangString
akan memenuhi pemeriksa tipe. (Dan, tentu saja, bahkannull
akan memuaskan juga.) Bahkan ada hal-hal yang sangat sederhana bahwa Jawa tidak dapat membuktikan, itulah sebabnya mengapa kita memiliki pengecualian sepertiArrayStoreException
,ClassCastException
, atau semua orang favorit, yangNullPointerException
.Sistem tipe yang lebih kuat seperti Agda juga dapat membuktikan hal-hal seperti "akan mengembalikan jumlah dari dua argumen" atau "mengembalikan versi daftar yang disortir sebagai argumen".
Sekarang, apa yang dimaksud oleh desainer Elm dengan pernyataan bahwa mereka tidak memiliki pengecualian runtime adalah bahwa sistem tipe Elm dapat membuktikan tidak adanya (sebagian besar dari) perilaku runtime yang dalam bahasa lain tidak dapat dibuktikan tidak terjadi dan dengan demikian dapat menyebabkan untuk perilaku yang salah saat runtime (yang dalam kasus terbaik berarti pengecualian, dalam kasus yang lebih buruk berarti crash, dan dalam kasus terburuk dari semua berarti tidak ada crash, tidak ada pengecualian, dan hanya hasil yang salah secara diam-diam).
Jadi, mereka tidak mengatakan "kami tidak menerapkan pengecualian". Mereka mengatakan "hal-hal yang akan menjadi pengecualian runtime dalam bahasa khas yang dialami oleh programmer khas Elm, ditangkap oleh sistem tipe". Tentu saja, seseorang yang datang dari Idris, Agda, Guru, Epigram, Isabelle / HOL, Coq, atau bahasa serupa akan melihat Elm sebagai sangat lemah dalam perbandingan. Pernyataan ini lebih ditujukan untuk programmer Java, C♯, C ++, Objective-C, PHP, ECMAScript, Python, Ruby, Perl,… programmer khas.
sumber
Elm dapat menjamin tidak ada pengecualian runtime karena alasan yang sama C dapat menjamin tidak ada pengecualian runtime: Bahasa tidak mendukung konsep pengecualian.
Elm memiliki cara memberi sinyal kondisi kesalahan saat runtime, tetapi sistem ini bukan pengecualian, ini adalah "Hasil". Fungsi yang mungkin gagal mengembalikan "Hasil" yang berisi nilai reguler atau kesalahan. Elms sangat diketik, jadi ini eksplisit dalam sistem tipe. Jika suatu fungsi selalu mengembalikan integer, ia memiliki tipe
Int
. Tetapi jika ia mengembalikan integer atau gagal, tipe pengembaliannya adalahResult Error Int
. (String adalah pesan kesalahan.) Ini memaksa Anda untuk secara eksplisit menangani kedua kasus di situs panggilan.Berikut adalah contoh dari pendahuluan (sedikit disederhanakan):
Fungsi ini
toInt
dapat gagal jika input tidak dapat diuraikan, jadi tipe pengembaliannya adalahResult String int
. Untuk mendapatkan nilai integer yang sebenarnya, Anda harus "membongkar" melalui pencocokan pola, yang pada gilirannya memaksa Anda untuk menangani kedua kasus.Hasil dan pengecualian pada dasarnya melakukan hal yang sama, perbedaan penting adalah "default". Pengecualian akan muncul dan menghentikan program secara default, dan Anda harus menangkapnya secara eksplisit jika ingin menanganinya. Hasilnya adalah sebaliknya - Anda dipaksa untuk menanganinya secara default, jadi Anda harus meneruskannya sampai ke puncak jika Anda ingin mereka menghentikan program. Sangat mudah untuk melihat bagaimana perilaku ini dapat menyebabkan kode yang lebih kuat.
sumber
doSomeStuff(x: Int): Int
. Biasanya Anda mengharapkannya mengembalikanInt
, tetapi bisakah itu memberikan pengecualian juga? Tanpa melihat kode sumbernya, Anda tidak bisa tahu. Sebaliknya, bahasa B yang mengkodekan kesalahan melalui tipe mungkin memiliki fungsi yang sama dideklarasikan seperti ini:doSomeStuff(x: Int): ErrorOrResultOfType<Int>
(dalam Elm jenis ini sebenarnya bernamaResult
). Tidak seperti pada kasus pertama, sekarang jelas apakah fungsi tersebut mungkin gagal, dan Anda harus menanganinya secara eksplisit.this is how you program in languages such as ML or Haskell
Di Haskell, ya; ML, tidak. Robert Harper, kontributor utama bagi ML Standar dan peneliti bahasa pemrograman, menganggap pengecualian berguna . Jenis kesalahan dapat menghalangi komposisi fungsi dalam kasus di mana Anda dapat menjamin kesalahan tidak akan terjadi. Pengecualian juga memiliki kinerja yang berbeda. Anda tidak membayar untuk pengecualian yang tidak dilemparkan, tetapi Anda membayar untuk memeriksa nilai kesalahan setiap waktu, dan pengecualian adalah cara alami untuk mengekspresikan backtracking dalam beberapa algoritmaPertama, harap perhatikan bahwa contoh pengecualian "menelan" pada umumnya adalah praktik yang mengerikan dan sama sekali tidak terkait dengan tidak memiliki pengecualian waktu berjalan; ketika Anda memikirkannya, Anda memang memiliki kesalahan run time, tetapi Anda memilih untuk menyembunyikannya dan tidak melakukan apa-apa. Ini sering menghasilkan bug yang sulit dimengerti.
Pertanyaan ini dapat ditafsirkan dalam beberapa cara, tetapi karena Anda menyebutkan Elm dalam komentar, konteksnya lebih jelas.
Elm, antara lain, adalah bahasa pemrograman yang diketik secara statis . Salah satu manfaat dari sistem jenis ini adalah bahwa banyak kelas kesalahan (meskipun tidak semua) ditangkap oleh kompiler, sebelum program tersebut benar-benar digunakan. Beberapa jenis kesalahan dapat dikodekan dalam jenis (seperti Elm
Result
danTask
), bukannya dibuang sebagai pengecualian. Inilah yang dimaksudkan oleh para desainer Elm: banyak kesalahan akan ditangkap pada waktu kompilasi alih-alih pada "run time", dan kompiler akan memaksa Anda untuk menghadapinya alih-alih mengabaikannya dan berharap yang terbaik. Sudah jelas mengapa ini merupakan keuntungan: lebih baik programmer mengetahui masalah sebelum pengguna melakukannya.Perhatikan bahwa saat Anda tidak menggunakan pengecualian, kesalahan dikodekan dengan cara lain yang tidak terlalu mengejutkan. Dari dokumentasi Elm :
Desainer Elm agak berani mengklaim "tidak ada pengecualian waktu berjalan" , meskipun mereka memenuhi syarat dengan "dalam praktik". Apa yang mereka maksudkan adalah "kesalahan yang lebih tidak terduga daripada jika Anda mengkode dalam javascript".
sumber
Result
danTask
yang terlihat sangat mirip dengan yang lebih akrabEither
danFuture
dari bahasa lain. Tidak seperti pengecualian, nilai dari tipe ini dapat digabungkan dan pada titik tertentu Anda harus menanganinya secara eksplisit: apakah mereka mewakili nilai yang valid atau kesalahan? Saya tidak membaca pikiran, tetapi kurangnya mengejutkan programmer mungkin adalah apa yang dimaksud oleh desainer Elm dengan "tidak ada pengecualian waktu berjalan" :)Elm mengklaim:
Tetapi Anda bertanya tentang pengecualian runtime . Ada perbedaan.
Di Elm, tidak ada yang mengembalikan hasil yang tidak terduga. Anda TIDAK bisa menulis program yang valid di Elm yang menghasilkan kesalahan runtime. Dengan demikian, Anda tidak perlu pengecualian.
Jadi, pertanyaannya adalah:
Jika Anda dapat menulis kode yang tidak pernah memiliki kesalahan runtime, program Anda tidak akan pernah macet.
sumber