Apakah variabel kesalahan merupakan anti-pola atau desain yang bagus?

44

Untuk menangani beberapa kemungkinan kesalahan yang seharusnya tidak menghentikan eksekusi, saya memiliki errorvariabel yang dapat diperiksa dan digunakan klien untuk melempar pengecualian. Apakah ini Anti-Pola? Apakah ada cara yang lebih baik untuk menangani ini? Untuk contoh tindakan ini, Anda dapat melihat API mysqli PHP . Asumsikan bahwa masalah visibilitas (pengakses, ruang lingkup publik dan pribadi, apakah variabel dalam kelas atau global?) Ditangani dengan benar.

Mikayla Maki
sumber
6
Ini untuk apa try/ catchada. Selain itu, Anda dapat meletakkan try/ catchlebih jauh ke atas tumpukan di lokasi yang lebih tepat untuk menanganinya (memungkinkan pemisahan masalah yang lebih besar).
jpmc26
Sesuatu yang perlu diingat: jika Anda akan menggunakan penanganan berbasis pengecualian dan Anda mendapatkan pengecualian, Anda tidak ingin menunjukkan terlalu banyak informasi kepada pengguna. Gunakan penangan kesalahan seperti Elmah atau Raygun.io untuk mencegatnya dan menampilkan pesan kesalahan umum kepada pengguna. JANGAN PERNAH menunjukkan jejak tumpukan atau pesan kesalahan khusus kepada pengguna, karena mereka mengungkapkan informasi tentang cara kerja aplikasi, yang dapat disalahgunakan.
Nzall
4
@Nate Nasihat Anda hanya berlaku untuk aplikasi penting keamanan di mana pengguna sama sekali tidak dipercaya. Pesan kesalahan yang tidak jelas itu sendiri merupakan anti-pola. Begitu juga mengirim laporan kesalahan melalui jaringan tanpa persetujuan tertulis dari pengguna.
piedar
3
@piedar Saya membuat pertanyaan terpisah di mana ini dapat didiskusikan lebih bebas: programmers.stackexchange.com/questions/245255/…
Nzall
26
Prinsip umum dalam desain API yang membawa Anda cukup jauh adalah untuk selalu melihat apa yang dilakukan PHP, dan kemudian melakukan hal yang sebaliknya.
Philipp

Jawaban:

65

Jika suatu bahasa secara inheren mendukung pengecualian, maka lebih disukai untuk melemparkan pengecualian dan klien dapat menangkap pengecualian jika mereka tidak ingin itu mengakibatkan kegagalan. Faktanya, klien kode Anda mengharapkan pengecualian dan akan mengalami banyak bug karena mereka tidak akan memeriksa nilai pengembalian.

Ada beberapa keuntungan menggunakan pengecualian jika Anda punya pilihan.

Pesan

Pengecualian berisi pesan kesalahan yang dapat dibaca pengguna yang dapat digunakan oleh pengembang untuk debugging atau bahkan ditampilkan kepada pengguna jika diinginkan. Jika kode pengkonsumsi tidak dapat menangani pengecualian, selalu dapat mencatatnya sehingga pengembang dapat menelusuri log tanpa harus berhenti di setiap penelusuran lainnya untuk mencari tahu apa nilai pengembalian dan memetakannya dalam tabel untuk mencari tahu apa yang menjadi pengecualian aktual.

Dengan nilai pengembalian, tidak ada informasi tambahan yang dapat diberikan dengan mudah. Beberapa bahasa akan mendukung pembuatan panggilan metode untuk mendapatkan pesan kesalahan terakhir, sehingga masalah ini sedikit berkurang, tetapi itu membutuhkan penelepon untuk membuat panggilan tambahan dan kadang-kadang akan memerlukan akses ke 'objek khusus' yang membawa informasi ini.

Dalam hal pesan pengecualian, saya memberikan konteks sebanyak mungkin, seperti:

Kebijakan nama "foo" tidak dapat diambil untuk "bilah" pengguna, yang dirujuk di profil pengguna.

Bandingkan ini dengan kode pengembalian -85. Manakah yang Anda pilih?

Panggil tumpukan

Pengecualian biasanya juga memiliki tumpukan panggilan terperinci yang membantu men-debug kode lebih cepat dan lebih cepat, dan juga dapat dicatat oleh kode panggilan jika diinginkan. Ini memungkinkan pengembang untuk menentukan masalah biasanya ke garis yang tepat, dan karenanya sangat kuat. Sekali lagi, bandingkan ini dengan file log dengan nilai balik (seperti -85, 101, 0, dll.), Yang mana yang Anda inginkan?

Pendekatan bias cepat gagal

