Pengecualian - "apa yang terjadi" vs "apa yang harus dilakukan"

19

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/0operasi).

Bagaimana jika ada kemungkinan untuk menentukan perilaku yang diharapkan dari konsumen?

Misalnya, bayangkan kami memiliki fetchsumber daya, yang melakukan permintaan HTTP dan mengembalikan data yang diambil. Dan alih-alih kesalahan seperti ServiceTemporaryUnavailableatau RateLimitExceededkita hanya akan mengajukan RetryableErrorsaran 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"?

Bodnarchuk Romawi
sumber
3
Bukankah HTTP sudah melakukan ini? 503 adalah kegagalan sementara untuk menjawab, jadi pemohon harus mencoba lagi, 404 adalah absen mendasar, jadi tidak masuk akal untuk mencoba lagi, 301 berarti "dipindahkan secara permanen", jadi Anda harus mencoba lagi, tetapi dengan alamat yang berbeda, dll.
Kilian Foth
7
Dalam banyak kasus, jika kita benar-benar tahu "apa yang harus dilakukan", kita bisa membuat komputer melakukannya secara otomatis dan pengguna bahkan tidak perlu tahu ada yang salah. Saya berasumsi setiap kali browser saya menerima 301 itu hanya pergi ke alamat baru tanpa meminta saya.
Ixrec
@ Isrec - punya ide yang sama juga. namun, konsumen mungkin tidak ingin menunggu permintaan lain dan mengabaikan barang atau gagal total.
Roman Bodnarchuk
1
@RomanBodnarchuk: Saya tidak setuju. Ini seperti mengatakan seseorang seharusnya tidak perlu tahu bahasa Mandarin untuk bisa berbicara bahasa Mandarin. HTTP adalah protokol, dan baik klien maupun server diharapkan mengetahuinya dan mengikutinya. Begitulah cara kerja protokol. Jika hanya satu sisi yang tahu dan mematuhinya, maka Anda tidak bisa berkomunikasi.
Chris Pratt
1
Ini secara jujur ​​kedengarannya seperti Anda mencoba untuk mengganti pengecualian Anda dengan blok penangkap. Itulah sebabnya kami pindah ke pengecualian - tidak lebih 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.
Sebb

Jawaban:

47

Tapi bayangkan itu adalah komponen tertentu yang kita tahu tindakan terbaik untuk penelepon.

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 ServiceTemporaryUnavailablesama dengan "coba lagi nanti", dan RateLimitExceededsama dengan "mau santai, mungkin sesuaikan parameter timer Anda, dan coba lagi dalam beberapa menit". Tetapi pengguna mungkin juga ingin mengaktifkan semacam alarm ServiceTemporaryUnavailable(yang menunjukkan masalah server), dan bukan untuk RateLimitExceeded(yang tidak).

Beri mereka pilihan .

Lightness Races with Monica
sumber
4
Saya setuju. Server seharusnya hanya menyampaikan informasi dengan benar. Dokumentasi di sisi lain harus secara jelas menguraikan tindakan yang tepat dalam kasus tersebut.
Neil
1
Satu kekurangan kecil dalam hal ini adalah bagi sebagian peretas, pengecualian tertentu dapat memberi tahu mereka cukup banyak tentang apa yang dilakukan kode Anda dan mereka dapat menggunakannya untuk mencari cara untuk mengeksploitasinya.
Pharap
3
@Pharap Jika peretas Anda memiliki akses ke pengecualian itu sendiri alih-alih pesan kesalahan, Anda sudah hilang.
corsiKa
2
Saya suka jawaban ini, tetapi tidak ada yang saya anggap sebagai persyaratan pengecualian ... jika Anda tahu cara memulihkannya, itu tidak akan menjadi pengecualian! Pengecualian seharusnya hanya ketika ketika Anda tidak dapat melakukan sesuatu tentang hal itu: input tidak valid, keadaan tidak valid, keamanan tidak valid - Anda tidak dapat memperbaikinya secara terprogram.
corsiKa
1
Sepakat. Jika Anda bersikeras untuk menunjukkan apakah coba lagi mungkin, Anda selalu bisa membuat pengecualian konkret yang relevan berasal dari RetryableError.
sapi
18

Peringatan! C ++ programmer datang ke sini dengan ide-ide yang mungkin berbeda tentang bagaimana penanganan pengecualian harus dilakukan dengan mencoba menjawab pertanyaan yang tentunya tentang bahasa lain!

Diberi ide ini:

Sebagai contoh, bayangkan kita memiliki sumber daya, yang melakukan permintaan HTTP dan mengembalikan data yang diambil. Dan alih-alih kesalahan seperti ServiceTentarUnavailable atau RateLimitExceeded kami hanya akan meningkatkan RetryableError menyarankan konsumen bahwa itu hanya harus mencoba kembali permintaan dan tidak peduli dengan kegagalan spesifik.

