Menggunakan validasi try-akhirnya (tanpa tangkapan) vs enum-state

9

Saya telah membaca saran tentang pertanyaan ini tentang bagaimana pengecualian harus ditangani sedekat mungkin dengan tempat diajukannya.

Dilema saya pada praktik terbaik adalah apakah seseorang harus menggunakan try / catch / akhirnya untuk mengembalikan enum (atau int yang mewakili nilai, 0 untuk kesalahan, 1 untuk ok, 2 untuk peringatan dll, tergantung pada kasus) sehingga jawaban selalu dalam urutan, atau haruskah seseorang membiarkan pengecualian melewati sehingga bagian yang memanggil akan menghadapinya?

Dari apa yang bisa saya kumpulkan, ini mungkin berbeda tergantung pada kasusnya, sehingga saran aslinya tampak aneh.

Misalnya, pada layanan web, Anda selalu ingin mengembalikan keadaan, jadi pengecualian apa pun harus ditangani saat itu juga, tetapi katakanlah di dalam fungsi yang memposting / mendapatkan beberapa data melalui http, Anda ingin pengecualian (misalnya dalam kasus 404) hanya melewati yang memecatnya. Jika tidak, Anda harus membuat beberapa cara untuk menginformasikan bagian panggilan dari kualitas hasil (kesalahan: 404), serta hasilnya sendiri.

Meskipun dimungkinkan untuk mencoba menangkap 404 pengecualian di dalam fungsi helper yang mendapat / memposting data, bukan? Apakah hanya saya yang menggunakan smallint untuk menunjukkan status dalam program (dan mendokumentasikannya dengan tepat tentu saja), dan kemudian menggunakan info ini untuk keperluan validasi kewarasan (semuanya ok / penanganan kesalahan) di luar?

Pembaruan: Saya mengharapkan pengecualian fatal / non-fatal untuk klasifikasi utama, tetapi saya tidak ingin memasukkan ini agar tidak mengurangi jawaban. Izinkan saya mengklarifikasi apa pertanyaannya: Menangani pengecualian yang dilemparkan, bukan melempar pengecualian. Apa efek yang diinginkan: Mendeteksi kesalahan, dan mencoba memulihkannya. Jika pemulihan tidak memungkinkan, berikan umpan balik yang paling berarti.

Sekali lagi, dengan contoh http get / post, pertanyaannya adalah, haruskah Anda memberikan objek baru yang menjelaskan apa yang terjadi pada penelepon asli? Jika pembantu ini ada di perpustakaan yang Anda gunakan, apakah Anda mengharapkannya memberi Anda kode status untuk operasi, atau akankah Anda memasukkannya dalam blok coba-coba? Jika Anda mendesainnya , apakah Anda akan memberikan kode status atau melemparkan pengecualian dan membiarkan tingkat atas menerjemahkannya ke kode status / pesan?

Sinopsis: Bagaimana Anda memilih jika sepotong kode alih-alih menghasilkan pengecualian, mengembalikan kode status bersama dengan hasil apa pun yang dapat dihasilkannya?

Mihalis Bagos
sumber
1
Itu tidak berurusan dengan kesalahan yang mengubah bentuk penanganan kesalahan yang digunakan. Dalam kasus 404 Anda akan membiarkannya lewat karena Anda tidak dapat menanganinya.
stonemetal
"int yang mewakili nilai, 0 untuk kesalahan, 1 untuk ok, 2 untuk peringatan dll" Tolong katakan ini adalah contoh off-the-cuff! Menggunakan 0 berarti OK jelas merupakan standar ...
anaximander

Jawaban:

15

Pengecualian harus digunakan untuk kondisi luar biasa. Melontarkan pengecualian pada dasarnya membuat pernyataan, "Saya tidak bisa menangani kondisi ini di sini; bisakah seseorang yang lebih tinggi di tumpukan panggilan menangkap ini untuk saya dan menanganinya?"

Mengembalikan nilai bisa lebih baik, jika jelas bahwa pemanggil akan mengambil nilai itu dan melakukan sesuatu yang berarti dengannya. Ini terutama benar jika melempar pengecualian memiliki implikasi kinerja, yaitu dapat terjadi dalam loop yang ketat. Melempar pengecualian membutuhkan waktu lebih lama daripada mengembalikan nilai (dengan setidaknya dua urutan besarnya).

Pengecualian tidak boleh digunakan untuk mengimplementasikan logika program. Dengan kata lain, jangan melemparkan pengecualian untuk menyelesaikan sesuatu; melemparkan pengecualian untuk menyatakan bahwa itu tidak bisa dilakukan.

