Kami menggunakan pengecualian untuk memungkinkan konsumen kode menangani perilaku tak terduga dengan cara yang bermanfaat. Biasanya pengecualian dibangun di sekitar skenario "apa yang terjadi" - seperti FileNotFound
(kami tidak dapat menemukan file yang Anda tentukan) atau ZeroDivisionError
(kami tidak dapat melakukan 1/0
operasi).
Bagaimana jika ada kemungkinan untuk menentukan perilaku yang diharapkan dari konsumen?
Misalnya, bayangkan kami memiliki fetch
sumber daya, yang melakukan permintaan HTTP dan mengembalikan data yang diambil. Dan alih-alih kesalahan seperti ServiceTemporaryUnavailable
atau RateLimitExceeded
kita hanya akan mengajukan RetryableError
saran kepada konsumen bahwa itu hanya harus mencoba kembali permintaan dan tidak peduli dengan kegagalan spesifik. Jadi, pada dasarnya kami menyarankan tindakan kepada penelepon - "apa yang harus dilakukan".
Kami tidak sering melakukan ini karena kami tidak tahu semua penggunaan konsumen. Tetapi bayangkan itu adalah beberapa komponen khusus yang kita tahu tindakan terbaik untuk penelepon - jadi haruskah kita menggunakan pendekatan "apa yang harus dilakukan"?
sumber
if( ! function() ) handle_something();
, tetapi bisa menangani kesalahan di suatu tempat Anda benar-benar tahu konteks panggilan - yaitu memberitahu klien untuk memanggil admin sistem jika server Anda gagal atau memuat ulang secara otomatis jika koneksi turun, tetapi mengingatkan Anda dalam huruf penelepon adalah microservice lain. Biarkan blok penangkap menangani tangkapan.Jawaban:
Ini hampir selalu gagal untuk setidaknya satu dari penelepon Anda, yang perilaku ini sangat menjengkelkan. Jangan menganggap Anda tahu yang terbaik. Beri tahu pengguna Anda apa yang terjadi, bukan menurut Anda apa yang harus mereka lakukan. Dalam banyak kasus sudah jelas apa tindakan yang seharusnya (dan, jika tidak, buat saran di manual user Anda).
Misalnya, bahkan pengecualian yang diberikan dalam pertanyaan Anda mendemonstrasikan asumsi Anda yang rusak: a
ServiceTemporaryUnavailable
sama dengan "coba lagi nanti", danRateLimitExceeded
sama dengan "mau santai, mungkin sesuaikan parameter timer Anda, dan coba lagi dalam beberapa menit". Tetapi pengguna mungkin juga ingin mengaktifkan semacam alarmServiceTemporaryUnavailable
(yang menunjukkan masalah server), dan bukan untukRateLimitExceeded
(yang tidak).Beri mereka pilihan .
sumber
RetryableError
.Diberi ide ini:
... satu hal yang saya sarankan adalah Anda mungkin mencampur-adukkan kekhawatiran melaporkan kesalahan dengan tindakan untuk meresponsnya dengan cara yang dapat menurunkan sifat umum kode Anda atau memerlukan banyak "titik terjemahan" untuk pengecualian .
Misalnya, jika saya memodelkan transaksi yang melibatkan pemuatan file, itu mungkin gagal karena sejumlah alasan. Mungkin memuat file melibatkan memuat plugin yang tidak ada di mesin pengguna. Mungkin file tersebut hanya rusak dan kami mengalami kesalahan dalam menguraikannya.
Apa pun yang terjadi, katakan saja tindakannya adalah melaporkan apa yang terjadi pada pengguna dan memberi tahu dia tentang apa yang ingin dia lakukan ("coba lagi, muat file lain, batalkan").
Pelempar vs Penangkap
Tindakan tersebut berlaku terlepas dari kesalahan apa yang kami temui dalam kasus ini. Itu tidak tertanam ke dalam gagasan umum tentang kesalahan parsing, itu tidak tertanam ke dalam gagasan umum gagal memuat sebuah plugin. Ini tertanam ke dalam gagasan menemukan kesalahan seperti itu selama konteks yang tepat memuat file (kombinasi memuat file dan gagal). Jadi biasanya saya melihatnya, secara kasar, sebagai
catcher's
tanggung jawab untuk menentukan arah tindakan dalam menanggapi pengecualian yang dilemparkan (mis: mendorong pengguna dengan opsi), bukanthrower's
.Dengan kata lain, situs-situs yang
throw
pengecualian biasanya tidak memiliki informasi kontekstual semacam ini, terutama jika fungsi-fungsi yang melempar secara umum berlaku. Bahkan dalam konteks yang sama sekali merosot ketika mereka memiliki informasi ini, Anda akhirnya menyudutkan diri Anda dalam hal perilaku pemulihan dengan memasukkannya ke dalamthrow
situs. Situs-situs yangcatch
umumnya memiliki jumlah informasi terbanyak tersedia untuk menentukan tindakan, dan memberi Anda satu tempat sentral untuk memodifikasi jika tindakan tersebut harus pernah berubah untuk transaksi yang diberikan.Ketika Anda mulai mencoba untuk melempar pengecualian tidak lagi melaporkan apa yang salah tetapi mencoba menentukan apa yang harus dilakukan, itu mungkin menurunkan sifat umum dan fleksibilitas kode Anda. Kesalahan penguraian seharusnya tidak selalu mengarah ke jenis prompt ini, ini bervariasi berdasarkan konteks di mana pengecualian seperti itu dilemparkan (transaksi di mana ia dilempar).
The Blind Thrower
Secara umum, banyak desain penanganan pengecualian sering berputar di sekitar gagasan pelempar buta. Tidak tahu bagaimana pengecualian akan ditangkap, atau di mana. Hal yang sama berlaku untuk bentuk pemulihan kesalahan yang lebih lama menggunakan propagasi kesalahan manual. Situs yang mengalami kesalahan tidak termasuk tindakan pengguna, mereka hanya menyematkan informasi minimal untuk melaporkan jenis kesalahan apa yang ditemui.
Tanggung jawab terbalik dan menggeneralisasi Penangkap
Memikirkan hal ini lebih hati-hati, saya mencoba membayangkan jenis basis kode di mana ini bisa menjadi godaan. Imajinasi saya (mungkin salah) adalah bahwa tim Anda masih memainkan peran "konsumen" di sini dan menerapkan sebagian besar kode panggilan juga. Mungkin Anda memiliki banyak transaksi yang berbeda (banyak
try
blok) yang semuanya dapat mengalami serangkaian kesalahan yang sama, dan semua harus, dari perspektif desain, mengarah ke arah tindakan pemulihan yang seragam.Mempertimbangkan saran bijak dari
Lightness Races in Orbit's
jawaban yang baik (yang saya pikir benar-benar berasal dari pola pikir berorientasi perpustakaan canggih), Anda mungkin masih tergoda untuk melempar pengecualian "apa yang harus dilakukan", hanya lebih dekat ke situs pemulihan transaksi.Dimungkinkan untuk menemukan perantara, tempat penanganan transaksi umum dari ini di sini yang sebenarnya memusatkan perhatian "apa yang harus dilakukan" tetapi masih dalam konteks penangkapan.
Ini hanya akan berlaku jika Anda dapat merancang semacam fungsi umum yang digunakan oleh semua transaksi luar ini (mis: fungsi yang memasukkan fungsi lain untuk dipanggil atau kelas dasar transaksi abstrak dengan pemodelan perilaku yang dapat ditimpa memodelkan situs transaksi perantara ini yang melakukan penangkapan canggih ).
Namun seseorang dapat bertanggung jawab untuk memusatkan tindakan pengguna dalam menanggapi berbagai kemungkinan kesalahan, dan masih dalam konteks menangkap daripada melempar. Contoh sederhana (pseudocode Python-ish, dan saya bukan pengembang Python yang berpengalaman sedikit pun sehingga mungkin ada cara yang lebih idiomatis tentang hal ini):
[Semoga dengan nama yang lebih baik dari
general_catcher
]. Dalam contoh ini, Anda bisa meneruskan fungsi yang berisi tugas apa yang harus dilakukan tetapi masih mendapat manfaat dari perilaku tangkapan umum / terpadu untuk semua jenis pengecualian yang Anda minati, dan terus memperluas atau memodifikasi bagian "apa yang harus dilakukan" Anda suka dari lokasi pusat ini dan masih dalamcatch
konteks di mana ini biasanya didorong. Yang terbaik dari semuanya, kita dapat menjaga situs-situs pelemparan agar tidak menganggap diri mereka sendiri sebagai "apa yang harus dilakukan" (mempertahankan gagasan "pelempar buta").Jika Anda tidak menemukan satu pun dari saran ini di sini yang membantu dan ada godaan yang kuat untuk melempar pengecualian "apa yang harus dilakukan", terutama perlu diketahui bahwa ini sangat anti-idiomatik, dan berpotensi menghambat pola pikir umum.
sumber
Saya pikir sebagian besar waktu akan lebih baik untuk melontarkan argumen ke fungsi yang mengatakannya bagaimana menangani situasi tersebut.
Misalnya, pertimbangkan fungsi:
Saya dapat melewati RetryPolicy.noRetries () atau RetryPolicy.retries (3) atau apa pun. Dalam hal kegagalan gagal coba-coba, ia akan berkonsultasi dengan kebijakan untuk memutuskan apakah harus mencoba lagi atau tidak.
sumber
new RetryPolicy().onRateLimitExceeded(STOP).onServiceTemporaryUnavailable(RETRY, 3)
atau sesuatu, karenaRateLimitExceeded
mungkin perlu ditangani secara berbedaServiceTemporaryUnavailable
. Setelah menulis itu, pikiran saya adalah: lebih baik melemparkan pengecualian, karena memberikan kontrol yang lebih fleksibel.