Jika suatu metode dipanggil di suatu tempat yang gagal, itu akan mengeluarkan pengecualian. Kode panggilan harus menekan pengecualian secara eksplisit atau akan gagal. Saya telah menemukan ini benar-benar luar biasa karena selama pengembangan dan pengujian (dan bahkan dalam produksi) kode gagal dengan cepat, memaksa pengembang untuk memperbaikinya. Dalam hal nilai pengembalian, jika pemeriksaan untuk nilai balik terlewatkan, kesalahan diabaikan secara diam-diam dan bug muncul di tempat yang tidak terduga, biasanya dengan biaya yang jauh lebih tinggi untuk debug dan perbaikan.

Pengecualian Pembungkus dan Pembukaan

Pengecualian dapat dimasukkan ke dalam pengecualian lain dan kemudian dibuka jika diperlukan. Misalnya, kode Anda mungkin dilempar ke ArgumentNullExceptionmana kode panggilan mungkin membungkus di dalam UnableToRetrievePolicyExceptionkarena operasi itu gagal dalam kode panggilan. Sementara pengguna mungkin diperlihatkan pesan yang mirip dengan contoh yang saya berikan di atas, beberapa kode diagnostik mungkin membuka bukaan pengecualian dan menemukan bahwa ArgumentNullExceptiontelah menyebabkan masalah, yang berarti itu adalah kesalahan pengkodean dalam kode konsumen Anda. Ini kemudian dapat mengaktifkan peringatan sehingga pengembang dapat memperbaiki kode. Skenario lanjutan seperti itu tidak mudah diimplementasikan dengan nilai pengembalian.

Kesederhanaan kode

Yang ini sedikit lebih sulit untuk dijelaskan, tetapi saya belajar melalui pengkodean ini baik dengan nilai pengembalian maupun pengecualian. Kode yang ditulis menggunakan nilai kembali biasanya akan melakukan panggilan dan kemudian memiliki serangkaian pemeriksaan pada apa nilai balik itu. Dalam beberapa kasus, itu akan membuat panggilan ke metode lain, dan sekarang akan memiliki serangkaian pemeriksaan untuk nilai-nilai kembali dari metode itu. Dengan pengecualian, penanganan pengecualian jauh lebih sederhana di sebagian besar atau tidak semua kasus. Anda memiliki blok coba / tangkap / akhirnya, dengan runtime mencoba yang terbaik untuk mengeksekusi kode di blok akhirnya untuk pembersihan. Bahkan blok try / catch / akhirnya yang bersarang relatif lebih mudah untuk ditindaklanjuti dan dipelihara dibandingkan dengan nested if / else dan nilai pengembalian terkait dari berbagai metode.

Kesimpulan

Jika platform yang Anda gunakan mendukung pengecualian (khususnya Java atau .NET), maka Anda harus mengasumsikan bahwa tidak ada cara lain selain melempar pengecualian karena platform ini memiliki panduan untuk melempar pengecualian, dan klien Anda akan mengharapkan begitu. Jika saya menggunakan perpustakaan Anda, saya tidak akan repot untuk memeriksa nilai kembali karena saya perkirakan pengecualian akan dilempar, seperti itulah dunia di platform ini.

Namun, jika itu adalah C ++, maka itu akan sedikit lebih sulit untuk ditentukan karena basis kode yang besar sudah ada dengan kode kembali, dan sejumlah besar pengembang disetel untuk mengembalikan nilai yang bertentangan dengan pengecualian (misalnya Windows penuh dengan HRESULT) . Selain itu, dalam banyak aplikasi, itu bisa menjadi masalah kinerja juga (atau setidaknya dianggap).