Robert Harvey
sumber
Terima kasih atas jawabannya, ini yang paling informatif tetapi fokus saya adalah pada penanganan pengecualian, dan bukan pengecualian melempar. Haruskah Anda menangkap pengecualian 404 segera setelah Anda menerimanya atau haruskah Anda membiarkannya lebih tinggi dari tumpukan?
Mihalis Bagos
Saya melihat hasil edit Anda, tetapi itu tidak mengubah jawaban saya. Konversi pengecualian menjadi kode kesalahan jika itu bermakna bagi pemanggil. Jika tidak, tangani pengecualiannya; biarkan naik tumpukan; atau menangkapnya, melakukan sesuatu dengan itu (seperti menulisnya ke log, atau sesuatu yang lain), dan rethrow.
Robert Harvey
1
+1: untuk penjelasan yang masuk akal dan seimbang. Pengecualian harus digunakan untuk menangani kasus luar biasa. Jika tidak, fungsi atau metode harus mengembalikan nilai yang berarti (enum atau jenis opsi) dan pemanggil harus menanganinya dengan benar. Mungkin orang bisa menyebutkan alternatif ketiga yang populer dalam pemrograman fungsional, yaitu kelanjutan.
Giorgio
4

Beberapa saran bagus yang pernah saya baca adalah, lempar pengecualian ketika Anda tidak dapat maju mengingat keadaan data yang Anda hadapi, namun jika Anda memiliki metode yang dapat membuang pengecualian, sediakan juga metode yang memungkinkan untuk menegaskan apakah data tersebut benar-benar valid sebelum metode dipanggil.

Sebagai contoh, System.IO.File.OpenRead () akan melempar FileNotFoundException jika file yang diberikan tidak ada, namun ia juga menyediakan metode .Exists () yang mengembalikan nilai boolean yang menunjukkan apakah file tersebut ada yang harus Anda panggil sebelumnya memanggil OpenRead () untuk menghindari pengecualian yang tidak terduga.

Untuk menjawab bagian "kapan saya harus berurusan dengan pengecualian", saya akan mengatakan di mana pun Anda benar-benar dapat melakukan sesuatu tentang hal itu. Jika metode Anda tidak dapat menangani pengecualian yang dilemparkan oleh metode yang dipanggil, jangan tangkap. Biarkan itu meningkatkan rantai panggilan ke sesuatu yang bisa menghadapinya. Dalam beberapa kasus, ini mungkin hanya seorang logger yang mendengarkan Application.UnhandledException.

Trevor Pilley
sumber
Banyak orang, terutama programmer python , lebih memilih EAFP yaitu "Lebih mudah untuk meminta pengampunan daripada meminta izin"
Mark Booth
1
+1 untuk komentar tentang menghindari pengecualian seperti dengan .Exists ().
codingoutloud
+1 Ini masih merupakan saran yang bagus. Dalam kode yang saya tulis / kelola, suatu Pengecualian adalah "Luar Biasa", 9/10 kali suatu Pengecualian dimaksudkan untuk dilihat oleh seorang pengembang, ia mengatakan hei, Anda harus pemrograman yang dapat dipertahankan! Lain waktu 1, itu adalah sesuatu yang kita tidak bisa berurusan dengan, dan kita mencatatnya, dan keluar sebaik mungkin. Konsistensi penting, misalnya, dengan konvensi, kami biasanya memiliki tanggapan salah yang benar, dan pesan internal untuk ongkos / pemrosesan standar. Api pihak ke-3 yang tampaknya memberikan pengecualian untuk semuanya dapat ditangani melalui panggilan, dan dikembalikan menggunakan proses yang disepakati standar.
Gavin Howden
3

gunakan try / catch / akhirnya untuk mengembalikan enum (atau int yang mewakili nilai, 0 untuk kesalahan, 1 untuk ok, 2 untuk peringatan dll, tergantung pada kasus) sehingga jawaban selalu dalam urutan,

Itu desain yang mengerikan. Jangan "menutupi" pengecualian dengan menerjemahkan ke kode numerik. Biarkan itu sebagai pengecualian yang tepat dan tidak ambigu.

atau haruskah seseorang membiarkan pengecualian melewati sehingga bagian yang memanggil akan menghadapinya?

Itulah pengecualian untuk.

ditangani sedekat mungkin dengan tempat dibesarkan

Bukan kebenaran universal sama sekali. Itu ide yang bagus beberapa kali. Lain kali itu tidak membantu.

