Saya telah diberitahu bahwa Pengecualian hanya boleh digunakan dalam kasus luar biasa. Bagaimana saya tahu jika kasus saya luar biasa?

99

Kasus spesifik saya di sini adalah bahwa pengguna dapat mengirimkan string ke dalam aplikasi, aplikasi mem-parsingnya dan menetapkannya ke objek terstruktur. Terkadang pengguna dapat mengetik sesuatu yang tidak valid. Misalnya, masukan mereka mungkin menggambarkan seseorang tetapi mereka mungkin mengatakan usia mereka adalah "apel". Perilaku yang benar dalam kasus itu adalah gulung balik transaksi dan untuk memberi tahu pengguna kesalahan terjadi dan mereka harus mencoba lagi. Mungkin ada persyaratan untuk melaporkan setiap kesalahan yang dapat kita temukan di input, bukan hanya yang pertama.

Dalam hal ini, saya berpendapat kita harus melempar pengecualian. Dia tidak setuju, mengatakan, "Pengecualian harus luar biasa: Diharapkan pengguna dapat memasukkan data yang tidak valid, jadi ini bukan kasus yang luar biasa" Saya tidak benar-benar tahu bagaimana memperdebatkan hal itu, karena menurut definisi kata, dia sepertinya benar.

Namun, menurut pemahaman saya inilah mengapa Pengecualian diciptakan sejak awal. Dulu Anda harus memeriksa hasilnya untuk melihat apakah ada kesalahan. Jika Anda gagal memeriksa, hal-hal buruk dapat terjadi tanpa Anda sadari.

Tanpa pengecualian setiap tingkat tumpukan perlu memeriksa hasil dari metode yang mereka panggil dan jika seorang programmer lupa untuk memeriksa di salah satu level ini, kode dapat secara tidak sengaja melanjutkan dan menyimpan data yang tidak valid (misalnya). Tampaknya lebih banyak kesalahan cenderung seperti itu.

Bagaimanapun, jangan ragu untuk memperbaiki apa pun yang saya katakan di sini. Pertanyaan utama saya adalah jika seseorang mengatakan Pengecualian harus luar biasa, bagaimana saya tahu jika kasus saya luar biasa?

Daniel Kaplan
sumber
3
mungkin duplikat? Kapan harus melempar pengecualian . Meskipun sudah ditutup di sana, tapi saya pikir cocok di sini. Ini masih sedikit filosofi, beberapa orang dan komunitas cenderung melihat pengecualian sebagai semacam kontrol aliran.
thorsten müller
8
Ketika pengguna bodoh, mereka memberikan input yang tidak valid. Ketika pengguna cerdas, mereka bermain dengan memberikan input yang tidak valid. Oleh karena itu, input pengguna yang tidak valid tidak terkecuali.
mouviciel
7
Juga, jangan bingung dengan pengecualian , yang merupakan mekanisme yang sangat spesifik di Java dan .NET, dengan kesalahan yang merupakan istilah yang jauh lebih umum. Ada lebih banyak penanganan kesalahan daripada melemparkan pengecualian. Diskusi ini menyentuh nuansa antara pengecualian dan kesalahan .
Eric King
4
"Luar Biasa"! = "Jarang Terjadi"
ConditionRacer
3
Saya menemukan pengecualian Vexing Eric Lippert menjadi saran yang layak.
Brian

Jawaban:

87

Pengecualian ditemukan untuk membantu mempermudah penanganan kesalahan dengan lebih sedikit kekacauan kode. Anda harus menggunakannya jika mereka membuat penanganan kesalahan lebih mudah dengan lebih sedikit kekacauan kode. Bisnis "pengecualian hanya untuk keadaan luar biasa" ini berasal dari saat penanganan pengecualian dianggap sebagai hit kinerja yang tidak dapat diterima. Itu tidak lagi terjadi pada sebagian besar kode, tetapi orang-orang masih menyemburkan aturan tanpa mengingat alasan di baliknya.

Khususnya di Jawa, yang mungkin merupakan bahasa yang paling mencintai pengecualian, Anda seharusnya tidak merasa bersalah menggunakan pengecualian ketika menyederhanakan kode Anda. Bahkan, Integerkelas Java sendiri tidak memiliki sarana untuk memeriksa apakah string adalah integer yang valid tanpa berpotensi melempar a NumberFormatException.

Selain itu, meskipun Anda tidak bisa hanya mengandalkan validasi UI, perlu diingat jika UI Anda dirancang dengan benar, seperti menggunakan pemintal untuk memasukkan nilai numerik pendek, maka nilai non-numerik yang membuatnya menjadi ujung belakang benar-benar akan menjadi kondisi luar biasa.