Omer Iqbal
sumber
5
Windows mengembalikan nilai HRESULT dari fungsi C ++ untuk menjaga kompatibilitas C dalam API publiknya (dan karena Anda mulai masuk ke dunia yang terluka saat mencoba untuk membuat pengecualian di seluruh batas). Jangan membabi buta mengikuti model sistem operasi jika Anda sedang menulis aplikasi.
Cody Grey
2
Satu-satunya hal yang akan saya tambahkan ke ini adalah penyebutan yang lebih longgar. Pengecualian memungkinkan Anda untuk menangani banyak situasi tak terduga di tempat yang paling tepat. Misalnya, dalam aplikasi web, Anda ingin mengembalikan 500 dengan pengecualian apa pun yang tidak disiapkan kode Anda, alih-alih menutup aplikasi web. Jadi, Anda perlu menangkap semua di bagian atas kode Anda (atau dalam kerangka kerja Anda). Situasi serupa terjadi di GUI desktop. Tetapi Anda juga dapat menempatkan penangan yang kurang umum di berbagai tempat dalam kode untuk menangani berbagai situasi kegagalan dengan cara yang sesuai untuk proses saat ini sedang dicoba.
jpmc26
2
@TrentonMaki Jika Anda berbicara tentang kesalahan dari konstruktor C ++, jawaban terbaik ada di sini: parashift.com/c++-faq-lite/ctors-can-throw.html . Singkatnya, lempar pengecualian, tapi ingat untuk membersihkan potensi kebocoran terlebih dahulu. Saya tidak mengetahui bahasa lain di mana hanya melempar langsung dari sebuah konstruktor adalah hal yang buruk. Sebagian besar pengguna API saya pikir lebih suka menangkap pengecualian daripada memeriksa kode kesalahan.
Ogre Psalm33
3
"Skenario lanjutan seperti itu tidak mudah diimplementasikan dengan nilai pengembalian." Tentu kamu bisa! Yang harus Anda lakukan adalah membuat ErrorStateReturnVariablekelas-super, dan salah satu propertinya adalah InnerErrorState(yang merupakan turunan dari ErrorStateReturnVariable), yang mengimplementasikan sub-kelas dapat diatur untuk menunjukkan rantai kesalahan ... oh, tunggu. : p
Brian S
5
Hanya karena suatu bahasa mendukung pengecualian tidak menjadikannya obat mujarab. Pengecualian memperkenalkan jalur eksekusi tersembunyi, dan karenanya pengaruhnya perlu dikontrol dengan baik; coba / tangkap mudah ditambahkan, tetapi mendapatkan pemulihan yang benar itu sulit , ...
Matthieu M.
21

Variabel kesalahan adalah peninggalan dari bahasa seperti C, di mana pengecualian tidak tersedia. Hari ini, Anda harus menghindarinya kecuali ketika Anda sedang menulis perpustakaan yang berpotensi digunakan dari program C (atau bahasa serupa tanpa penanganan pengecualian).

Tentu saja, jika Anda memiliki jenis kesalahan yang bisa lebih baik diklasifikasikan sebagai "peringatan" (= perpustakaan Anda dapat memberikan hasil yang valid dan penelepon dapat mengabaikan peringatan jika menurutnya itu tidak penting), maka indikator status dalam formulir variabel dapat masuk akal bahkan dalam bahasa dengan pengecualian. Tapi waspadalah. Penelepon perpustakaan cenderung mengabaikan peringatan seperti itu bahkan jika mereka tidak seharusnya. Jadi pikirkan dua kali sebelum memperkenalkan konstruksi seperti itu ke lib Anda.

Doc Brown
sumber
1
Terima kasih untuk penjelasannya! Jawaban Anda + Omer Iqbal menjawab pertanyaan saya.
Mikayla Maki
Cara lain untuk menangani "peringatan" adalah dengan melemparkan pengecualian secara default dan memiliki semacam bendera opsional untuk menghentikan pengecualian agar tidak dilempar.
Cyanfish
1
@Cyanfish: ya, tapi kita harus berhati-hati untuk tidak mendesain berlebihan hal-hal seperti itu, terutama ketika membuat perpustakaan. Lebih baik berikan satu mekanisme peringatan yang sederhana dan berfungsi daripada 2, 3 atau lebih.
Doc Brown
Pengecualian hanya boleh dilontarkan ketika sebuah kegagalan, skenario yang tidak diketahui atau tidak dapat dipulihkan telah dialami - keadaan luar biasa . Anda harus mengharapkan dampak kinerja saat membuat pengecualian
Gusdor
@ Goddor: tentu saja, itu sebabnya "peringatan" khas harus IMHO tidak membuang pengecualian secara default. Tetapi ini juga sedikit tergantung pada tingkat abstraksi. Terkadang layanan atau perpustakaan tidak dapat memutuskan apakah suatu peristiwa yang tidak biasa harus diperlakukan sebagai pengecualian dari sudut pandang penelepon. Secara pribadi, dalam situasi seperti itu saya lebih suka lib hanya untuk menetapkan indikator peringatan (tidak terkecuali), biarkan penelepon menguji bendera itu dan melemparkan pengecualian jika ia menganggapnya sesuai. Itulah yang ada dalam pikiran saya ketika saya menulis di atas "lebih baik menyediakan satu mekanisme peringatan di perpustakaan".
Doc Brown
20

Ada beberapa cara untuk memberi sinyal kesalahan:

  • variabel kesalahan untuk memeriksa: C , Go , ...
  • pengecualian: Java , C # , ...
  • pawang "condition": Lisp (hanya?), ...
  • pengembalian polimorfik: Haskell , ML , Rust , ...

Masalah dari variabel kesalahan adalah mudah lupa untuk memeriksa.

Masalah pengecualian adalah yang menciptakan jalur eksekusi yang tersembunyi, dan, meskipun percobaan / tangkapan mudah untuk ditulis, memastikan pemulihan yang tepat dalam klausa tangkapan benar-benar sulit dilakukan (tidak ada dukungan dari sistem tipe / kompiler).