S.Lott
sumber
Itu bukan desain yang mengerikan. Microsoft mengimplementasikannya di banyak tempat, yaitu pada penyedia Keanggotaan asp.NET default. Selain itu saya tidak bisa melihat bagaimana jawaban ini berkontribusi apa pun untuk percakapan
Mihalis Bagos
@ MihalisBagos: Yang bisa saya lakukan adalah menyarankan bahwa pendekatan Microsoft tidak dianut oleh setiap bahasa pemrograman. Dalam bahasa tanpa pengecualian, mengembalikan nilai sangat penting. C adalah contoh yang paling menonjol. Dalam bahasa dengan pengecualian, mengembalikan "nilai kode" untuk menunjukkan kesalahan adalah desain yang buruk. Ini mengarah ke (kadang-kadang) ifpernyataan rumit alih-alih (terkadang) penanganan pengecualian yang lebih sederhana. Jawabannya ("Bagaimana Anda memilih ...") sederhana. Jangan . Saya pikir mengatakan ini menambah percakapan. Namun, Anda bebas untuk mengabaikan jawaban ini.
S.Lott
Saya tidak mengatakan pendapat Anda tidak masuk hitungan tetapi saya mengatakan pendapat Anda tidak berkembang. Dengan komentar itu, saya berpendapat bahwa di mana kita bisa menggunakan pengecualian, kita harus, hanya karena kita bisa? Saya tidak melihat kode status sebagai masking, daripada kategorisasi / organisasi dari kasus aliran kode
Mihalis Bagos
1
Pengecualian sudah merupakan kategorisasi / organisasi. Saya tidak melihat nilai dalam mengganti pengecualian yang tidak ambigu dengan nilai kembali yang dapat dengan mudah dikacaukan dengan nilai pengembalian "normal" atau "tidak luar biasa". Nilai pengembalian harus selalu non-luar biasa. Pendapat saya tidak terlalu berkembang: sangat sederhana. Jangan mengubah pengecualian menjadi kode numerik. Itu sudah objek data yang sangat baik yang melayani semua kasus penggunaan yang sangat baik yang bisa kita bayangkan.
S.Lott
3

Saya setuju dengan S.Lott . Menangkap pengecualian sedekat mungkin dengan sumbernya mungkin merupakan ide bagus atau ide buruk tergantung pada situasinya. Kunci untuk menangani pengecualian adalah dengan menangkapnya saat Anda dapat melakukan sesuatu. Menangkap mereka dan mengembalikan nilai numerik ke fungsi panggilan pada umumnya adalah desain yang buruk. Anda hanya ingin membiarkannya melayang sampai Anda dapat pulih.

Saya selalu menganggap penanganan pengecualian sebagai langkah menjauh dari logika aplikasi saya. Saya bertanya pada diri sendiri, Jika pengecualian ini dilemparkan seberapa jauh cadangan stack panggilan yang harus saya jelajahi sebelum aplikasi saya dalam keadaan dapat dipulihkan? Dalam banyak kasus, jika tidak ada yang bisa saya lakukan dalam aplikasi untuk memulihkan, itu mungkin berarti saya tidak menangkapnya sampai tingkat atas dan hanya mencatat pengecualian, gagal pekerjaan dan mencoba mematikan dengan bersih.

Sebenarnya tidak ada aturan yang keras dan cepat untuk kapan dan bagaimana mengatur penanganan pengecualian selain membiarkannya sendiri sampai Anda tahu apa yang harus dilakukan dengan mereka.

DwayneAllen
sumber
1

Pengecualian adalah hal yang indah. Mereka memungkinkan Anda untuk menghasilkan deskripsi yang jelas tentang masalah run time tanpa menggunakan ambiguitas yang tidak perlu. Pengecualian dapat diketik, diketik-ketik, dan dapat ditangani berdasarkan jenis. Mereka dapat diedarkan untuk penanganan di tempat lain, dan jika mereka tidak dapat ditangani mereka dapat diangkat kembali untuk ditangani pada lapisan yang lebih tinggi dalam aplikasi Anda. Mereka juga akan secara otomatis kembali dari metode Anda tanpa harus menggunakan banyak logika gila untuk berurusan dengan kode kesalahan yang dikaburkan.

Anda harus melempar pengecualian segera setelah menemukan data yang tidak valid dalam kode Anda. Anda harus membungkus panggilan ke metode lain dalam try..catch .. akhirnya untuk menangani pengecualian yang mungkin dilemparkan, dan jika Anda tidak tahu bagaimana menanggapi pengecualian yang diberikan, Anda melemparkannya lagi untuk menunjukkan ke lapisan yang lebih tinggi yang ada sesuatu yang salah yang harus ditangani di tempat lain.