... 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'stanggung jawab untuk menentukan arah tindakan dalam menanggapi pengecualian yang dilemparkan (mis: mendorong pengguna dengan opsi), bukan thrower's.

Dengan kata lain, situs-situs yang throwpengecualian 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 dalam throwsitus. Situs-situs yang catchumumnya 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 tryblok) 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'sjawaban 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.

masukkan deskripsi gambar di sini

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):

def general_catcher(task):
    try:
       task()
    except SomeError1:
       # do some uniformly-designed recovery stuff here
    except SomeError2:
       # do some other uniformly-designed recovery stuff here
    ...

[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 dalam catchkonteks 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.

ChrisF
sumber
2
+1. Saya tidak pernah mendengar ide "Blind Thrower" seperti itu sebelumnya, tapi itu cocok dengan cara saya berpikir tentang penanganan pengecualian: nyatakan kesalahan yang terjadi, jangan pikirkan bagaimana itu harus ditangani. Ketika Anda bertanggung jawab untuk tumpukan penuh, sulit (tapi penting!) Untuk memisahkan tanggung jawab dengan bersih, dan callee bertanggung jawab untuk memberitahukan tentang masalah, dan penelepon untuk menangani masalah. Callee hanya tahu apa yang diminta untuk dilakukan, bukan mengapa. Penanganan kesalahan harus dilakukan dalam konteks 'mengapa', karenanya: di pemanggil.
Sjoerd Job Postmus
1
(Juga: Saya kira balasan Anda tidak khusus untuk C ++, tetapi berlaku untuk penanganan pengecualian secara umum)
Sjoerd Job Postmus
1
@SjoerdJobPostmus Yay terima kasih! "Blind Thrower" hanyalah analogi konyol yang saya buat di sini - Saya tidak terlalu pintar atau cepat dalam mencerna konsep teknis yang rumit, jadi saya sering ingin menemukan sedikit gambaran dan analogi untuk mencoba menjelaskan dan meningkatkan pemahaman saya sendiri. hal. Mungkin suatu hari nanti saya bisa mencoba cara menulis buku pemrograman kecil yang dipenuhi banyak gambar kartun. :-D
1
Heh, itu gambar kecil yang bagus. Karakter kartun kecil mengenakan penutup mata dan mengeluarkan pengecualian berbentuk bisbol, tidak yakin siapa yang akan menangkap mereka (atau bahkan apakah mereka akan ditangkap sama sekali), tetapi memenuhi tugas mereka sebagai pelempar buta.
Blacklight Shining
1
@DrunkCoder: Tolong jangan merusak posting Anda. Kami sudah memiliki cukup banyak borken di Internet. Jika Anda memiliki alasan yang bagus untuk dihapus, tandai pos Anda untuk mendapatkan perhatian moderator dan buat alasan Anda.
Robert Harvey
2

Saya pikir sebagian besar waktu akan lebih baik untuk melontarkan argumen ke fungsi yang mengatakannya bagaimana menangani situasi tersebut.

Misalnya, pertimbangkan fungsi:

Response fetchUrl(URL url, RetryPolicy retryPolicy);

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.

Winston Ewert
sumber
Itu tidak benar-benar ada hubungannya dengan melempar pengecualian kembali ke situs panggilan. Anda sedang berbicara tentang sesuatu yang sedikit berbeda, yang baik-baik saja tetapi tidak benar-benar bagian dari pertanyaan ..
Lightness Races with Monica
@LightnessRacesinOrbit, sebaliknya. Saya menyajikannya sebagai alternatif untuk gagasan membuang pengecualian kembali ke situs panggilan. Dalam contoh OP, fetchUrl akan melempar RetryableException, dan saya katakan Anda harus memberi tahu fetchUrl kapan harus coba lagi.
Winston Ewert
2
@ WinstonEwert: Meskipun saya setuju dengan LightnessRacesinOrbit, saya juga melihat maksud Anda, tetapi menganggapnya sebagai cara berbeda untuk mewakili kontrol yang sama. Tapi, pertimbangkan bahwa Anda mungkin ingin lulus new RetryPolicy().onRateLimitExceeded(STOP).onServiceTemporaryUnavailable(RETRY, 3)atau sesuatu, karena RateLimitExceededmungkin perlu ditangani secara berbeda ServiceTemporaryUnavailable. Setelah menulis itu, pikiran saya adalah: lebih baik melemparkan pengecualian, karena memberikan kontrol yang lebih fleksibel.
Sjoerd Job Postmus
@ SojoerdJobPostmus, saya pikir itu tergantung. Mungkin karena pembatasan tingkat dan coba lagi logika dibuat sejak di perpustakaan Anda, dalam hal ini saya pikir pendekatan saya masuk akal. Jika lebih masuk akal untuk menyerahkannya kepada penelepon Anda, tentu saja lemparlah.
Winston Ewert