Masalah penangan kondisi adalah bahwa mereka tidak menyusun dengan baik: jika Anda memiliki eksekusi kode dinamis (fungsi virtual), maka tidak mungkin untuk memprediksi kondisi mana yang harus ditangani. Selain itu, jika kondisi yang sama dapat dinaikkan di beberapa titik, tidak ada yang mengatakan bahwa solusi yang seragam dapat diterapkan setiap kali, dan dengan cepat menjadi berantakan.

Pengembalian polimorfik ( Either a bdalam Haskell) adalah solusi favorit saya sejauh ini:

  • eksplisit: tidak ada jalur eksekusi yang tersembunyi
  • eksplisit: sepenuhnya didokumentasikan dalam tanda tangan jenis fungsi (tidak ada kejutan)
  • sulit untuk diabaikan: Anda harus mencocokkan pola untuk mendapatkan hasil yang diinginkan, dan menangani kasus kesalahan

Satu-satunya masalah adalah bahwa mereka berpotensi menyebabkan pemeriksaan berlebihan; bahasa yang menggunakannya memiliki idiom untuk mengaitkan panggilan fungsi yang menggunakannya, tetapi mungkin masih membutuhkan pengetikan / kekacauan yang lebih sedikit. Di Haskell ini akan menjadi monad ; namun, ini jauh lebih menakutkan daripada kedengarannya, lihat Railway Oriented Programming .

Matthieu M.
sumber
1
Jawaban yang bagus! Saya berharap saya bisa mendapatkan daftar cara untuk menangani kesalahan.
Mikayla Maki
Arrghhhh! Berapa kali saya melihat ini "Masalah dari variabel kesalahan adalah mudahnya lupa untuk memeriksa". Masalah dengan pengecualian adalah mudah untuk melupakannya. Kemudian aplikasi Anda mogok. Bos Anda tidak ingin membayar untuk memperbaikinya tetapi pelanggan Anda berhenti menggunakan aplikasi Anda karena mereka merasa frustrasi dengan crash. Semua karena sesuatu yang tidak akan mempengaruhi eksekusi program jika kode kesalahan dikembalikan dan diabaikan. Satu-satunya saat saya pernah melihat orang mengabaikan kode kesalahan adalah ketika tidak terlalu penting. Kode lebih tinggi dari rantai tahu ada yang salah.
Dunk
1
@Dunk: Saya tidak berpikir bahwa jawaban saya membuat permintaan maaf pengecualian juga; meskipun, itu mungkin tergantung pada jenis kode yang Anda tulis. Pengalaman kerja pribadi saya cenderung lebih menyukai sistem yang gagal di hadapan kesalahan karena korupsi data diam lebih buruk (dan tidak terdeteksi) dan data yang saya kerjakan bermanfaat bagi klien (tentu saja, itu juga berarti perbaikan yang mendesak).
Matthieu M.
Ini bukan masalah korupsi data diam-diam. Ini adalah pertanyaan untuk memahami aplikasi Anda dan mengetahui kapan dan di mana Anda perlu memverifikasi apakah suatu operasi berhasil atau tidak. Dalam banyak kasus, membuat tekad dan penanganan itu dapat ditunda. Seharusnya tidak terserah orang lain untuk memberi tahu saya ketika saya harus menangani operasi yang gagal, yang memerlukan pengecualian. Ini aplikasi saya, saya tahu kapan dan di mana saya ingin menangani masalah yang relevan. Jika orang menulis aplikasi yang dapat merusak data maka itu hanya melakukan pekerjaan yang sangat buruk. Menulis aplikasi yang macet (yang sering saya lihat) juga melakukan pekerjaan yang buruk.
Dunk
12

Saya pikir itu mengerikan. Saat ini saya sedang melakukan refactoring aplikasi Java yang menggunakan nilai balik alih-alih pengecualian. Meskipun Anda mungkin sama sekali tidak bekerja dengan Java, saya pikir ini berlaku.

Anda berakhir dengan kode seperti ini:

String result = x.doActionA();
if (result != null) {
  throw new Exception(result);
}
result = x.doActionB();
if (result != null) {
  throw new Exception(result);
}

Atau ini:

if (!x.doActionA()) {
  throw new Exception(x.getError());
}
if (!x.doActionB()) {
  throw new Exception(x.getError());
}

Saya lebih suka memiliki tindakan melemparkan pengecualian sendiri, sehingga Anda berakhir dengan sesuatu seperti:

x.doActionA();
x.doActionB();