Mengelola kode kesalahan bisa sangat sulit. Anda biasanya berakhir dengan banyak duplikasi yang tidak perlu dalam kode Anda, dan / atau banyak logika berantakan untuk menangani status kesalahan. Menjadi pengguna dan menemukan kode kesalahan bahkan lebih buruk, karena kode itu sendiri tidak akan berarti, dan tidak akan memberikan konteks kepada pengguna untuk kesalahan tersebut. Pengecualian di sisi lain dapat memberi tahu pengguna sesuatu yang bermanfaat, seperti "Anda lupa memasukkan nilai", atau "Anda memasukkan nilai yang tidak valid, berikut ini rentang yang valid yang dapat Anda gunakan ...", atau "Saya tidak tahu apa yang terjadi, hubungi dukungan teknis dan beri tahu mereka bahwa saya baru saja mogok, dan beri mereka jejak tumpukan berikut ... ".

Jadi pertanyaan saya kepada OP adalah mengapa Anda TIDAK ingin menggunakan pengecualian untuk mengembalikan kode kesalahan?

S.Robins
sumber
0

Semua jawaban bagus. Saya juga ingin menambahkan bahwa mengembalikan kode kesalahan daripada melemparkan pengecualian dapat membuat kode pemanggil lebih rumit. Metode say A memanggil metode B memanggil metode C dan C menemui kesalahan. Jika C mengembalikan kode kesalahan, sekarang B perlu memiliki logika untuk menentukan apakah itu dapat menangani kode kesalahan itu. Jika tidak, maka perlu mengembalikannya ke A. Jika A tidak dapat menangani kesalahan, lalu apa yang Anda lakukan? Melempar pengecualian? Dalam contoh ini, kode jauh lebih bersih jika C hanya melempar pengecualian, B tidak menangkap pengecualian sehingga secara otomatis dibatalkan tanpa kode tambahan yang diperlukan untuk melakukannya dan A dapat menangkap jenis pengecualian tertentu sambil membiarkan orang lain melanjutkan panggilan tumpukan.

Ini mengingatkan aturan yang baik pada kode dengan:

Baris kode seperti peluru emas. Anda ingin menggunakan sesedikit mungkin untuk menyelesaikan pekerjaan.

Raymond Saltrelli
sumber
0

Saya menggunakan kombinasi kedua solusi: untuk setiap fungsi validasi, saya melewati catatan yang saya isi dengan status validasi (kode kesalahan). Di akhir fungsi, jika ada kesalahan validasi, saya melempar pengecualian, dengan cara ini saya tidak melempar pengecualian untuk setiap bidang, tetapi hanya sekali.

Saya juga mengambil keuntungan bahwa melempar pengecualian akan menghentikan eksekusi karena saya tidak ingin eksekusi berlanjut ketika data tidak valid.

Sebagai contoh

procedure Validate(var R:TValidationRecord);
begin
  if Field1 is not valid then
  begin
    R.Field1ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end; 
  if Field2 is not valid then
  begin
    R.Field2ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;
  if Field3 is not valid then
  begin
    R.Field3ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;

  if ErrorFlag then
    ThrowException
end;

Jika hanya mengandalkan boolean, pengembang yang menggunakan fungsi saya harus mempertimbangkan ini dalam penulisan akun:

if not Validate() then
  DoNotContinue();

tapi dia mungkin lupa dan hanya menelepon Validate() (saya tahu dia tidak boleh, tapi mungkin dia mungkin).

Jadi, dalam kode di atas saya mendapatkan dua keuntungan:

  1. Hanya satu pengecualian dalam fungsi validasi.
  2. Pengecualian, bahkan tanpa tertangkap, akan menghentikan eksekusi, dan muncul pada waktu ujian
Bahaa
sumber
0

Tidak ada satu jawaban di sini - jenis seperti tidak ada satu jenis HttpException.

Sangat masuk akal bahwa pustaka HTTP yang mendasarinya membuat pengecualian ketika mereka mendapatkan respons 4xx atau 5xx; Terakhir kali saya melihat spesifikasi HTTP itu adalah kesalahan.

Adapun melemparkan pengecualian itu - atau membungkusnya dan memikirkan kembali - saya pikir itu benar-benar masalah use case. Misalnya, jika Anda menulis pembungkus untuk mengambil beberapa data dari API dan memaparkannya ke aplikasi, Anda dapat memutuskan bahwa secara semantik permintaan sumber daya yang tidak ada yang mengembalikan HTTP 404 akan lebih masuk akal untuk menangkapnya dan mengembalikan nol. Di sisi lain, kesalahan 406 (tidak dapat diterima) mungkin layak untuk menimbulkan kesalahan karena itu berarti ada sesuatu yang berubah dan aplikasi harusnya mogok dan terbakar dan berteriak minta tolong.

Wyatt Barnett
sumber