Karl Bielefeldt
sumber
10
Geser bagus, di sana. Sebenarnya, dalam aplikasi dunia nyata saya merancang, kinerja hit melakukan membuat perbedaan, dan saya harus mengubahnya ke tidak membuang pengecualian untuk operasi parsing tertentu.
Robert Harvey
17
Saya tidak mengatakan masih tidak ada kasus di mana kinerja menjadi alasan yang sah, tetapi kasus-kasus itu adalah pengecualian (pun intended) daripada aturan.
Karl Bielefeldt
11
@RobertHarvey Trik di Jawa adalah untuk membuang objek pengecualian yang sudah dibuat sebelumnya, bukan throw new .... Atau, lempar pengecualian khusus, tempat fillInStackTrace () ditimpa. Maka Anda seharusnya tidak melihat penurunan kinerja, apalagi hit .
Ingo
3
+1: Persis apa yang akan saya jawab. Gunakan ketika menyederhanakan kode. Pengecualian dapat memberikan kode yang lebih jelas di mana Anda tidak perlu repot memeriksa nilai kembali di setiap level dalam panggilan-tumpukan. (Seperti yang lainnya, jika digunakan dengan cara yang salah dapat membuat kode Anda berantakan.)
Leo
4
@ Brendan Misalkan beberapa pengecualian terjadi, dan kode penanganan kesalahan adalah 4 tingkat di bawah ini di tumpukan panggilan. Jika Anda menggunakan kode kesalahan, semua 4 fungsi di atas pawang harus memiliki jenis kode kesalahan sebagai nilai baliknya, dan Anda harus melakukan rantai if (foo() == ERROR) { return ERROR; } else { // continue }di setiap level. Jika Anda melempar pengecualian yang tidak dicentang, tidak ada bising dan berlebihan "jika kesalahan pengembalian kesalahan". Juga, jika Anda meneruskan fungsi sebagai argumen, menggunakan kode kesalahan dapat mengubah tanda tangan fungsi menjadi tipe yang tidak kompatibel, meskipun kesalahan mungkin tidak terjadi.
Doval
72

Kapan pengecualian harus dilemparkan? Ketika datang ke kode, saya pikir penjelasan berikut ini sangat membantu:

Pengecualian adalah ketika seorang anggota gagal menyelesaikan tugas yang seharusnya dia lakukan sebagaimana ditunjukkan oleh namanya . (Jeffry Richter, CLR via C #)

Mengapa ini membantu? Ini menunjukkan bahwa itu tergantung pada konteks ketika sesuatu harus ditangani sebagai pengecualian atau tidak. Pada tingkat panggilan metode, konteksnya diberikan oleh (a) nama, (b) tanda tangan metode dan (b) kode klien, yang menggunakan atau diharapkan untuk menggunakan metode tersebut.

Untuk menjawab pertanyaan Anda, Anda harus melihat pada kode, di mana input pengguna diproses. Mungkin terlihat seperti ini:

public void Save(PersonData personData) {  }

Apakah nama metode menyarankan agar beberapa validasi dilakukan? Tidak. Dalam hal ini, PersonData yang tidak valid harus memberikan pengecualian.

Misalkan kelas memiliki metode lain yang terlihat seperti ini:

public ValidationResult Validate(PersonData personData) {  }

Apakah nama metode menyarankan agar beberapa validasi dilakukan? Iya. Dalam hal ini, PersonData yang tidak valid tidak boleh melempar pengecualian.

Untuk menyatukan semuanya, kedua metode menyarankan bahwa kode klien harus terlihat seperti ini:

ValidationResult validationResult = personRegister.Validate(personData);
if (validationResult.IsValid())
{
    personRegister.Save(personData)
}
else
{
    // Throw an exception? To answer this look at the context!
    // That is: (a) Method name, (b) signature and
    // (c) where this method is (expected) to be used.
}

Ketika tidak jelas apakah suatu metode harus mengeluarkan pengecualian, maka mungkin itu karena nama metode atau tanda tangan yang dipilih dengan buruk. Mungkin desain kelasnya tidak jelas. Terkadang Anda perlu memodifikasi desain kode untuk mendapatkan jawaban yang jelas atas pertanyaan apakah pengecualian harus dibuang atau tidak.

Theo Lenndorff
sumber
Baru kemarin saya membuat yang structdisebut "ValidationResult" dan menyusun kode saya seperti yang Anda gambarkan.
paul
4
Tidak membantu menjawab pertanyaan Anda, tetapi saya hanya ingin menunjukkan bahwa Anda secara implisit atau sengaja mengikuti prinsip pemisahan kueri-permintaan ( en.wikipedia.org/wiki/Command-query_separation ). ;-)
Theo Lenndorff
Ide bagus! Satu kelemahan: Dalam contoh Anda, validasi sebenarnya dilakukan dua kali: Sekali selama Validate(mengembalikan False jika tidak valid) dan sekali selama Save(melempar pengecualian spesifik, yang terdokumentasi dengan baik jika tidak valid). Tentu saja, hasil validasi dapat di-cache di dalam objek, tetapi itu akan menambah kompleksitas tambahan, karena hasil validasi perlu dibatalkan pada perubahan.
Heinzi
@Heinzi saya setuju. Itu bisa di refactored sehingga Validate()disebut di dalam Save()metode, dan detail spesifik dari ValidationResultbisa digunakan untuk membangun pesan yang sesuai untuk pengecualian.
Phil
3
Ini lebih baik daripada jawaban yang diterima menurut saya. Lempar ketika panggilan tidak dapat melakukan hal yang seharusnya dilakukan.
Andy
31

Pengecualian harus luar biasa: Diharapkan pengguna dapat memasukkan data yang tidak valid, jadi ini bukan kasus yang luar biasa

Atas argumen itu:

  • Diharapkan file mungkin tidak ada, jadi itu bukan kasus yang luar biasa.
  • Diharapkan koneksi ke server dapat hilang, jadi itu bukan kasus yang luar biasa
  • Diharapkan file konfigurasi dapat kacau sehingga bukan kasus yang luar biasa
  • Diharapkan bahwa permintaan Anda terkadang jatuh, jadi itu bukan kasus yang luar biasa

Setiap pengecualian yang Anda tangkap, Anda harus berharap karena, Anda memutuskan untuk menangkapnya. Dan dengan logika ini, Anda seharusnya tidak pernah melemparkan pengecualian yang Anda rencanakan untuk ditangkap.

Karenanya saya pikir "pengecualian harus luar biasa" adalah aturan praktis yang mengerikan.

Apa yang harus Anda lakukan tergantung pada bahasanya. Bahasa yang berbeda memiliki konvensi yang berbeda tentang kapan pengecualian harus dilemparkan. Python, misalnya, melempar pengecualian untuk semua dan ketika di Python, saya mengikutinya. C ++, di sisi lain, melempar pengecualian yang relatif sedikit, dan di sana saya mengikutinya. Anda dapat memperlakukan C ++ atau Java seperti Python dan memberikan pengecualian untuk semuanya, tetapi kerja Anda tidak sesuai dengan bagaimana bahasa itu sendiri diharapkan untuk digunakan.

Saya lebih suka pendekatan Python, tapi saya pikir itu ide yang buruk untuk memilih bahasa lain ke dalamnya.

Winston Ewert
sumber
1
@gnat, saya tahu. Maksud saya adalah bahwa Anda harus mengikuti konvensi bahasa (dalam hal ini Java) bahkan jika mereka bukan favorit Anda.
Winston Ewert
6
+1 "exceptions should be exceptional" is a terrible rule of thumb.Dikatakan dengan baik! Itulah salah satu hal yang orang ulangi tanpa memikirkannya.
Andres F.
2
"Diharapkan" didefinisikan bukan oleh argumen subyektif atau konvensi tetapi oleh kontrak API / fungsi (ini mungkin eksplisit, tetapi seringkali hanya tersirat). Fungsi / API / subsistem yang berbeda dapat memiliki harapan yang berbeda, misalnya untuk beberapa fungsionalitas tingkat yang lebih tinggi, file yang tidak ada merupakan kasus yang diharapkan untuk ditangani (mungkin melaporkan hal ini kepada pengguna melalui GUI), untuk fungsi tingkat bawah lainnya. mungkin tidak (dan karenanya harus membuang pengecualian). Jawaban ini sepertinya melewatkan poin penting itu ....
mikera
1
@ Mikera, ya fungsi harus (hanya) membuang pengecualian yang ditentukan dalam kontraknya. Tapi bukan itu pertanyaannya. Pertanyaannya adalah bagaimana Anda memutuskan kontrak apa yang seharusnya. Saya berpendapat bahwa aturan umum, "pengecualian harus luar biasa" tidak membantu dalam membuat keputusan itu.
Winston Ewert
1
@ supercat, saya tidak berpikir itu benar-benar masalah yang akhirnya menjadi lebih umum. Saya pikir pertanyaan kritis adalah memiliki standar waras. Jika saya tidak secara eksplisit menangani kondisi kesalahan, apakah kode saya berpura-pura tidak terjadi, atau apakah saya mendapatkan pesan kesalahan yang bermanfaat?
Winston Ewert
30

Saya selalu memikirkan hal-hal seperti mengakses server basis data atau API web ketika memikirkan pengecualian. Anda mengharapkan server / API web berfungsi, tetapi dalam kasus luar biasa mungkin tidak (server down). Permintaan web mungkin cepat biasanya, tetapi dalam keadaan luar biasa (beban tinggi) mungkin waktu habis. Ini adalah sesuatu di luar kendali Anda.

Data input pengguna Anda berada dalam kendali Anda, karena Anda dapat memeriksa apa yang mereka kirimkan dan melakukan apa yang Anda suka. Dalam kasus Anda, saya akan memvalidasi input pengguna bahkan sebelum mencoba menyimpannya. Dan saya cenderung setuju bahwa pengguna yang menyediakan data yang tidak valid harusnya diharapkan, dan aplikasi Anda harus memperhitungkannya dengan memvalidasi input dan menyediakan pesan kesalahan yang ramah pengguna.

Yang mengatakan, saya menggunakan pengecualian di sebagian besar setter model domain saya, di mana seharusnya sama sekali tidak ada peluang data tidak valid masuk. Namun, ini adalah garis pertahanan terakhir, dan saya cenderung membangun form input dengan aturan validasi yang kaya , sehingga praktis tidak ada peluang memicu pengecualian model domain itu. Jadi ketika seorang setter mengharapkan satu hal, dan mendapatkan yang lain, itu adalah situasi yang luar biasa, yang seharusnya tidak terjadi dalam keadaan biasa.

EDIT (sesuatu yang perlu dipertimbangkan):

Saat mengirim data yang disediakan pengguna ke db, Anda tahu sebelumnya apa yang harus dan tidak harus Anda masukkan ke dalam tabel Anda. Ini berarti bahwa data dapat divalidasi terhadap beberapa format yang diharapkan. Ini adalah sesuatu yang bisa Anda kendalikan. Yang tidak bisa Anda kendalikan adalah server Anda gagal di tengah kueri Anda. Jadi, Anda tahu kueri itu ok dan data disaring / divalidasi, Anda mencoba kueri dan masih gagal, ini adalah situasi yang luar biasa.

Demikian pula dengan permintaan web, Anda tidak dapat mengetahui apakah permintaan akan habis, atau gagal terhubung sebelum Anda mencoba mengirimnya. Jadi ini juga menjamin pendekatan coba / tangkap, karena Anda tidak dapat menanyakan server apakah itu akan berfungsi beberapa milidetik nanti ketika Anda mengirim permintaan.

Ivan Pintar
sumber
8
Tapi kenapa? Mengapa pengecualian kurang bermanfaat dalam menangani masalah yang lebih diharapkan?
Winston Ewert
6
@Pinetree, memeriksa keberadaan file sebelum membuka file adalah ide yang buruk. File dapat berhenti ada antara cek dan buka, file tidak dapat memiliki izin yang memungkinkan Anda membukanya, dan memeriksa keberadaan dan kemudian membuka file akan membutuhkan dua panggilan sistem yang mahal. Anda lebih baik mencoba membuka file dan kemudian berurusan dengan kegagalan untuk melakukannya.
Winston Ewert
10
Sejauh yang saya bisa lihat, hampir semua kemungkinan kegagalan lebih baik ditangani sebagai pemulihan dari kegagalan daripada mencoba untuk memeriksa kesuksesan sebelumnya. Apakah Anda menggunakan pengecualian atau sesuatu yang mengindikasikan kegagalan adalah masalah terpisah. Saya lebih suka pengecualian karena saya tidak bisa mengabaikannya secara tidak sengaja.
Winston Ewert
11
Saya tidak setuju dengan premis Anda bahwa karena data pengguna yang tidak valid diharapkan, itu tidak dapat dianggap luar biasa. Jika saya menulis parser, dan seseorang memberinya data yang tidak dapat diurai, itu merupakan pengecualian. Saya tidak bisa melanjutkan penguraian. Bagaimana pengecualian ditangani adalah pertanyaan lain sepenuhnya.
ConditionRacer
4
File.ReadAllBytes akan melempar FileNotFoundExceptionketika diberi input yang salah (misalnya nama file yang tidak ada). Itu adalah satu-satunya cara yang sah untuk mengancam kesalahan ini, apa lagi yang dapat Anda lakukan tanpa menghasilkan kode kesalahan kembali?
o
16

Referensi

Dari Programmer Pragmatis:

Kami percaya bahwa pengecualian harus jarang digunakan sebagai bagian dari aliran normal program; pengecualian harus disediakan untuk acara yang tidak terduga. Asumsikan bahwa pengecualian yang tidak tertangkap akan menghentikan program Anda dan bertanya pada diri sendiri, "Apakah kode ini akan tetap berjalan jika saya menghapus semua penangan pengecualian?" Jika jawabannya "tidak," maka mungkin pengecualian sedang digunakan dalam keadaan yang tidak khusus.

Mereka melanjutkan untuk memeriksa contoh membuka file untuk dibaca, dan file tidak ada - haruskah itu menimbulkan pengecualian?

Jika file tersebut seharusnya ada di sana, maka pengecualian dibenarkan. [...] Di sisi lain, jika Anda tidak tahu apakah file tersebut seharusnya ada atau tidak, maka itu tidak tampak luar biasa jika Anda tidak dapat menemukannya, dan pengembalian kesalahan sesuai.

Kemudian, mereka membahas mengapa mereka memilih pendekatan ini:

[A] dan pengecualian mewakili transfer kontrol langsung dan nonlokal - ini semacam cascading goto. Program yang menggunakan pengecualian sebagai bagian dari pemrosesan normal mereka menderita dari semua masalah keterbacaan dan pemeliharaan kode spaghetti klasik. Program-program ini memecahkan enkapsulasi: rutinitas dan penelepon mereka lebih erat digabungkan melalui penanganan pengecualian.

Mengenai situasi Anda

Pertanyaan Anda menjadi "Haruskah kesalahan validasi meningkatkan pengecualian?" Jawabannya adalah itu tergantung pada di mana validasi terjadi.

Jika metode yang dimaksud adalah dalam bagian kode yang diasumsikan bahwa data input telah divalidasi, data input yang tidak valid harus memunculkan pengecualian; jika kode dirancang sedemikian rupa sehingga metode ini akan menerima input tepat yang dimasukkan oleh pengguna, diharapkan data yang tidak valid, dan pengecualian tidak boleh diajukan.

Mike Partridge
sumber
11

Ada banyak pemenuhan filosofis di sini, tetapi secara umum, kondisi luar biasa hanyalah kondisi yang tidak dapat atau tidak ingin Anda tangani (selain pembersihan, pelaporan kesalahan, dan sejenisnya) tanpa campur tangan pengguna. Dengan kata lain, itu adalah kondisi yang tidak dapat dipulihkan.

Jika Anda menyerahkan program path file, dengan maksud memproses file itu dengan cara tertentu, dan file yang ditentukan oleh path itu tidak ada, itu adalah kondisi yang luar biasa. Anda tidak dapat melakukan apa pun tentang itu dalam kode Anda, selain melaporkannya kepada pengguna dan mengizinkan mereka untuk menentukan jalur file yang berbeda.

Robert Harvey
sumber
1
+1, sangat dekat dengan apa yang akan saya katakan. Saya akan mengatakan ini lebih tentang ruang lingkup, dan benar-benar tidak ada hubungannya dengan pengguna. Sebuah contoh yang baik dari ini adalah perbedaan antara dua fungsi .Net int.Parse dan int.TryParse, yang pertama tidak memiliki pilihan selain melempar pengecualian pada input yang buruk, yang kemudian seharusnya tidak pernah melempar pengecualian
jmoreno
1
@ jmoreno: Ergo, Anda akan menggunakan TryParse ketika kode dapat melakukan sesuatu tentang kondisi yang tidak dapat diuraikan, dan Parse ketika tidak bisa.
Robert Harvey
7

Ada dua masalah yang harus Anda pertimbangkan:

  1. Anda membahas satu masalah - sebut saja Assignerkarena masalah ini adalah untuk menetapkan input ke objek terstruktur - dan Anda menyatakan kendala bahwa inputnya valid

  2. antarmuka pengguna yang diimplementasikan dengan baik memiliki masalah tambahan: validasi input pengguna & umpan balik konstruktif tentang kesalahan (sebut saja bagian ini Validator)

Dari sudut pandang Assignerkomponen, melemparkan pengecualian benar-benar masuk akal, karena Anda telah menyatakan kendala yang telah dilanggar.

Dari sudut pandang pengalaman pengguna , pengguna tidak boleh berbicara langsung dengan ini Assignersejak awal. Mereka harus berbicara untuk itu melalui para Validator.

Sekarang, dalam Validator, input pengguna yang tidak valid bukanlah kasus yang luar biasa, ini benar-benar kasus yang membuat Anda lebih tertarik. Jadi di sini pengecualian tidak akan sesuai, dan ini juga tempat Anda ingin mengidentifikasi semua kesalahan daripada menyerah pada yang pertama.

Anda akan memperhatikan bahwa saya tidak menyebutkan bagaimana keprihatinan ini diterapkan. Sepertinya Anda sedang membicarakan tentang hal itu Assignerdan kolega Anda berbicara tentang gabungan Validator+Assigner. Setelah Anda menyadari ada yang dua terpisah (atau dipisahkan) keprihatinan, setidaknya Anda bisa membicarakannya bijaksana.


Untuk menanggapi komentar Renan, saya hanya berasumsi bahwa setelah Anda mengidentifikasi dua masalah Anda yang berbeda, jelas kasus apa yang harus dianggap luar biasa dalam setiap konteks.

Bahkan, jika tidak jelas apakah sesuatu harus dianggap luar biasa, saya berpendapat Anda mungkin belum selesai mengidentifikasi masalah independen dalam solusi Anda.

Saya kira itu membuat jawaban langsung untuk

... bagaimana saya tahu jika kasus saya luar biasa?

tetap menyederhanakan sampai jelas . Ketika Anda memiliki setumpuk konsep sederhana yang Anda pahami dengan baik, Anda dapat beralasan dengan jelas untuk menyusunnya kembali menjadi kode, kelas, perpustakaan atau apa pun.

Tak berguna
sumber
-1 Ya, ada dua masalah, tetapi ini tidak menjawab pertanyaan "Bagaimana saya tahu jika kasus saya luar biasa?"
RMalke
Intinya adalah kasus yang sama dapat menjadi luar biasa dalam satu konteks, dan tidak dalam konteks lain. Mengidentifikasi konteks mana yang sebenarnya Anda bicarakan (alih-alih menyatukan keduanya) menjawab pertanyaan di sini.
berguna
... sebenarnya, mungkin tidak - saya telah menjawab pokok jawaban Anda, sebagai gantinya.
berguna
4

Orang lain telah menjawab dengan baik, tetapi masih di sini adalah jawaban singkat saya. Pengecualian adalah situasi di mana ada sesuatu di lingkungan yang salah, yang tidak dapat Anda kendalikan dan kode Anda tidak bisa maju sama sekali. Dalam hal ini Anda juga harus memberi tahu pengguna apa yang salah, mengapa Anda tidak bisa melangkah lebih jauh, dan apa resolusinya.

Manoj R
sumber
3

Saya tidak pernah menjadi penggemar berat saran bahwa Anda hanya harus melemparkan pengecualian dalam kasus-kasus yang luar biasa, sebagian karena itu tidak mengatakan apa-apa (seperti mengatakan bahwa Anda hanya harus makan makanan yang dapat dimakan), tetapi juga karena itu sangat subjektif, dan seringkali tidak jelas apa yang merupakan kasus luar biasa dan apa yang tidak.

Namun, ada alasan bagus untuk saran ini: melempar dan menangkap pengecualian lambat, dan jika Anda menjalankan kode Anda di debugger di Visual Studio dengan itu diatur untuk memberi tahu Anda setiap kali pengecualian dilemparkan, Anda bisa berakhir dipukul oleh puluhan jika tidak ratusan pesan jauh sebelum Anda sampai ke masalah.

Jadi sebagai aturan umum, jika:

  • kode Anda bebas bug, dan
  • layanan yang bergantung semuanya tersedia, dan
  • pengguna Anda menggunakan program Anda dengan cara yang seharusnya digunakan (bahkan jika beberapa input yang mereka berikan tidak valid)

maka kode Anda seharusnya tidak pernah melemparkan pengecualian, bahkan yang tertangkap kemudian. Untuk menjebak data yang tidak valid, Anda dapat menggunakan validator di tingkat UI atau kode seperti Int32.TryParse()di lapisan presentasi.

Untuk hal lain, Anda harus tetap berpegang pada prinsip bahwa pengecualian berarti bahwa metode Anda tidak dapat melakukan apa yang dikatakan namanya. Secara umum itu bukan ide yang baik untuk menggunakan kode kembali untuk menunjukkan kegagalan (kecuali nama metode Anda dengan jelas menunjukkan bahwa ia melakukannya, misalnya TryParse()) karena dua alasan. Pertama, respons default untuk kode kesalahan adalah mengabaikan kondisi kesalahan dan melanjutkan tanpa memperhatikan; kedua, Anda dapat dengan mudah berakhir dengan beberapa metode menggunakan kode pengembalian dan metode lain menggunakan pengecualian, dan lupa yang mana. Saya bahkan telah melihat basis kode di mana dua implementasi yang berbeda dipertukarkan dari antarmuka yang sama mengambil pendekatan yang berbeda di sini.

jammycakes
sumber
2

Pengecualian harus mewakili kondisi yang kemungkinan kode panggilan langsung tidak akan siap untuk ditangani, bahkan jika metode panggilan mungkin. Pertimbangkan, misalnya, kode yang membaca beberapa data dari file, dapat secara sah mengasumsikan bahwa file yang valid akan berakhir dengan catatan yang valid, dan tidak diharuskan untuk mengekstrak informasi apa pun dari catatan parsial.

Jika rutin baca-data tidak menggunakan pengecualian tetapi hanya melaporkan apakah pembacaan berhasil atau tidak, kode panggilan harus seperti:

temp = dataSource.readInteger();
if (temp == null) return null;
field1 = (int)temp;
temp = dataSource.readInteger();
if (temp == null) return null;
field2 = (int)temp;
temp = dataSource.readString();
if (temp == null) return null;
field3 = temp;

dll. menghabiskan tiga baris kode untuk setiap karya yang bermanfaat. Sebaliknya, jika readIntegerakan melempar pengecualian saat menemukan akhir file, dan jika pemanggil dapat meneruskan pengecualian, maka kode menjadi:

field1 = dataSource.readInteger();
field2 = dataSource.readInteger();
field3 = dataSource.readString();

Jauh lebih sederhana dan tampak lebih bersih, dengan penekanan yang jauh lebih besar pada kasus di mana segala sesuatu bekerja secara normal. Perhatikan bahwa dalam kasus-kasus di mana penelepon langsung akan mengharapkan untuk menangani suatu kondisi, metode yang mengembalikan kode kesalahan sering kali lebih bermanfaat daripada yang melempar pengecualian. Misalnya, untuk menjumlahkan semua bilangan bulat dalam file:

do
{
  temp = dataSource.tryReadInteger();
  if (temp == null) break;
  total += (int)temp;
} while(true);

melawan

try
{
  do
  {
    total += (int)dataSource.readInteger();
  }
  while(true);
}
catch endOfDataSourceException ex
{ // Don't do anything, since this is an expected condition (eventually)
}

Kode yang meminta bilangan bulat mengharapkan bahwa salah satu dari panggilan itu akan gagal. Memiliki kode menggunakan loop tanpa akhir yang akan berjalan sampai itu terjadi jauh lebih elegan daripada menggunakan metode yang menunjukkan kegagalan melalui nilai kembali.

Karena kelas sering tidak tahu kondisi apa yang klien mereka akan atau tidak harapkan, sering membantu menawarkan dua versi metode yang bisa gagal dengan cara yang diharapkan oleh beberapa penelepon dan penelepon lainnya tidak. Melakukannya akan memungkinkan metode seperti itu digunakan secara bersih dengan kedua jenis penelepon. Perhatikan juga bahwa bahkan metode "coba" harus mengeluarkan pengecualian jika situasi muncul, penelepon mungkin tidak mengharapkannya. Misalnya, tryReadIntegertidak boleh melempar pengecualian jika menemui kondisi akhir file yang bersih (jika pemanggil tidak mengharapkan itu, pemanggil akan menggunakanreadInteger). Di sisi lain, mungkin harus membuang pengecualian jika data tidak dapat dibaca karena mis. Memory stick yang mengandung itu dicabut. Meskipun peristiwa semacam itu harus selalu diakui sebagai suatu kemungkinan, tidak mungkin kode panggilan langsung akan disiapkan untuk melakukan sesuatu yang berguna sebagai tanggapan; seharusnya tidak dilaporkan dengan cara yang sama seperti kondisi akhir file.

supercat
sumber
2

Hal terpenting dalam menulis perangkat lunak adalah membuatnya dapat dibaca. Semua pertimbangan lain bersifat sekunder, termasuk membuatnya efisien dan membuatnya benar. Jika itu dapat dibaca, sisanya dapat diurus dalam pemeliharaan, dan jika tidak dapat dibaca maka Anda lebih baik membuangnya saja. Karena itu, Anda harus melempar pengecualian ketika meningkatkan keterbacaan.

Saat Anda menulis beberapa algoritme, pikirkan saja orang di masa depan yang akan membacanya. Ketika Anda datang ke suatu tempat di mana ada potensi masalah, tanyakan pada diri Anda apakah pembaca ingin melihat bagaimana Anda menangani masalah itu sekarang , atau apakah pembaca lebih suka untuk melanjutkan dengan algoritma?

Saya suka memikirkan resep untuk kue coklat. Ketika ia memberi tahu Anda untuk menambahkan telur, ia memiliki pilihan: ia dapat mengasumsikan Anda memiliki telur dan melanjutkan resepnya, atau dapat memulai penjelasan tentang bagaimana Anda bisa mendapatkan telur jika Anda tidak memiliki telur. Itu bisa mengisi seluruh buku dengan teknik untuk berburu ayam liar, semua untuk membantu Anda membuat kue. Itu bagus, tetapi kebanyakan orang tidak akan mau membaca resep itu. Kebanyakan orang lebih suka berasumsi bahwa telur tersedia, dan melanjutkan resepnya. Itu panggilan penilaian yang perlu penulis buat saat menulis resep.

Tidak ada aturan yang dijamin tentang apa yang membuat pengecualian yang baik dan masalah apa yang harus ditangani segera, karena itu mengharuskan Anda membaca pikiran pembaca Anda. Yang terbaik yang pernah Anda lakukan adalah aturan praktis, dan "pengecualian hanya untuk keadaan luar biasa" adalah yang cukup bagus. Biasanya ketika pembaca membaca metode Anda, mereka mencari apa yang akan dilakukan 99% dari waktu itu, dan mereka lebih suka tidak memiliki kasus-kasus sudut yang aneh seperti menangani pengguna memasukkan input ilegal dan hal-hal lain yang hampir tidak pernah terjadi. Mereka ingin melihat aliran normal perangkat lunak Anda ditata secara langsung, satu per satu instruksi seolah-olah masalah tidak pernah terjadi.

Geo
sumber
2

Mungkin ada persyaratan untuk melaporkan setiap kesalahan yang dapat kita temukan di input, bukan hanya yang pertama.

Inilah sebabnya mengapa Anda tidak dapat melemparkan pengecualian di sini. Pengecualian segera mengganggu proses validasi. Jadi akan ada banyak penyelesaian untuk menyelesaikan ini.

Contoh buruk:

Metode validasi untuk Dogkelas menggunakan pengecualian:

void validate(Set<DogValidationException> previousExceptions) {
    if (!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        DogValidationException disallowedName = new DogValidationException(Problem.DISALLOWED_DOG_NAME);
        if (!previousExceptions.contains(disallowedName)){
            throw disallowedName;
        }
    }
    if (this.legs < 4) {
        DogValidationException invalidDog = new DogValidationException(Problem.LITERALLY_INVALID_DOG);
        if (!previousExceptions.contains(invalidDog)){
            throw invalidDog;
        }
    }
    // etc.
}

Bagaimana cara menyebutnya:

Set<DogValidationException> exceptions = new HashSet<DogValidationException>();
boolean retry;
do {
    retry = false;
    try {
        dog.validate(exceptions);
    } catch (DogValidationException e) {
        exceptions.add(e);
        retry = true;
    }
} while (retry);

if(exceptions.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

Masalahnya di sini adalah bahwa proses validasi, untuk mendapatkan semua kesalahan, harus melewati pengecualian yang sudah ditemukan. Hal di atas bisa berhasil, tetapi ini merupakan penyalahgunaan pengecualian . Jenis validasi yang Anda minta harus dilakukan sebelum basis data disentuh. Jadi tidak perlu memutar kembali apa pun. Dan, hasil validasi cenderung menjadi kesalahan validasi (semoga nol, meskipun).

Pendekatan yang lebih baik adalah:

Panggilan metode:

Set<Problem> validationResults = dog.validate();
if(validationResults.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

Metode validasi:

Set<Problem> validate() {
    Set<Problem> result = new HashSet<Problem>();
    if(!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        result.add(Problem.DISALLOWED_DOG_NAME);
    }
    if(this.legs < 4) {
        result.add(Problem.LITERALLY_INVALID_DOG);
    }
    // etc.
    return result;
}

Mengapa? Ada banyak alasan, dan sebagian besar alasan telah ditunjukkan dalam tanggapan lainnya. Sederhananya: Jauh lebih mudah untuk dibaca dan dipahami oleh orang lain. Kedua, apakah Anda ingin menunjukkan jejak tumpukan pengguna untuk menjelaskan bahwa ia salah mengaturnya dog?

Jika , selama komit dalam contoh kedua, masih muncul kesalahan , meskipun validator Anda memvalidasi dogdengan nol masalah, maka melemparkan pengecualian adalah hal yang benar . Seperti: Tidak ada koneksi basis data, entri basis data telah dimodifikasi oleh orang lain, atau sejenisnya.

Matthias Ronge
sumber