Anda bisa membungkusnya dalam try-catch, dan mendapatkan pesan dari pengecualian, atau Anda dapat memilih untuk mengabaikan pengecualian, misalnya ketika Anda menghapus sesuatu yang mungkin sudah hilang. Ini juga menjaga jejak stack Anda, jika Anda memilikinya. Metode itu sendiri menjadi lebih mudah juga. Alih-alih menangani pengecualian sendiri, mereka hanya membuang apa yang salah.

Kode (mengerikan) saat ini:

private String doActionA() {
  try {
    someOperationThatCanGoWrong1();
    someOperationThatCanGoWrong2();
    someOperationThatCanGoWrong3();
    return null;
  } catch(Exception e) {
    return "Something went wrong!";
  }
}

Baru dan ditingkatkan:

private void doActionA() throws Exception {
  someOperationThatCanGoWrong1();
  someOperationThatCanGoWrong2();
  someOperationThatCanGoWrong3();
}

Strack trace dipertahankan dan pesannya tersedia dalam pengecualian, alih-alih yang tidak berguna "Ada yang salah!".

Anda tentu saja dapat memberikan pesan kesalahan yang lebih baik, dan Anda harus melakukannya. Tetapi posting ini ada di sini karena kode yang saya kerjakan saat ini sangat menyebalkan, dan Anda tidak boleh melakukan hal yang sama.

mrjink
sumber
1
Satu tangkapan, ada situasi di mana 'baru dan lebih baik' Anda akan kehilangan konteks di mana pengecualian awalnya terjadi. Misalnya dalam doActionA () "versi saat ini (mengerikan)", klausa tangkapan akan memiliki akses ke variabel instan dan informasi lain dari objek yang melampirkan untuk memberikan pesan yang lebih berguna.
InformedA
1
Itu benar, tetapi saat ini tidak terjadi di sini. Dan Anda selalu dapat menangkap pengecualian di doActionA dan membungkusnya di pengecualian lain dengan pesan status. Maka Anda masih akan memiliki jejak stack dan pesan yang bermanfaat. throw new Exception("Something went wrong with " + instanceVar, ex);
mrjink
Saya setuju, itu mungkin tidak terjadi dalam situasi Anda. Tetapi Anda tidak dapat "selalu" memasukkan informasi ke doActionA (). Mengapa? Penelepon doActionA () mungkin satu-satunya yang menyimpan informasi yang perlu Anda sertakan.
InformedA
2
Jadi, minta penelepon memasukkannya saat menangani pengecualian. Hal yang sama berlaku untuk pertanyaan awal. Tidak ada yang dapat Anda lakukan sekarang yang tidak dapat Anda lakukan dengan pengecualian, dan itu mengarah pada kode yang lebih bersih. Saya lebih suka pengecualian daripada boolean yang dikembalikan atau pesan kesalahan.
mrjink
Anda membuat asumsi yang biasanya keliru bahwa pengecualian terjadi di dalam "someOperation" apa pun dapat ditangani dan dibersihkan dengan cara yang sama. Apa yang terjadi dalam kehidupan nyata adalah bahwa Anda perlu menangkap dan menangani pengecualian untuk setiap operasi. Jadi, Anda tidak hanya melempar pengecualian seperti pada contoh Anda. Selain itu, menggunakan pengecualian kemudian berakhir dengan membuat sekelompok blok try-catch bersarang atau serangkaian blok try-catch. Seringkali membuat kode jauh lebih mudah dibaca. Saya tidak menentang pengecualian, tetapi saya menggunakan alat yang sesuai untuk situasi tertentu. Pengecualian hanya 1 alat.
Dunk
5

"Untuk menangani beberapa kemungkinan kesalahan yang terjadi, itu seharusnya tidak menghentikan eksekusi,"

Jika Anda bermaksud bahwa kesalahan tidak boleh menghentikan pelaksanaan fungsi saat ini, tetapi harus dilaporkan ke pemanggil dengan cara tertentu - maka Anda memiliki beberapa opsi yang belum benar-benar disebutkan. Kasus ini benar-benar lebih merupakan peringatan daripada kesalahan. Melempar / Mengembalikan bukan opsi karena itu mengakhiri fungsi saat ini. Parameter pesan kesalahan tunggal atau kembali hanya memungkinkan paling banyak satu dari kesalahan ini terjadi.

Dua pola yang saya gunakan adalah:

  • Koleksi kesalahan / peringatan, baik diteruskan atau disimpan sebagai variabel anggota. Yang Anda tambahkan barang ke dan terus diproses. Saya pribadi tidak begitu menyukai pendekatan ini karena saya merasa itu melemahkan penelepon.

  • Mengirimkan objek penanganan kesalahan / peringatan (atau mengaturnya sebagai variabel anggota). Dan setiap kesalahan memanggil fungsi anggota dari pawang. Dengan cara ini penelepon dapat memutuskan apa yang harus dilakukan dengan kesalahan yang tidak berhenti tersebut.

Apa yang Anda sampaikan ke koleksi / penangan ini harus mengandung konteks yang cukup agar kesalahan ditangani "dengan benar" - Sebuah string biasanya terlalu sedikit, memberikannya beberapa contoh Pengecualian sering masuk akal - tetapi kadang-kadang disukai (sebagai penyalahgunaan Pengecualian) .

Kode umum yang menggunakan penangan kesalahan mungkin terlihat seperti ini

class MyFunClass {
  public interface ErrorHandler {
     void onError(Exception e);
     void onWarning(Exception e);
  }

  ErrorHandler eh;

  public void canFail(int i) {
     if(i==0) {
        if(eh!=null) eh.onWarning(new Exception("canFail shouldn't be called with i=0"));
     }
     if(i==1) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=1 is fatal");
        throw new RuntimeException("canFail called with i=2");
     }
     if(i==2) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=2 is an error, but not fatal"));
     }
  }
}
Michael Anderson
sumber
3
+1 karena mengetahui bahwa pengguna menginginkan peringatan, bukan kesalahan. Mungkin perlu disebutkan warningspaket Python , yang memberikan pola lain untuk masalah ini.
James_pic
Terima kasih atas jawaban Anda! Ini lebih dari apa yang ingin saya lihat, pola tambahan untuk menangani kesalahan di mana coba / tangkapan tradisional mungkin tidak cukup.
Mikayla Maki
Mungkin berguna untuk menyebutkan bahwa objek call-error-callback yang lewat bisa mengeluarkan pengecualian ketika kesalahan atau peringatan tertentu terjadi - memang, itu mungkin perilaku default - tetapi mungkin juga berguna untuk sarana yang dengannya ia dapat meminta fungsi panggilan untuk melakukan sesuatu. Misalnya, metode "handle parsing error" mungkin memberi nilai kepada penelepon bahwa parse harus dianggap telah dikembalikan.
supercat
5

Seringkali tidak ada yang salah dengan menggunakan pola ini atau pola itu, selama Anda menggunakan pola yang digunakan orang lain. Dalam pengembangan Objective-C , pola yang lebih disukai adalah untuk melewatkan pointer di mana metode yang disebut dapat menyimpan objek NSError. Pengecualian disediakan untuk kesalahan pemrograman dan menyebabkan crash (kecuali jika Anda memiliki programmer Java atau .NET yang menulis aplikasi iPhone pertama mereka). Dan ini bekerja dengan sangat baik.

gnasher729
sumber
4

Pertanyaannya sudah dijawab, tetapi saya tidak bisa menahan diri.

Anda tidak bisa benar-benar mengharapkan Pengecualian untuk memberikan solusi untuk semua kasus penggunaan. Palu siapa pun?

Ada beberapa kasus di mana Pengecualian bukan merupakan akhir semua dan menjadi semua, misalnya, jika suatu metode menerima permintaan dan bertanggung jawab untuk memvalidasi semua bidang yang diteruskan, dan bukan hanya yang pertama, Anda harus berpikir bahwa itu mungkin untuk dilakukan. menunjukkan penyebab kesalahan selama lebih dari satu bidang. Harus dimungkinkan juga untuk menunjukkan apakah sifat validasi mencegah pengguna melangkah lebih jauh atau tidak. Contohnya adalah kata sandi yang tidak kuat. Anda dapat menampilkan pesan kepada pengguna yang menunjukkan bahwa kata sandi yang dimasukkan tidak terlalu kuat, tetapi cukup kuat.

Anda dapat berargumen bahwa semua validasi ini dapat dilemparkan sebagai pengecualian pada akhir modul validasi, tetapi mereka akan menjadi kode kesalahan dalam apa pun kecuali dalam nama.

Jadi pelajaran di sini adalah: Pengecualian ada di tempatnya, seperti halnya kode kesalahan. Pilih dengan bijak.

Alexandre Santos
sumber
Saya pikir ini agak menyiratkan desain yang buruk. Suatu metode yang mengambil argumen harus dapat mengatasinya atau tidak - tidak ada di antaranya. Contoh Anda harus memiliki Validator(antarmuka) yang disuntikkan ke dalam metode yang dimaksud (atau objek di belakangnya). Bergantung pada yang disuntikkan Validator, metode akan melanjutkan dengan kata sandi yang buruk - atau tidak. Kode di sekitarnya kemudian dapat mencoba WeakValidatorjika pengguna memintanya setelah, misalnya, WeakPasswordExceptiondilemparkan oleh yang awalnya dicoba StrongValidator.
jhr
Ah, tapi saya tidak mengatakan ini bukan antarmuka, atau validator. Saya bahkan tidak menyebutkan JSR303. Dan, jika Anda membaca dengan seksama, saya memastikan untuk tidak mengatakan kata sandi yang lemah, tetapi itu tidak terlalu kuat. Kata sandi yang lemah akan menjadi alasan untuk menghentikan aliran dan meminta kata sandi yang lebih kuat kepada pengguna.
Alexandre Santos
Dan apa yang akan Anda lakukan dengan kata sandi yang middly-kuat-tapi-tidak-sangat-lemah? Anda akan menghentikan aliran dan menunjukkan pesan kepada pengguna yang menunjukkan bahwa kata sandi yang dimasukkan tidak terlalu kuat. Jadi punya MiddlyStrongValidatoratau sesuatu. Dan jika itu tidak benar-benar mengganggu aliran Anda, Validatorharus sudah dipanggil sebelumnya, yaitu sebelum melanjutkan aliran saat pengguna masih memasukkan kata sandi mereka (atau serupa). Tetapi kemudian validasi itu bukan bagian dari metode yang dipertanyakan di tempat pertama. :) Mungkin masalah selera ...
jr
@ jhr Dalam validator yang saya tulis, saya biasanya akan membuat AggregateException(atau yang serupa ValidationException), dan memberikan pengecualian khusus untuk setiap masalah validasi di InnerExceptions. Misalnya, bisa berupa BadPasswordException: "Kata sandi pengguna kurang dari panjang minimum 6" atau MandatoryFieldMissingException: "Nama depan harus disediakan untuk pengguna" dll. Ini tidak setara dengan kode kesalahan. Semua pesan ini dapat ditampilkan kepada pengguna dengan cara yang akan mereka pahami, dan jika NullReferenceExceptiondilempar sebagai gantinya, maka kami mendapat bug.
Omer Iqbal
4

Ada kasus penggunaan adalah kode kesalahan lebih disukai daripada pengecualian.

Jika kode Anda dapat dilanjutkan meskipun ada kesalahan, tetapi perlu dilaporkan, maka pengecualian adalah pilihan yang buruk karena pengecualian menghentikan alur. Misalnya, jika Anda membaca dalam file data dan menemukan itu berisi beberapa data buruk non-terminal, mungkin lebih baik untuk membaca di sisa file dan melaporkan kesalahan daripada gagal sekaligus.

Jawaban lain telah mencakup mengapa pengecualian harus lebih disukai daripada kode kesalahan secara umum.

Jack Aidley
sumber
Jika peringatan perlu dicatat atau semacamnya, biarlah. Tetapi jika kesalahan "perlu dilaporkan", yang saya asumsikan maksud Anda melaporkan kepada pengguna, tidak ada cara untuk menjamin bahwa kode di sekitarnya akan membaca kode pengembalian Anda dan benar-benar melaporkannya.
jhr
Tidak, maksud saya melaporkannya ke penelepon. Adalah tanggung jawab penelepon untuk memutuskan apakah pengguna perlu tahu tentang kesalahan atau tidak, seperti halnya dengan pengecualian.
Jack Aidley
1
@ jhr: Kapan ada jaminan apa pun? Kontrak untuk suatu kelas dapat menetapkan bahwa klien memiliki tanggung jawab tertentu; jika klien mematuhi kontrak, mereka akan melakukan hal-hal yang disyaratkan kontrak. Jika tidak, konsekuensi apa pun akan menjadi kesalahan kode klien. Jika seseorang ingin berjaga-jaga terhadap kesalahan tak disengaja oleh klien dan memiliki kendali atas jenis yang dikembalikan oleh metode serialisasi, orang bisa saja memasukkan bendera "kemungkinan korupsi yang tidak diakui" dan tidak mengizinkan klien membaca data darinya tanpa memanggil AcknowledgePossibleCorruptionmetode. .
supercat
1
... tetapi memiliki kelas objek untuk menyimpan informasi tentang masalah mungkin lebih bermanfaat daripada melemparkan pengecualian atau mengembalikan kode kesalahan lulus-gagal. Terserah pada aplikasi untuk menggunakan informasi itu dengan cara yang sesuai (misalnya ketika memuat file "Foo", beri tahu pengguna bahwa data mungkin tidak dapat diandalkan, dan meminta pengguna untuk memilih nama baru saat menyimpan).
supercat
1
Ada satu jaminan jika menggunakan pengecualian: Jika Anda tidak menangkapnya, mereka melempar lebih tinggi - terburuk hingga UI. Tidak ada jaminan seperti itu jika Anda menggunakan kode pengembalian yang tidak seorang pun membaca. Tentu, ikuti API jika Anda ingin menggunakannya. Saya setuju! Tapi ada ruang untuk kesalahan, sayangnya ...
JHR
2

Jelas tidak ada yang salah dengan tidak menggunakan pengecualian ketika pengecualian tidak cocok.

Ketika eksekusi kode tidak boleh terganggu (misalnya bertindak atas input pengguna yang mungkin mengandung banyak kesalahan, seperti program untuk dikompilasi atau formulir untuk diproses), saya menemukan bahwa mengumpulkan kesalahan dalam variabel kesalahan seperti has_errorsdan error_messagesmemang desain yang jauh lebih elegan daripada melempar pengecualian pada kesalahan pertama. Memungkinkan untuk menemukan semua kesalahan dalam input pengguna tanpa memaksa pengguna untuk mengirim ulang yang tidak perlu.

ikh
sumber
Mengambil pertanyaan yang menarik. Saya pikir masalah dengan pertanyaan saya, dan pemahaman saya, adalah terminologi yang tidak jelas. Apa yang Anda gambarkan tidak luar biasa, tetapi ini adalah kesalahan. Apa yang harus kita sebut itu?
Mikayla Maki
1

Dalam beberapa bahasa pemrograman dinamis Anda dapat menggunakan nilai kesalahan dan penanganan pengecualian . Hal ini dilakukan dengan mengembalikan objek pengecualian yang tidak dilemparkan ke tempat nilai pengembalian biasa, yang dapat diperiksa seperti nilai kesalahan, tetapi itu melempar pengecualian jika tidak dicentang.

Dalam Perl 6 hal ini dilakukan melalui fail, yang jika dalam no fatal;lingkup mengembalikan Failureobjek pengecualian khusus yang tidak dilewatkan .

Dalam Perl 5 Anda dapat menggunakan Kontekstual :: Pengembalian Anda dapat melakukannya dengan return FAIL.

Jakub Narębski
sumber
-1

Kecuali ada sesuatu yang sangat spesifik, saya pikir memiliki variabel kesalahan untuk validasi adalah ide yang buruk. Tujuannya tampaknya tentang menghemat waktu yang dihabiskan untuk validasi (Anda hanya dapat mengembalikan nilai variabel)

Tetapi jika Anda mengubah sesuatu, Anda harus menghitung ulang nilai itu. Saya tidak bisa mengatakan lebih banyak tentang berhenti dan melempar Exception.

EDIT: Saya tidak menyadari ini adalah masalah paradigma perangkat lunak, bukan kasus tertentu.

Izinkan saya mengklarifikasi poin-poin saya lebih lanjut dalam kasus khusus saya di mana jawaban saya masuk akal

  1. Saya memiliki koleksi objek entitas
  2. Saya memiliki layanan web gaya prosedural yang berfungsi dengan objek entitas ini

Ada dua jenis kesalahan:

  1. Kesalahan saat pemrosesan terjadi di lapisan layanan
  2. Kesalahan karena ada ketidakkonsistenan dalam objek entitas

Di lapisan layanan, tidak ada pilihan selain menggunakan objek Hasil sebagai pembungkus yang merupakan kesetaraan variabel kesalahan. Mensimulasikan pengecualian melalui panggilan layanan pada protokol seperti http dimungkinkan, tetapi jelas bukan hal yang baik untuk dilakukan. Saya tidak berbicara tentang jenis kesalahan ini dan tidak berpikir ini adalah jenis kesalahan yang ditanyakan dalam pertanyaan ini.

Saya sedang berpikir tentang jenis kesalahan kedua. Dan jawaban saya adalah tentang jenis kesalahan kedua ini. Dalam objek entitas, ada pilihan untuk kita, beberapa di antaranya

  • menggunakan variabel validasi
  • lempar pengecualian segera saat bidang diatur secara tidak benar dari penyetel

Menggunakan variabel validasi sama dengan memiliki metode validasi tunggal untuk setiap objek entitas. Secara khusus, pengguna dapat mengatur nilai dengan cara yang menjaga setter sebagai setter murni, tidak ada efek samping (ini sering merupakan praktik yang baik) atau seseorang dapat memasukkan validasi ke dalam setiap setter dan kemudian menyimpan hasilnya ke dalam variabel validasi. Keuntungan dari ini adalah untuk menghemat waktu, hasil validasi di-cache ke dalam variabel validasi sehingga ketika pengguna memanggil validasi () beberapa kali, tidak perlu melakukan beberapa validasi.

Hal terbaik untuk dilakukan dalam hal ini adalah menggunakan metode validasi tunggal tanpa menggunakan validasi apa pun untuk kesalahan validasi cache. Ini membantu menjaga setter sebagai setter saja.

InformedA
sumber
Saya mengerti apa yang Anda bicarakan. Pendekatan yang menarik. Saya senang itu bagian dari set jawaban.
Mikayla Maki