Kasus terhadap pengecualian yang diperiksa

456

Selama beberapa tahun sekarang saya tidak bisa mendapatkan jawaban yang layak untuk pertanyaan berikut: mengapa beberapa pengembang menentang pengecualian yang diperiksa? Saya telah melakukan banyak percakapan, membaca berbagai hal di blog, membaca apa yang dikatakan Bruce Eckel (orang pertama yang saya lihat berbicara menentang mereka).

Saat ini saya sedang menulis beberapa kode baru dan sangat memperhatikan bagaimana saya menangani pengecualian. Saya mencoba melihat sudut pandang kerumunan "kami tidak suka memeriksa pengecualian" dan saya masih tidak bisa melihatnya.

Setiap percakapan yang saya akhiri dengan pertanyaan yang sama tidak terjawab ... izinkan saya mengaturnya:

Secara umum (dari bagaimana Java dirancang),

  • Error adalah untuk hal-hal yang tidak boleh ditangkap (VM ​​memiliki alergi kacang dan seseorang menjatuhkan toples kacang di atasnya)
  • RuntimeException adalah untuk hal-hal yang tidak dilakukan oleh programmer (programmer berjalan di akhir array)
  • Exception(Kecuali RuntimeException) untuk hal-hal yang di luar kendali programmer (disk terisi saat menulis ke sistem file, batas pegangan file untuk proses telah tercapai dan Anda tidak dapat membuka file lagi)
  • Throwable hanyalah induk dari semua jenis pengecualian.

Argumen umum yang saya dengar adalah bahwa jika pengecualian terjadi maka semua pengembang akan lakukan adalah keluar dari program.

Argumen umum lain yang saya dengar adalah bahwa pengecualian yang diperiksa membuatnya lebih sulit untuk memperbaiki kode.

Untuk argumen "yang akan saya lakukan adalah keluar", saya katakan bahwa meskipun Anda keluar, Anda perlu menampilkan pesan kesalahan yang masuk akal. Jika Anda hanya mengayuh pada penanganan kesalahan maka pengguna Anda tidak akan terlalu senang ketika program keluar tanpa indikasi yang jelas mengapa.

Untuk kerumunan "itu membuatnya sulit untuk refactor", itu menunjukkan bahwa tingkat abstraksi yang tepat tidak dipilih. Alih-alih mendeklarasikan metode melempar IOException, IOExceptionharus diubah menjadi pengecualian yang lebih cocok untuk apa yang sedang terjadi.

Saya tidak memiliki masalah dengan membungkus Main dengan catch(Exception)(atau dalam beberapa kasus catch(Throwable)untuk memastikan bahwa program dapat keluar dengan anggun - tapi saya selalu menangkap pengecualian khusus yang saya butuhkan. Melakukan itu memungkinkan saya untuk, setidaknya, menampilkan yang sesuai pesan eror.

Pertanyaan yang tidak pernah dibalas orang adalah ini:

Jika Anda melempar RuntimeException subclass, bukan Exception subclass, bagaimana Anda tahu apa yang seharusnya Anda tangkap?

Jika jawabannya adalah menangkap Exceptionmaka Anda juga berurusan dengan kesalahan programmer dengan cara yang sama seperti pengecualian sistem. Sepertinya itu salah bagi saya.

Jika Anda menangkap Throwablemaka Anda memperlakukan pengecualian sistem dan kesalahan VM (dan sejenisnya) dengan cara yang sama. Sepertinya itu salah bagi saya.

Jika jawabannya adalah Anda hanya menangkap pengecualian yang Anda tahu dilemparkan maka bagaimana Anda tahu yang dilemparkan? Apa yang terjadi ketika programmer X melempar pengecualian baru dan lupa untuk menangkapnya? Sepertinya itu sangat berbahaya bagi saya.

Saya akan mengatakan bahwa program yang menampilkan jejak stack salah. Apakah orang yang tidak suka memeriksa pengecualian tidak merasa seperti itu?

Jadi, jika Anda tidak menyukai pengecualian yang dicentang, dapatkah Anda menjelaskan mengapa tidak DAN menjawab pertanyaan yang tidak dijawab?

Sunting: Saya tidak mencari saran kapan harus menggunakan model mana pun, apa yang saya cari adalah mengapa orang memperluas dari RuntimeExceptionkarena mereka tidak suka memperpanjang dari Exceptiondan / atau mengapa mereka menangkap pengecualian dan kemudian menyusun kembali RuntimeExceptionalih - alih menambahkan lemparan ke metode mereka. Saya ingin memahami motivasi untuk tidak menyukai pengecualian yang diperiksa.

TofuBeer
sumber
46
Saya tidak berpikir itu sepenuhnya subjektif - ini adalah fitur bahasa yang dirancang untuk memiliki penggunaan spesifik, daripada untuk semua orang untuk memutuskan apa itu untuk diri mereka sendiri. Dan itu tidak terlalu argumentatif, ini membahas bantahan khusus di muka yang orang bisa dengan mudah datang dengan.
Gareth
6
Ayolah. Dilihat sebagai fitur bahasa, topik ini telah dan dapat didekati dengan cara yang objektif.
Kurt Schelfthout
6
@cletus "menjawab pertanyaan Anda sendiri" jika saya memiliki jawaban saya tidak akan mengajukan pertanyaan!
TofuBeer
8
Pertanyaan yang bagus Di C ++ tidak ada pengecualian yang diperiksa sama sekali, dan menurut saya itu membuat fitur pengecualian tidak dapat digunakan. Anda berakhir dalam situasi di mana Anda harus menangkap setiap panggilan fungsi yang Anda buat, karena Anda tidak tahu apakah itu bisa melempar sesuatu.
Dimitri C.
4
Argumen terkuat yang saya tahu untuk pengecualian diperiksa adalah bahwa mereka tidak asli ada di Jawa, dan ketika mereka diperkenalkan mereka menemukan ratusan bug di JDK. Ini agak sebelum Java 1.0. Saya pribadi tidak akan tanpa mereka, dan tidak setuju dengan Bruce Eckel dan orang lain dalam hal ini.
Marquis of Lorne

Jawaban:

277

Saya pikir saya membaca wawancara Bruce Eckel yang sama dengan yang Anda lakukan - dan itu selalu mengganggu saya. Bahkan, argumen dibuat oleh orang yang diwawancarai (jika ini memang posting yang sedang Anda bicarakan) Anders Hejlsberg, jenius MS di belakang .NET dan C #.

http://www.artima.com/intv/handcuffs.html

Meskipun saya penggemar Hejlsberg dan karyanya, argumen ini selalu mengejutkan saya. Pada dasarnya bermuara pada:

"Pengecualian yang diperiksa buruk karena programmer hanya menyalahgunakannya dengan selalu menangkapnya dan mengabaikannya yang menyebabkan masalah disembunyikan dan diabaikan yang kalau tidak akan disajikan kepada pengguna".

Dengan "jika tidak disajikan kepada pengguna" maksud saya jika Anda menggunakan pengecualian runtime programmer malas hanya akan mengabaikannya (dibandingkan menangkapnya dengan blok tangkapan kosong) dan pengguna akan melihatnya.

Ringkasan dari argumen adalah bahwa "Programmer tidak akan menggunakannya dengan benar dan tidak menggunakannya dengan benar lebih buruk daripada tidak memilikinya" .

Ada beberapa kebenaran dalam argumen ini dan pada kenyataannya, saya menduga motivasi Gosling untuk tidak menempatkan operator override di Jawa berasal dari argumen serupa - mereka membingungkan programmer karena mereka sering disalahgunakan.

Tetapi pada akhirnya, saya menemukan argumen palsu Hejlsberg dan mungkin post-hoc yang dibuat untuk menjelaskan kekurangan dan bukan keputusan yang dipikirkan dengan matang.

Saya berpendapat bahwa sementara terlalu banyak menggunakan pengecualian diperiksa adalah hal yang buruk dan cenderung mengarah pada penanganan yang ceroboh oleh pengguna, tetapi penggunaan yang tepat dari mereka memungkinkan programmer API untuk memberikan manfaat besar bagi programmer klien API.

Sekarang programmer API harus berhati-hati untuk tidak membuang pengecualian yang diperiksa di semua tempat, atau mereka hanya akan mengganggu programmer klien. Programmer klien yang sangat malas akan berusaha untuk menangkap (Exception) {}ketika Hejlsberg memperingatkan dan semua manfaat akan hilang dan neraka akan terjadi. Tetapi dalam beberapa keadaan, tidak ada pengganti untuk pengecualian yang diperiksa.

Bagi saya, contoh klasik adalah API buka file. Setiap bahasa pemrograman dalam sejarah bahasa (setidaknya pada sistem file) memiliki API di suatu tempat yang memungkinkan Anda membuka file. Dan setiap programmer klien yang menggunakan API ini tahu bahwa mereka harus berurusan dengan kasus bahwa file yang mereka coba buka tidak ada. Biarkan saya ulangi bahwa: Setiap programmer klien yang menggunakan API ini harus tahu bahwa mereka harus berurusan dengan kasus ini. Dan ada intinya: dapatkah programmer API membantu mereka tahu bahwa mereka harus menghadapinya melalui komentar saja atau dapatkah mereka memaksa klien untuk menghadapinya.

Dalam C idiomnya seperti

  if (f = fopen("goodluckfindingthisfile")) { ... } 
  else { // file not found ...

di mana fopenmenunjukkan kegagalan dengan mengembalikan 0 dan C (bodoh) memungkinkan Anda memperlakukan 0 sebagai boolean dan ... Pada dasarnya, Anda mempelajari idiom ini dan Anda baik-baik saja. Tetapi bagaimana jika Anda seorang noob dan Anda tidak belajar idiom itu. Kemudian, tentu saja, Anda mulai dengan

   f = fopen("goodluckfindingthisfile");
   f.read(); // BANG! 

dan belajar dengan cara yang sulit.

Perhatikan bahwa kita hanya berbicara tentang bahasa yang sangat diketik di sini: Ada gagasan yang jelas tentang apa itu API dalam bahasa yang sangat diketik: Ini adalah smorgasbord fungsionalitas (metode) untuk Anda gunakan dengan protokol yang jelas untuk masing-masing bahasa.

Protokol yang didefinisikan dengan jelas biasanya ditentukan oleh metode tanda tangan. Di sini fopen mengharuskan Anda memberikannya string (atau char * dalam kasus C). Jika Anda memberikannya sesuatu yang lain, Anda mendapatkan kesalahan waktu kompilasi. Anda tidak mengikuti protokol - Anda tidak menggunakan API dengan benar.

Dalam beberapa bahasa (tidak jelas) jenis kembali juga merupakan bagian dari protokol. Jika Anda mencoba memanggil yang setara fopen()dalam beberapa bahasa tanpa menugaskannya ke variabel Anda juga akan mendapatkan kesalahan waktu kompilasi (Anda hanya dapat melakukannya dengan fungsi yang tidak berlaku).

Poin yang saya coba sampaikan adalah: Dalam bahasa yang diketik secara statis, programmer API mendorong klien untuk menggunakan API dengan benar dengan mencegah kode klien mereka dari kompilasi jika itu membuat kesalahan yang jelas.

(Dalam bahasa yang diketik secara dinamis, seperti Ruby, Anda dapat melewatkan apa pun, ucapkan float, sebagai nama file - dan itu akan dikompilasi. Mengapa merepotkan pengguna dengan pengecualian yang diperiksa jika Anda bahkan tidak akan mengontrol argumen metode. argumen yang dibuat di sini hanya berlaku untuk bahasa yang diketik secara statis.)

Jadi, bagaimana dengan pengecualian yang diperiksa?

Nah, inilah salah satu API Java yang dapat Anda gunakan untuk membuka file.

try {
  f = new FileInputStream("goodluckfindingthisfile");
}
catch (FileNotFoundException e) {
  // deal with it. No really, deal with it!
  ... // this is me dealing with it
}

Lihat tangkapan itu? Inilah tanda tangan untuk metode API itu:

public FileInputStream(String name)
                throws FileNotFoundException

Perhatikan bahwa FileNotFoundExceptionini adalah pengecualian yang dicentang .

Programmer API mengatakan ini kepada Anda: "Anda dapat menggunakan konstruktor ini untuk membuat FileInputStream baru tetapi Anda

a) harus memasukkan nama file sebagai String
b) harus menerima kemungkinan bahwa file tersebut mungkin tidak ditemukan saat runtime "

Dan itulah intinya sejauh yang saya ketahui.

Kuncinya pada dasarnya adalah pertanyaan apa yang dinyatakan sebagai "Hal-hal yang di luar kendali programmer". Pikiran pertama saya adalah bahwa dia memiliki arti yang di luar kendali programmer API . Namun pada kenyataannya, pengecualian yang diperiksa bila digunakan dengan benar harus benar-benar untuk hal-hal yang berada di luar kendali programmer klien dan programmer API. Saya pikir ini adalah kunci untuk tidak menyalahgunakan pengecualian yang diperiksa.

Saya pikir file-terbuka menggambarkan hal itu dengan baik. Programmer API tahu Anda mungkin memberi mereka nama file yang ternyata tidak ada pada saat API dipanggil, dan bahwa mereka tidak akan dapat mengembalikan apa yang Anda inginkan, tetapi harus memberikan pengecualian. Mereka juga tahu bahwa ini akan terjadi secara teratur dan bahwa pemrogram klien mungkin mengharapkan nama file menjadi benar pada saat mereka menulis panggilan, tetapi mungkin salah saat runtime karena alasan di luar kendali mereka juga.

Jadi API membuatnya eksplisit: Akan ada kasus-kasus di mana file ini tidak ada pada saat Anda menelepon saya dan Anda sebaiknya berurusan dengan itu.

Ini akan lebih jelas dengan kasus-counter. Bayangkan saya sedang menulis API tabel. Saya memiliki model tabel di suatu tempat dengan API termasuk metode ini:

public RowData getRowData(int row) 

Sekarang sebagai programmer API saya tahu akan ada kasus-kasus di mana beberapa klien memberikan nilai negatif untuk baris atau nilai baris di luar tabel. Jadi saya mungkin tergoda untuk melempar pengecualian yang dicek dan memaksa klien untuk menanganinya:

public RowData getRowData(int row) throws CheckedInvalidRowNumberException

(Tentu saja saya tidak akan menyebutnya "Diperiksa".)

Ini adalah penggunaan buruk dari pengecualian yang diperiksa. Kode klien akan penuh dengan panggilan untuk mengambil data baris, yang semuanya harus menggunakan coba / tangkap, dan untuk apa? Apakah mereka akan melaporkan kepada pengguna bahwa baris yang salah dicari? Mungkin tidak - karena apa pun UI di sekitar tampilan tabel saya, tidak boleh membiarkan pengguna masuk ke keadaan di mana baris ilegal diminta. Jadi itu adalah bug pada bagian dari programmer klien.

Programmer API masih dapat memprediksi bahwa klien akan mengkodekan bug tersebut dan harus menanganinya dengan pengecualian runtime seperti IllegalArgumentException.

Dengan pengecualian yang dicek dalam getRowData, ini jelas merupakan kasus yang akan mengarah pada programmer malas Hejlsberg hanya dengan menambahkan tangkapan kosong. Ketika itu terjadi, nilai-nilai baris ilegal tidak akan jelas bahkan untuk penguji atau debugging pengembang klien, melainkan akan menyebabkan kesalahan knock-on yang sulit untuk menentukan sumber. Roket Arianne akan meledak setelah diluncurkan.

Oke, jadi inilah masalahnya: Saya mengatakan bahwa pengecualian yang diperiksa FileNotFoundExceptionbukan hanya hal yang baik tetapi alat penting dalam kotak alat pemrogram API untuk mendefinisikan API dengan cara yang paling berguna bagi pemrogram klien. Tetapi CheckedInvalidRowNumberExceptionini adalah ketidaknyamanan besar, yang mengarah ke pemrograman yang buruk dan harus dihindari. Tapi bagaimana cara membedakannya.

Saya kira itu bukan ilmu pasti dan saya kira itu mendasari dan mungkin membenarkan argumen Hejlsberg sampai batas tertentu. Tapi saya tidak senang membuang bayi keluar dengan air mandi di sini, jadi izinkan saya untuk mengekstrak beberapa aturan di sini untuk membedakan pengecualian yang diperiksa dengan yang buruk:

  1. Di luar kendali klien atau Ditutup vs Terbuka:

    Pengecualian yang dicentang hanya boleh digunakan di mana kasus kesalahan tidak terkendali baik API dan pemrogram klien. Ini ada hubungannya dengan seberapa terbuka atau tertutupnya sistem. Dalam UI terbatas di mana programmer klien memiliki kontrol, katakanlah, atas semua tombol, perintah keyboard dll yang menambah dan menghapus baris dari tampilan tabel (sistem tertutup), itu adalah bug pemrograman klien jika mencoba mengambil data dari baris tidak ada. Dalam sistem operasi berbasis file di mana sejumlah pengguna / aplikasi dapat menambah dan menghapus file (sistem terbuka), dapat dibayangkan bahwa file yang diminta oleh klien telah dihapus tanpa sepengetahuan mereka sehingga mereka diharapkan untuk menanganinya .

  2. Ubiquity:

    Pengecualian yang dicentang tidak boleh digunakan pada panggilan API yang sering dilakukan oleh klien. Maksud saya dari banyak tempat dalam kode klien - tidak sering tepat waktu. Jadi kode klien tidak cenderung mencoba membuka file yang sama banyak, tetapi tampilan tabel saya mendapatkan RowDatasemua tempat dari metode yang berbeda. Secara khusus, saya akan menulis banyak kode seperti

    if (model.getRowData().getCell(0).isEmpty())

    dan akan menyakitkan jika harus mencoba / menangkap setiap waktu.

  3. Memberitahu Pengguna:

    Pengecualian yang diperiksa harus digunakan dalam kasus-kasus di mana Anda dapat membayangkan pesan kesalahan yang bermanfaat disajikan kepada pengguna akhir. Ini adalah "dan apa yang akan Anda lakukan ketika itu terjadi?" pertanyaan yang saya ajukan di atas. Ini juga terkait dengan item 1. Karena Anda dapat memprediksi bahwa sesuatu di luar sistem API klien-Anda mungkin menyebabkan file tidak ada di sana, Anda dapat memberi tahu pengguna tentang hal itu:

    "Error: could not find the file 'goodluckfindingthisfile'"

    Karena nomor baris ilegal Anda disebabkan oleh bug internal dan bukan karena kesalahan pengguna, sebenarnya tidak ada informasi berguna yang dapat Anda berikan kepada mereka. Jika aplikasi Anda tidak membiarkan pengecualian runtime masuk ke konsol, mungkin akan berakhir memberi mereka beberapa pesan buruk seperti:

    "Internal error occured: IllegalArgumentException in ...."

    Singkatnya, jika Anda tidak berpikir programmer klien Anda dapat menjelaskan pengecualian Anda dengan cara yang membantu pengguna, maka Anda mungkin tidak boleh menggunakan pengecualian yang diperiksa.

Jadi itu aturan saya. Agak dibuat-buat, dan pasti akan ada pengecualian (tolong bantu saya sempurnakan jika Anda mau). Tapi argumen utama saya adalah bahwa ada kasus-kasus seperti di FileNotFoundExceptionmana pengecualian yang diperiksa sama pentingnya dan berguna bagian dari kontrak API sebagai jenis parameter. Jadi kita tidak boleh membuangnya hanya karena disalahgunakan.

Maaf, bukan berarti membuat ini begitu lama dan wafel. Biarkan saya selesai dengan dua saran:

A: Pemrogram API: gunakan pengecualian yang diperiksa untuk menghemat kegunaannya. Jika ragu, gunakan pengecualian yang tidak dicentang.

B: Programer klien: biasakan membuat pengecualian terbungkus (google it) sejak awal dalam pengembangan Anda. JDK 1.4 dan yang lebih baru menyediakan konstruktor RuntimeExceptionuntuk ini, tetapi Anda dapat dengan mudah membuatnya sendiri. Inilah konstruktornya:

public RuntimeException(Throwable cause)

Kemudian biasakan setiap kali Anda harus menangani pengecualian yang diperiksa dan Anda merasa malas (atau Anda pikir programmer API terlalu bersemangat dalam menggunakan pengecualian yang diperiksa di tempat pertama), jangan hanya menelan pengecualian, bungkus saja dan rethrow itu.

try {
  overzealousAPI(thisArgumentWontWork);
}
catch (OverzealousCheckedException exception) {
  throw new RuntimeException(exception);  
}

Masukkan ini ke dalam salah satu templat kode kecil IDE Anda dan gunakan saat Anda merasa malas. Dengan cara ini jika Anda benar-benar perlu menangani pengecualian yang dicentang Anda akan dipaksa untuk kembali dan menanganinya setelah melihat masalah saat runtime. Karena, percayalah pada saya (dan Anders Hejlsberg), Anda tidak akan pernah kembali ke TODO itu dalam diri Anda

catch (Exception e) { /* TODO deal with this at some point (yeah right) */}
Perkelahian
sumber
110
Membuka file sebenarnya adalah contoh yang bagus untuk pengecualian yang benar-benar kontraproduktif. Karena sebagian besar waktu kode yang membuka file TIDAK dapat melakukan sesuatu yang berguna tentang pengecualian - itu lebih baik dilakukan beberapa lapis tumpukan panggilan. Pengecualian yang dicentang memaksa Anda untuk mengacaukan tanda tangan metode Anda agar Anda dapat melakukan apa yang seharusnya Anda lakukan - menangani pengecualian di tempat yang paling masuk akal untuk melakukannya.
Michael Borgwardt
116
@Michael: Setuju Anda biasanya menangani pengecualian IO ini beberapa tingkat lebih tinggi - tapi saya tidak mendengar Anda menyangkal bahwa sebagai klien API Anda harus mengantisipasinya. Untuk alasan ini, saya pikir pengecualian yang diperiksa sudah sesuai. Ya, Anda harus menyatakan "melempar" pada setiap metode panggilan stack, tapi saya tidak setuju bahwa ini berantakan. Ini informasi deklaratif yang berguna dalam tanda tangan metode Anda. Maksud Anda: metode tingkat rendah saya mungkin mengalami file yang hilang, tetapi mereka akan menyerahkannya kepada Anda. Saya melihat tidak ada ruginya dan hanya bagus, kode bersih untuk mendapatkan
Rhubarb
23
@Rhubarb: +1, jawaban yang sangat menarik, menunjukkan argumen untuk kedua belah pihak. Bagus. Tentang komentar terakhir Anda: perhatikan bahwa mendeklarasikan lemparan pada setiap metode ke atas tumpukan panggilan mungkin tidak selalu mungkin, terutama jika Anda menerapkan antarmuka di sepanjang jalan.
R. Martinho Fernandes
8
Ini adalah argumen yang sangat kuat (dan saya setuju dengan itu secara umum), tetapi ada kasus di mana itu tidak berhasil: panggilan balik umum. Jika saya memiliki beberapa perpustakaan memanggil beberapa kode yang ditentukan pengguna, tidak ada cara (yang saya tahu, di java) untuk perpustakaan itu untuk menyebarkan pengecualian yang diperiksa dari kode pengguna yang memanggil ke kode pengguna yang memanggilnya. Masalah ini juga meluas ke bagian lain dari sistem jenis banyak bahasa yang diketik secara statis yang tidak mendukung jenis generik yang tepat. Jawaban ini akan ditingkatkan dengan menyebutkan hal ini (dan mungkin tanggapan).
Mankarse
7
@Rhubarb salah. Pengecualian yang diperiksa dimaksudkan untuk 'kemungkinan' yang dapat & harus ditangani, tetapi selalu tidak sesuai dengan praktik terbaik "melempar lebih awal, terlambat". Membuka file adalah contoh yang dipilih dengan ceri, yang dapat ditangani. Kebanyakan IOException (misalnya menulis byte), SQLException, RemoteException tidak mungkin ditangani dengan cara yang bermakna. Kegagalan atau coba lagi harus di tingkat "bisnis" atau "permintaan", dan pengecualian yang diperiksa - seperti yang digunakan di perpustakaan Java - adalah kesalahan yang menyulitkan. literatejava.com/exceptions/…
Thomas W
184

Hal tentang pengecualian diperiksa adalah bahwa mereka tidak benar-benar pengecualian oleh pemahaman konsep yang biasa. Sebaliknya, mereka adalah nilai pengembalian alternatif API.

Seluruh gagasan pengecualian adalah bahwa kesalahan yang dilemparkan ke suatu tempat di sepanjang rantai panggilan dapat muncul dan ditangani oleh kode di suatu tempat lebih jauh, tanpa kode intervensi harus khawatir tentang hal itu. Pengecualian yang diperiksa, di sisi lain, mengharuskan setiap tingkat kode antara pelempar dan penangkap untuk menyatakan bahwa mereka tahu tentang semua bentuk pengecualian yang dapat melewati mereka. Ini benar-benar sedikit berbeda dalam praktiknya jika pengecualian yang dicentang hanyalah nilai pengembalian khusus yang harus diperiksa oleh penelepon. mis. [kodesemu]:

public [int or IOException] writeToStream(OutputStream stream) {
    [void or IOException] a= stream.write(mybytes);
    if (a instanceof IOException)
        return a;
    return mybytes.length;
}

Karena Java tidak dapat melakukan nilai pengembalian alternatif, atau tupel inline sederhana sebagai nilai balik, pengecualian yang diperiksa adalah respons yang wajar.

Masalahnya adalah bahwa banyak kode, termasuk petak besar dari pustaka standar, penyalahgunaan mengecek pengecualian untuk kondisi luar biasa nyata yang mungkin ingin Anda ikuti beberapa level. Mengapa IOException bukan RuntimeException? Dalam setiap bahasa lain saya dapat membiarkan pengecualian IO terjadi, dan jika saya tidak melakukan apa-apa untuk mengatasinya, aplikasi saya akan berhenti dan saya akan mendapatkan jejak stack yang berguna untuk dilihat. Ini adalah hal terbaik yang bisa terjadi.

Mungkin dua metode dari contoh Anda ingin menangkap semua IOExeception dari seluruh proses penulisan-untuk-streaming, batalkan proses dan lompat ke kode pelaporan kesalahan; di Java Anda tidak dapat melakukannya tanpa menambahkan 'throws IOException' di setiap level panggilan, bahkan level yang mereka sendiri tidak lakukan IO. Metode seperti itu seharusnya tidak perlu tahu tentang penanganan pengecualian; harus menambahkan pengecualian pada tanda tangan mereka:

  1. meningkatkan kopling yang tidak perlu;
  2. membuat tanda tangan antarmuka sangat rapuh untuk diubah;
  3. membuat kode kurang mudah dibaca;
  4. sangat menjengkelkan sehingga reaksi programmer umumnya adalah mengalahkan sistem dengan melakukan sesuatu yang mengerikan seperti 'throws Exception', 'catch (Exception e) {}', atau membungkus semuanya dalam RuntimeException (yang membuat debugging lebih sulit).

Dan kemudian ada banyak pengecualian perpustakaan yang konyol seperti:

try {
    httpconn.setRequestMethod("POST");
} catch (ProtocolException e) {
    throw new CanNeverHappenException("oh dear!");
}

Ketika Anda harus mengacaukan kode Anda dengan crud menggelikan seperti ini, tidak heran jika pengecualian menerima banyak kebencian, meskipun sebenarnya ini hanya desain API sederhana yang buruk.

Efek buruk tertentu lainnya adalah pada Inversion of Control, di mana komponen A memasok panggilan balik ke komponen generik B. Komponen A ingin dapat membiarkan pengecualian melempar dari panggilan baliknya kembali ke tempat di mana ia disebut komponen B, tetapi tidak dapat karena itu akan mengubah antarmuka panggilan balik yang diperbaiki oleh B. A hanya dapat melakukannya dengan membungkus pengecualian nyata dalam RuntimeException, yang merupakan penanganan boilerplate yang lebih pengecualian untuk menulis.

Memeriksa pengecualian seperti yang diterapkan di Jawa dan pustaka standarnya berarti boilerplate, boilerplate, boilerplate. Dalam bahasa yang sudah lisan ini bukan menang.

bobince
sumber
14
Dalam contoh kode Anda, akan lebih baik untuk rantai pengecualian sehingga penyebab asli dapat ditemukan saat membaca log: throw CanNeverHappenException (e);
Esko Luontola
5
Saya tidak setuju. Pengecualian, dicentang atau tidak, adalah kondisi luar biasa. Contoh: metode yang mengambil objek melalui HTTP. Nilai pengembaliannya adalah objek atau tidak sama sekali, semua hal yang bisa menjadi buruk luar biasa. Memperlakukan mereka sebagai nilai pengembalian seperti yang dilakukan dalam C hanya mengarah pada kebingungan dan desain yang buruk.
Tuan Smith
15
@ Pak: Apa yang saya katakan adalah bahwa pengecualian yang diperiksa seperti yang diterapkan di Java berperilaku, dalam praktiknya, lebih seperti mengembalikan nilai seperti dalam C daripada melakukan 'pengecualian' tradisional yang mungkin kita kenali dari C ++ dan bahasa pra-Jawa lainnya. Dan IMO ini memang menyebabkan kebingungan dan desain yang buruk.
bobince
9
Setuju bahwa perpustakaan standar menyalahgunakan pengecualian yang diperiksa pasti menambah kebingungan dan perilaku penangkapan yang buruk. Dan, seringkali itu hanya dari dokumentasi yang buruk, misalnya metode merobohkan seperti putuskan () yang melempar IOException ketika "beberapa kesalahan I / O lainnya terjadi". Yah, saya putuskan! Apakah saya membocorkan pegangan atau sumber daya lainnya? Apakah saya perlu mencoba lagi? Tanpa mengetahui mengapa itu terjadi, saya tidak dapat mengambil tindakan yang harus saya ambil dan jadi saya harus menebak apakah saya harus menelannya, mencoba kembali, atau membayar jaminan.
charstar
14
+1 untuk "Nilai pengembalian alternatif API". Cara menarik untuk melihat pengecualian yang diperiksa.
Zsolt Török
75

Daripada mengulangi semua (banyak) alasan terhadap pengecualian yang diperiksa, saya akan memilih satu saja. Saya telah kehilangan hitungan berapa kali saya menulis blok kode ini:

try {
  // do stuff
} catch (AnnoyingcheckedException e) {
  throw new RuntimeException(e);
}

99% dari waktu saya tidak bisa berbuat apa-apa. Akhirnya, blok melakukan pembersihan yang diperlukan (atau setidaknya harus dilakukan).

Saya juga kehilangan hitungan berapa kali saya melihat ini:

try {
  // do stuff
} catch (AnnoyingCheckedException e) {
  // do nothing
}

Mengapa? Karena seseorang harus menghadapinya dan malas. Apakah itu salah? Tentu. Apakah itu terjadi Benar. Bagaimana jika ini merupakan pengecualian yang tidak dicentang? Aplikasi ini baru saja mati (yang lebih baik menelan pengecualian).

Dan kemudian kita memiliki kode menyebalkan yang menggunakan pengecualian sebagai bentuk kontrol aliran, seperti java.text.Format . Bzzzt. Salah. Pengguna yang memasukkan "abc" ke dalam bidang angka di formulir bukan pengecualian.

Ok, saya kira itu tiga alasan.

cletus
sumber
4
Tetapi jika pengecualian ditangkap dengan benar, Anda dapat memberi tahu pengguna, melakukan tugas-tugas lain (log?) Dan keluar dari aplikasi dengan cara yang terkontrol. Saya setuju bahwa beberapa bagian API bisa dirancang lebih baik. Dan untuk alasan programmer yang malas, saya pikir sebagai programmer Anda 100% bertanggung jawab atas kode Anda.
Mister Smith
3
perhatikan bahwa try-catch-rethrow memungkinkan Anda menentukan pesan - Saya biasanya menggunakannya untuk menambahkan informasi tentang isi variabel keadaan. Contoh yang sering adalah untuk IOExceptions untuk menambahkan absolutePathName () dari file yang bersangkutan.
Thorbjørn Ravn Andersen
15
Saya pikir IDE seperti Eclipse memiliki banyak kesalahan untuk berapa kali Anda telah melihat blok tangkap kosong. Sungguh, mereka harus rethrow secara default.
artbristol
12
"99% dari waktu saya tidak bisa berbuat apa-apa" - salah, Anda dapat menunjukkan pesan kepada pengguna yang mengatakan "Tidak dapat terhubung ke server" atau "Perangkat IO gagal", alih-alih membiarkan aplikasi mogok karena sedikit gangguan jaringan. Kedua contoh Anda adalah seni kerja dari programmer yang buruk. Anda harus menyerang programmer yang buruk dan tidak memeriksa pengecualian itu sendiri. Ini seperti saya menyerang insulin karena tidak membantu diabetes saya ketika saya menggunakannya sebagai saus salad.
AxiomaticNexus
2
@YasmaniLlanes Anda tidak selalu dapat melakukan hal-hal ini. Terkadang Anda memiliki antarmuka untuk dipatuhi. Dan ini terutama benar ketika Anda merancang API yang dapat dirawat dengan baik karena Anda tidak bisa mulai melemparkan efek samping di semua tempat. Baik itu, maupun kerumitan yang akan diperkenalkannya, akan sangat menggigit Anda dalam skala besar. Jadi ya, 99% dari waktu, tidak ada yang bisa dilakukan tentang itu.
MasterMastic
50

Saya tahu ini adalah pertanyaan lama tapi saya sudah menghabiskan waktu bergulat dengan pengecualian yang sudah diperiksa dan saya harus menambahkan sesuatu. Maafkan saya untuk panjangnya!

Daging sapi utama saya dengan pengecualian adalah mereka merusak polimorfisme. Tidak mungkin membuat mereka bermain dengan baik dengan antarmuka polimorfik.

Ambil Listantarmuka Java yang bagus . Kami memiliki implementasi dalam memori yang umum seperti ArrayListdan LinkedList. Kami juga memiliki kelas kerangka AbstractListyang membuatnya mudah untuk merancang daftar tipe baru. Untuk daftar read-only, kita hanya perlu mengimplementasikan dua metode: size()dan get(int index).

WidgetListKelas contoh ini membaca beberapa objek ukuran tetap tipe Widget(tidak ditampilkan) dari file:

class WidgetList extends AbstractList<Widget> {
    private static final int SIZE_OF_WIDGET = 100;
    private final RandomAccessFile file;

    public WidgetList(RandomAccessFile file) {
        this.file = file;
    }

    @Override
    public int size() {
        return (int)(file.length() / SIZE_OF_WIDGET);
    }

    @Override
    public Widget get(int index) {
        file.seek((long)index * SIZE_OF_WIDGET);
        byte[] data = new byte[SIZE_OF_WIDGET];
        file.read(data);
        return new Widget(data);
    }
}

Dengan mengekspos Widget menggunakan Listantarmuka yang sudah dikenal , Anda dapat mengambil item ( list.get(123)) atau mengulangi daftar ( for (Widget w : list) ...) tanpa perlu tahu tentang WidgetListdirinya sendiri. Seseorang dapat meneruskan daftar ini ke metode standar apa saja yang menggunakan daftar generik, atau membungkusnya dalam Collections.synchronizedList. Kode yang menggunakannya tidak perlu tahu atau tidak peduli apakah "Widget" dibuat di tempat, berasal dari array, atau dibaca dari file, atau database, atau dari seluruh jaringan, atau dari relay ruang bagian masa depan. Itu masih akan berfungsi dengan benar karena Listantarmuka diimplementasikan dengan benar.

Kecuali itu tidak. Kelas di atas tidak dikompilasi karena metode akses file dapat melempar IOException, pengecualian yang diperiksa yang harus Anda "tangkap atau tentukan". Anda tidak dapat menetapkannya sebagai dilemparkan - kompiler tidak akan membiarkan Anda karena itu akan melanggar kontrak Listantarmuka. Dan tidak ada cara yang berguna yang WidgetListdapat menangani pengecualian (seperti yang akan saya jelaskan nanti).

Tampaknya satu-satunya hal yang harus dilakukan adalah menangkap dan menguji kembali pengecualian yang diperiksa sebagai pengecualian yang tidak dicentang:

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw new WidgetListException(e);
    }
}

public static class WidgetListException extends RuntimeException {
    public WidgetListException(Throwable cause) {
        super(cause);
    }
}

((Sunting: Java 8 telah menambahkan UncheckedIOExceptionkelas untuk kasus ini: untuk menangkap dan mem-rethrowing IOExceptionmelintasi batas-batas metode polimorfik. Jenis membuktikan poin saya!))

Jadi pengecualian yang diperiksa tidak berfungsi dalam kasus seperti ini. Anda tidak bisa melemparnya. Ditto untuk pandai yang Mapdidukung oleh database, atau implementasi java.util.Randomterhubung ke sumber entropi kuantum melalui port COM. Segera setelah Anda mencoba melakukan novel apa pun dengan penerapan antarmuka polimorfik, konsep pengecualian yang diperiksa gagal. Tetapi pengecualian yang dicentang sangat berbahaya sehingga mereka tidak akan meninggalkan Anda dengan tenang, karena Anda masih harus menangkap dan mengolah kembali dari metode tingkat bawah, mengacaukan kode dan mengacaukan jejak tumpukan.

Saya menemukan bahwa Runnableantarmuka di mana-mana sering dicadangkan ke sudut ini, jika itu memanggil sesuatu yang melempar pengecualian yang diperiksa. Itu tidak bisa melempar pengecualian seperti apa adanya, jadi yang bisa dilakukan hanyalah mengacaukan kode dengan menangkap dan mem-rethrowing sebagai RuntimeException.

Sebenarnya, Anda dapat membuang pengecualian yang tidak dideklarasikan jika Anda menggunakan peretasan. JVM, pada saat dijalankan, tidak peduli dengan aturan pengecualian yang diperiksa, jadi kita hanya perlu menipu kompilator. Cara termudah untuk melakukan ini adalah dengan menyalahgunakan obat generik. Ini adalah metode saya untuk itu (nama kelas ditampilkan karena (sebelum Java 8) itu diperlukan dalam sintaks panggilan untuk metode generik):

class Util {
    /**
     * Throws any {@link Throwable} without needing to declare it in the
     * method's {@code throws} clause.
     * 
     * <p>When calling, it is suggested to prepend this method by the
     * {@code throw} keyword. This tells the compiler about the control flow,
     * about reachable and unreachable code. (For example, you don't need to
     * specify a method return value when throwing an exception.) To support
     * this, this method has a return type of {@link RuntimeException},
     * although it never returns anything.
     * 
     * @param t the {@code Throwable} to throw
     * @return nothing; this method never returns normally
     * @throws Throwable that was provided to the method
     * @throws NullPointerException if {@code t} is {@code null}
     */
    public static RuntimeException sneakyThrow(Throwable t) {
        return Util.<RuntimeException>sneakyThrow1(t);
    }

    @SuppressWarnings("unchecked")
    private static <T extends Throwable> RuntimeException sneakyThrow1(
            Throwable t) throws T {
        throw (T)t;
    }
}

Hore! Dengan menggunakan ini kita bisa melempar pengecualian yang diperiksa setiap kedalaman tumpukan tanpa mendeklarasikannya, tanpa membungkusnya dalam RuntimeException, dan tanpa mengacaukan jejak tumpukan! Menggunakan contoh "WidgetList" lagi:

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw sneakyThrow(e);
    }
}

Sayangnya, penghinaan terakhir dari pengecualian yang diperiksa adalah bahwa kompiler menolak untuk mengizinkan Anda menangkap pengecualian yang diperiksa jika, menurut pendapatnya yang cacat, itu tidak mungkin terlempar. (Pengecualian yang tidak dicentang tidak memiliki aturan ini.) Untuk mengetahui pengecualian yang terlempar secara licik, kita harus melakukan ini:

try {
    ...
} catch (Throwable t) { // catch everything
    if (t instanceof IOException) {
        // handle it
        ...
    } else {
        // didn't want to catch this one; let it go
        throw t;
    }
}

Itu agak canggung, tetapi di sisi positifnya, itu masih sedikit lebih sederhana daripada kode untuk mengekstraksi pengecualian diperiksa yang dibungkus dengan a RuntimeException.

Untungnya, throw t;pernyataan itu sah di sini, meskipun jenisnya tdicek, berkat aturan yang ditambahkan di Jawa 7 tentang rethrowing pengecualian yang ditangkap.


Ketika pengecualian yang diperiksa memenuhi polimorfisme, kasus sebaliknya juga menjadi masalah: ketika suatu metode dispesifikasi berpotensi melemparkan pengecualian yang diperiksa, tetapi implementasi yang ditimpa tidak. Sebagai contoh, kelas abstrak OutputStream's writemetode semua tentukan throws IOException. ByteArrayOutputStreamadalah subclass yang menulis ke array di dalam memori alih-alih sumber I / O yang sebenarnya. writeMetode yang diganti tidak dapat menyebabkan IOExceptions, sehingga mereka tidak memiliki throwsklausa, dan Anda dapat memanggil mereka tanpa khawatir tentang persyaratan tangkap-atau-tentukan.

Kecuali tidak selalu. Misalkan Widgetmemiliki metode untuk menyimpannya ke aliran:

public void writeTo(OutputStream out) throws IOException;

Mendeklarasikan metode ini untuk menerima sebuah plain OutputStreamadalah hal yang benar untuk dilakukan, sehingga dapat digunakan secara polimorfis dengan semua jenis output: file, database, jaringan, dan sebagainya. Dan array dalam memori. Namun, dengan array dalam memori, ada persyaratan palsu untuk menangani pengecualian yang tidak dapat benar-benar terjadi:

ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
    someWidget.writeTo(out);
} catch (IOException e) {
    // can't happen (although we shouldn't ignore it if it does)
    throw new RuntimeException(e);
}

Seperti biasa, pengecualian yang diperiksa menghalangi. Jika variabel Anda dideklarasikan sebagai tipe dasar yang memiliki lebih banyak persyaratan pengecualian terbuka, Anda harus menambahkan penangan untuk pengecualian itu bahkan jika Anda tahu mereka tidak akan terjadi dalam aplikasi Anda.

Tetapi tunggu, pengecualian yang diperiksa sebenarnya sangat menjengkelkan, sehingga mereka bahkan tidak akan membiarkan Anda melakukan yang sebaliknya! Bayangkan saat ini Anda menangkap setiap panggilan yang IOExceptiondilemparkan writepada OutputStream, tetapi Anda ingin mengubah tipe variabel yang dideklarasikan menjadi a ByteArrayOutputStream, kompiler akan mencaci Anda karena mencoba untuk menangkap pengecualian yang dicentang yang dikatakan tidak dapat dibuang.

Aturan itu menyebabkan beberapa masalah yang tidak masuk akal. Misalnya, salah satu dari tiga writemetode OutputStreamyang tidak diganti oleh ByteArrayOutputStream. Secara khusus, write(byte[] data)adalah metode kenyamanan yang menulis array penuh dengan memanggil write(byte[] data, int offset, int length)dengan offset 0 dan panjang array. ByteArrayOutputStreammengganti metode tiga argumen tetapi mewarisi metode kenyamanan satu argumen apa adanya. Metode yang diwariskan melakukan hal yang tepat, tetapi termasuk throwsklausa yang tidak diinginkan . Itu mungkin sebuah kekeliruan dalam desain ByteArrayOutputStream, tetapi mereka tidak pernah bisa memperbaikinya karena itu akan merusak kompatibilitas sumber dengan kode apa pun yang menangkap pengecualian - pengecualian yang tidak pernah, tidak pernah, dan tidak akan pernah dilempar!

Aturan itu mengganggu saat mengedit dan debugging juga. Misalnya, kadang-kadang saya akan mengomentari panggilan metode sementara, dan jika itu bisa melempar pengecualian diperiksa, kompiler sekarang akan mengeluh tentang keberadaan lokal trydan catchblok. Jadi saya harus berkomentar juga, dan sekarang ketika mengedit kode di dalam, IDE akan masuk ke level yang salah karena {dan }dikomentari. Gah! Ini keluhan kecil tapi sepertinya satu-satunya pengecualian yang pernah dilakukan adalah menyebabkan masalah.


Saya hampir selesai. Frustrasi terakhir saya dengan pengecualian yang diperiksa adalah bahwa di sebagian besar situs panggilan , tidak ada yang berguna yang dapat Anda lakukan dengan mereka. Idealnya ketika terjadi kesalahan, kami akan memiliki penangan khusus aplikasi yang kompeten yang dapat memberi tahu pengguna tentang masalah dan / atau mengakhiri atau mencoba ulang operasi sebagaimana mestinya. Hanya pawang tinggi di atas tumpukan yang dapat melakukan ini karena itu adalah satu-satunya yang tahu tujuan keseluruhan.

Sebagai gantinya, kami mendapatkan idiom berikut, yang merajalela sebagai cara untuk menutup kompilator:

try {
    ...
} catch (SomeStupidExceptionOmgWhoCares e) {
    e.printStackTrace();
}

Dalam GUI atau program otomatis pesan yang dicetak tidak akan terlihat. Lebih buruk lagi, ia melanjutkan dengan sisa kode setelah pengecualian. Apakah pengecualian itu sebenarnya bukan kesalahan? Maka jangan cetak itu. Jika tidak, sesuatu yang lain akan meledak sebentar, pada saat objek pengecualian asli akan hilang. Ungkapan ini tidak lebih baik dari BASIC On Error Resume Nextatau PHP error_reporting(0);.

Memanggil semacam kelas logger tidak jauh lebih baik:

try {
    ...
} catch (SomethingWeird e) {
    logger.log(e);
}

Itu sama malas e.printStackTrace();dan masih bajak dengan kode dalam keadaan tak tentu. Plus, pilihan sistem pencatatan tertentu atau penangan lain adalah spesifik-aplikasi, jadi ini menyakitkan untuk digunakan kembali.

Tapi tunggu! Ada cara mudah dan universal untuk menemukan penangan khusus aplikasi. Ini lebih tinggi dari tumpukan panggilan (atau diatur sebagai pengendali pengecualian Thread yang tidak tertangkap ). Jadi di sebagian besar tempat, yang perlu Anda lakukan adalah melemparkan pengecualian lebih tinggi ke tumpukan . Misalnya throw e;,. Pengecualian diperiksa hanya menghalangi.

Saya yakin pengecualian yang diperiksa terdengar seperti ide yang bagus ketika bahasa dirancang, tetapi dalam praktiknya saya merasa semuanya mengganggu dan tidak bermanfaat.

Boann
sumber
Untuk metode ukuran Anda dengan WidgetList, saya akan cache ukuran dalam variabel dan mengaturnya di konstruktor. Konstruktor bebas untuk melemparkan pengecualian. Ini tidak akan berfungsi jika file berubah saat menggunakan WidgetList, yang mungkin akan menjadi buruk jika itu terjadi.
TofuBeer
2
SomeStupidExceptionOmgWhoCares dengan baik seseorang yang cukup peduli untuk membuangnya. Jadi entah itu seharusnya tidak pernah dibuang (desain buruk) atau Anda harus benar-benar menanganinya. Hal yang sama berlaku untuk implementasi buruk dari kelas pra-1.0 (aliran keluaran array byte) di mana desainnya, sayangnya buruk.
TofuBeer
2
Ungkapan yang tepat akan menjadi arahan yang akan menangkap pengecualian tertentu yang dilemparkan oleh panggilan subrutin bersarang dan rethrow mereka dibungkus dalam a RuntimeException. Perhatikan bahwa suatu rutin dapat secara bersamaan dinyatakan sebagai throws IOExceptionnamun juga menentukan bahwa segala yang IOExceptionterlempar dari panggilan bersarang harus dianggap tidak terduga dan dibungkus.
supercat
15
Saya seorang pengembang C # profesional dengan beberapa pengalaman Java yang tersandung pada posting ini. Saya bingung mengapa ada orang yang mendukung perilaku aneh ini. Dalam. NET jika saya ingin menangkap jenis pengecualian tertentu, saya bisa menangkapnya. Jika saya ingin membiarkannya dibuang, tidak ada yang bisa dilakukan. Saya berharap Jawa tidak begitu unik. :)
aikeru
Mengenai "kadang-kadang saya akan berkomentar panggilan metode sementara" - Saya belajar menggunakan if (false)ini. Ini menghindari masalah klausa lemparan dan peringatan membantu saya untuk menavigasi kembali lebih cepat. +++ Yang mengatakan, saya setuju dengan semua yang Anda tulis. Pengecualian yang diperiksa memiliki beberapa nilai, tetapi nilai ini dapat diabaikan jika dibandingkan dengan biayanya. Hampir selalu mereka hanya menghalangi.
maaartinus
45

Yah, ini bukan tentang menampilkan stacktrace atau crash secara diam-diam. Ini tentang cara mengkomunikasikan kesalahan antar lapisan.

Masalah dengan pengecualian yang diperiksa adalah mereka mendorong orang untuk menelan detail penting (yaitu, kelas pengecualian). Jika Anda memilih untuk tidak menelan detail itu, maka Anda harus terus menambahkan deklarasi pelemparan di seluruh aplikasi Anda. Ini berarti 1) bahwa tipe pengecualian baru akan memengaruhi banyak tanda tangan fungsi, dan 2) Anda dapat melewatkan contoh spesifik dari pengecualian yang sebenarnya ingin Anda tangkap (misalnya Anda membuka file sekunder untuk fungsi yang menulis data ke File sekunder adalah opsional, sehingga Anda dapat mengabaikan kesalahannya, tetapi karena tanda tangannya throws IOException, mudah untuk mengabaikan ini).

Saya sebenarnya berurusan dengan situasi ini sekarang dalam aplikasi. Kami mengemas ulang hampir pengecualian sebagai AppSpecificException. Ini membuat tanda tangan benar-benar bersih dan kami tidak perlu khawatir tentang meledaknya throwstanda tangan.

Tentu saja, sekarang kita perlu mengkhususkan penanganan kesalahan di tingkat yang lebih tinggi, menerapkan logika coba lagi dan semacamnya. Semuanya adalah AppSpecificException, jadi kami tidak dapat mengatakan "Jika IOException dilempar, coba lagi" atau "Jika ClassNotFound dilempar, batalkan sepenuhnya". Kami tidak memiliki cara yang dapat diandalkan untuk sampai ke pengecualian nyata karena hal-hal akan dikemas ulang berulang kali saat mereka melewati antara kode kami dan kode pihak ketiga.

Inilah sebabnya saya penggemar berat penanganan python. Anda hanya dapat menangkap hal-hal yang Anda inginkan dan / atau dapat tangani. Segala sesuatu yang lain meluap seolah-olah Anda memainkannya kembali sendiri (yang telah Anda lakukan)

Saya telah menemukan, berkali-kali, dan sepanjang proyek yang saya sebutkan, penanganan pengecualian termasuk dalam 3 kategori:

  1. Tangkap dan tangani pengecualian tertentu . Ini untuk menerapkan coba lagi logika, misalnya.
  2. Tangkap dan rethrow pengecualian lain . Semua yang terjadi di sini biasanya masuk, dan biasanya pesan basi seperti "Tidak dapat membuka $ nama file". Ini adalah kesalahan yang tidak bisa Anda lakukan; hanya level yang lebih tinggi yang cukup tahu untuk menanganinya.
  3. Tangkap semuanya dan tampilkan pesan kesalahan. Ini biasanya merupakan akar dari suatu pengirim, dan semua itu memastikannya dapat mengkomunikasikan kesalahan kepada pemanggil melalui mekanisme non-Pengecualian (dialog sembulan, mengotak-atik objek Kesalahan RPC, dll).
Richard Levasseur
sumber
5
Anda bisa membuat subclass spesifik dari AppSpecificException untuk memungkinkan pemisahan sambil menjaga tanda tangan metode biasa.
Thorbjørn Ravn Andersen
1
Juga tambahan yang sangat penting untuk item 2, adalah memungkinkan Anda untuk MENAMBAH INFORMASI ke pengecualian yang tertangkap (misalnya dengan bersarang di RuntimeException). Adalah jauh, jauh lebih baik memiliki nama file yang tidak ditemukan dalam jejak stack, daripada tersembunyi jauh di dalam file log.
Thorbjørn Ravn Andersen
1
Pada dasarnya argumen Anda adalah "Mengelola pengecualian itu melelahkan jadi saya lebih suka tidak menghadapinya". Sebagai pengecualian muncul itu kehilangan makna dan pembuatan konteks praktis tidak berguna. Sebagai perancang API yang harus Anda buat adalah jelas secara kontrak tentang apa yang dapat diharapkan ketika terjadi kesalahan, jika program saya mogok karena saya tidak diberitahu bahwa pengecualian ini atau itu dapat "meluap" maka Anda, sebagai perancang, gagal dan sebagai akibat kegagalan Anda, sistem saya tidak stabil seperti yang seharusnya.
Newtopian
4
Bukan itu yang saya katakan sama sekali. Kalimat terakhir Anda sebenarnya setuju dengan saya. Jika semuanya dibungkus dalam AppSpecificException, maka itu tidak muncul (dan makna / konteks hilang), dan, ya, klien API tidak diinformasikan - ini adalah persis apa yang terjadi dengan pengecualian yang diperiksa (karena mereka berada di java) , karena orang tidak ingin berurusan dengan fungsi dengan banyak throwsdeklarasi.
Richard Levasseur
6
@Newtopian - sebagian besar pengecualian hanya dapat ditangani di tingkat "bisnis" atau "permintaan". Masuk akal untuk gagal atau mencoba lagi pada granularity besar, tidak untuk setiap kegagalan kecil yang potensial. Untuk alasan ini, praktik terbaik penanganan pengecualian diringkas sebagai "melempar lebih awal, terlambat". Pengecualian yang diperiksa mempersulit pengelolaan keandalan pada tingkat yang benar, dan mendorong sejumlah besar blok tangkap yang salah kode. literatejava.com/exceptions/…
Thomas W
24

SNR

Pertama, pengecualian yang diperiksa mengurangi "rasio signal-to-noise" untuk kode. Anders Hejlsberg juga berbicara tentang pemrograman imperatif vs deklaratif yang merupakan konsep serupa. Pokoknya pertimbangkan potongan kode berikut:

Perbarui UI dari non-UI-utas di Java:

try {  
    // Run the update code on the Swing thread  
    SwingUtilities.invokeAndWait(() -> {  
        try {
            // Update UI value from the file system data  
            FileUtility f = new FileUtility();  
            uiComponent.setValue(f.readSomething());
        } catch (IOException e) {  
            throw new UncheckedIOException(e);
        }
    });
} catch (InterruptedException ex) {  
    throw new IllegalStateException("Interrupted updating UI", ex);  
} catch (InvocationTargetException ex) {
    throw new IllegalStateException("Invocation target exception updating UI", ex);
}

Perbarui UI dari non-UI-utas di C #:

private void UpdateValue()  
{  
   // Ensure the update happens on the UI thread  
   if (InvokeRequired)  
   {  
       Invoke(new MethodInvoker(UpdateValue));  
   }  
   else  
   {  
       // Update UI value from the file system data  
       FileUtility f = new FileUtility();  
       uiComponent.Value = f.ReadSomething();  
   }  
}  

Yang tampaknya jauh lebih jelas bagi saya. Ketika Anda mulai melakukan lebih banyak pekerjaan UI di Swing, pengecualian mulai menjadi sangat menjengkelkan dan tidak berguna.

Break Penjara

Untuk mengimplementasikan bahkan implementasi paling dasar, seperti antarmuka Daftar Java, pengecekan pengecualian sebagai alat untuk desain berdasarkan kontrak jatuh. Pertimbangkan daftar yang didukung oleh database atau sistem file atau implementasi lain yang melempar pengecualian yang diperiksa. Satu-satunya implementasi yang mungkin adalah menangkap pengecualian yang diperiksa dan mengolahnya kembali sebagai pengecualian yang tidak dicentang:

@Override
public void clear()  
{  
   try  
   {  
       backingImplementation.clear();  
   }  
   catch (CheckedBackingImplException ex)  
   {  
       throw new IllegalStateException("Error clearing underlying list.", ex);  
   }  
}  

Dan sekarang Anda harus bertanya apa gunanya semua kode itu? Pengecualian diperiksa hanya menambah kebisingan, pengecualian telah ditangkap tetapi tidak ditangani dan desain oleh kontrak (dalam hal pengecualian diperiksa) telah rusak.

Kesimpulan

  • Menangkap pengecualian berbeda dengan menanganinya.
  • Pengecualian diperiksa menambah kebisingan ke kode.
  • Penanganan pengecualian bekerja dengan baik di C # tanpa mereka.

Saya menulis blog tentang ini sebelumnya .

Luke Quinane
sumber
3
Dalam contoh ini, Java dan C # hanya membiarkan pengecualian menyebar tanpa menanganinya (Java via IllegalStateException). Perbedaannya adalah bahwa Anda mungkin ingin menangani FileNotFoundException tetapi tidak mungkin menangani InvocationTargetException atau InterruptedException akan berguna.
Luke Quinane
3
Dan dengan cara C # bagaimana saya tahu bahwa pengecualian I / O dapat terjadi? Juga saya tidak akan pernah membuang pengecualian dari menjalankan ... Saya menganggap bahwa penanganan pengecualian menyalahgunakan. Maaf tetapi untuk bagian kode Anda, saya hanya bisa melihat sisi Anda dari itu.
TofuBeer
7
Kami sampai di sana :-) Jadi dengan setiap rilis baru dari API Anda harus menyisir semua panggilan Anda dan mencari pengecualian baru yang mungkin terjadi? Dapat dengan mudah terjadi dengan internal ke API perusahaan karena mereka tidak perlu khawatir tentang kompatibilitas mundur.
TofuBeer
3
Apakah maksud Anda mengurangi rasio signal-to-noise?
neo2862
3
@TofuBeer Apakah tidak dipaksa untuk memperbarui kode Anda setelah antarmuka API yang mendasari mengubah hal yang baik? Jika Anda hanya memiliki pengecualian yang tidak dicentang di sana, Anda akan berakhir dengan program yang rusak / tidak lengkap tanpa menyadarinya.
Francois Bourgeois
23

Artima menerbitkan sebuah wawancara dengan salah satu arsitek .NET, Anders Hejlsberg, yang secara aktif membahas argumen terhadap pengecualian yang diperiksa. Pencicip singkat:

Klausa pelemparan, setidaknya cara penerapannya di Jawa, tidak serta merta memaksa Anda untuk menangani pengecualian, tetapi jika Anda tidak menanganinya, itu memaksa Anda untuk mengetahui secara tepat pengecualian mana yang mungkin melewatinya. Ini mengharuskan Anda untuk menangkap pengecualian yang dinyatakan atau menempatkannya di klausa lemparan Anda sendiri. Untuk mengatasi persyaratan ini, orang melakukan hal-hal konyol. Misalnya, mereka menghiasi setiap metode dengan, "throws Exception." Itu hanya benar-benar mengalahkan fitur, dan Anda hanya membuat programmer menulis lebih banyak omong kosong. Itu tidak membantu siapa pun.

Le Dude
sumber
2
Bahkan, kepala arsitek.
Perangkap
18
Saya telah membaca bahwa, bagi saya argumennya bermuara pada "ada programmer buruk di luar sana".
TofuBeer
6
TofuBeer, tidak sama sekali. Intinya adalah bahwa sering kali Anda tidak tahu apa yang harus dilakukan dengan Pengecualian bahwa metode yang disebut melempar, dan kasus Anda benar-benar tertarik bahkan tidak disebutkan. Anda membuka file, Anda mendapatkan Pengecualian IO, misalnya ... itu bukan masalah saya, jadi saya membuangnya. Tetapi metode panggilan tingkat atas hanya ingin berhenti memproses dan memberi tahu pengguna bahwa ada masalah yang tidak diketahui. Pengecualian yang dicentang tidak membantu sama sekali. Itu adalah satu dari sejuta hal aneh yang bisa terjadi.
Dan Rosenstark
4
@yar, jika Anda tidak menyukai pengecualian yang dicentang, maka lakukan "lempar RuntimeException baru (" kami tidak mengharapkan ini ketika melakukan Foo.bar () ", e)" dan selesai dengan itu.
Thorbjørn Ravn Andersen
4
@ ThorbjørnRavnAndersen: Kelemahan desain mendasar di Jawa, yang sayangnya disalin oleh .net, adalah bahwa ia menggunakan tipe pengecualian sebagai keduanya sarana utama untuk memutuskan apakah harus ditindaklanjuti, dan sarana utama untuk menunjukkan jenis umum dari hal tersebut. itu salah, padahal sebenarnya kedua isu itu sebagian besar ortogonal. Yang penting bukan apa yang salah, tetapi objek negara apa yang masuk. Selanjutnya, baik .net dan Java menganggap secara default bahwa menindaklanjuti dan menyelesaikan pengecualian pada umumnya adalah hal yang sama, padahal sebenarnya mereka sering berbeda.
supercat
20

Awalnya saya setuju dengan Anda, karena saya selalu mendukung pengecualian yang dicek, dan mulai memikirkan mengapa saya tidak suka tidak memeriksa pengecualian di .Net. Tetapi kemudian saya menyadari bahwa saya tidak benar-benar menyukai pengecualian yang diperiksa.

Untuk menjawab pertanyaan Anda, ya, saya suka program saya untuk menampilkan jejak stack, terutama yang jelek. Saya ingin aplikasi meledak menjadi tumpukan pesan kesalahan paling jelek yang pernah Anda inginkan.

Dan alasannya adalah karena, jika berhasil, saya harus memperbaikinya, dan saya harus segera memperbaikinya. Saya ingin segera tahu bahwa ada masalah.

Berapa kali Anda benar-benar menangani pengecualian? Saya tidak berbicara tentang menangkap pengecualian - saya berbicara tentang menangani mereka? Terlalu mudah untuk menulis yang berikut ini:

try {
  thirdPartyMethod();
} catch(TPException e) {
  // this should never happen
}

Dan saya tahu Anda dapat mengatakan bahwa ini adalah praktik buruk, dan bahwa 'jawabannya' adalah melakukan sesuatu dengan pengecualian (biarkan saya tebak, catat itu?), Tetapi di Dunia Nyata (tm), sebagian besar programmer tidak melakukan Itu.

Jadi ya, saya tidak ingin menangkap pengecualian jika saya tidak harus melakukannya, dan saya ingin program saya meledak secara spektakuler ketika saya gagal. Gagal diam-diam adalah hasil terburuk yang mungkin terjadi.

tsimon
sumber
Java mendorong Anda untuk melakukan hal semacam ini, sehingga Anda tidak harus menambahkan setiap jenis pengecualian ke setiap tanda tangan metode.
yfeldblum
17
Lucu .. sejak saya menerima pengecualian yang dicek dengan benar dan menggunakannya dengan tepat, program saya berhenti meledak di tumpukan besar kekecewaan pelanggan Anda. Jika saat mengembangkan Anda memiliki jejak tumpukan buruk besar jelek maka pelanggan pasti akan mendapatkannya juga. D 'cintai untuk melihat wajahnya ketika dia melihat ArrayIndexOutOfBoundsException dengan jejak setumpuk mil tinggi pada sistem yang macet, bukannya pemberitahuan baki kecil yang mengatakan bahwa konfigurasi warna untuk tombol XYZ tidak dapat diuraikan sehingga default digunakan sebagai gantinya dengan perangkat lunak yang dengan senang menyenandungkan along
Newtopian
1
Mungkin yang dibutuhkan Java adalah deklarasi "cantHandle" yang akan menentukan bahwa metode atau coba / tangkap blok kode tidak siap untuk menangani pengecualian tertentu yang terjadi di dalamnya, dan bahwa setiap pengecualian yang terjadi melalui sarana selain dari eksplisit melempar dalam metode itu (sebagai lawan dari metode yang disebut) harus secara otomatis dibungkus dan diposisikan kembali dalam RuntimeException. IMHO, pengecualian yang dicek harus jarang menyebarkan tumpukan panggilan tanpa dibungkus.
supercat
3
@Newtopian - Saya menulis server & perangkat lunak keandalan tinggi & telah melakukannya selama 25 tahun. Program saya tidak pernah meledak, dan saya bekerja dengan ketersediaan tinggi, coba lagi & sambungkan kembali, sistem keuangan & militer berbasis integrasi. Saya memiliki dasar objektif absolut untuk lebih memilih pengecualian runtime. Pengecualian diperiksa membuat lebih sulit untuk mengikuti praktik terbaik "melempar lebih awal, menangkap terlambat" yang benar. Keandalan & penanganan kesalahan yang benar ada di level "bisnis", "koneksi" atau "permintaan". (Atau sesekali saat mem-parsing data). Pengecualian diperiksa menghalangi jalannya dengan benar.
Thomas W
1
Pengecualian yang Anda bicarakan di sini adalah RuntimeExceptionsyang memang tidak perlu Anda tangkap, dan saya setuju Anda harus membiarkan program ini meledak. Pengecualian yang harus selalu Anda tangkap dan tangani adalah pengecualian yang dicentang seperti IOException. Jika Anda mendapatkan IOException, tidak ada yang diperbaiki dalam kode Anda; program Anda tidak boleh meledak hanya karena ada gangguan jaringan.
AxiomaticNexus
20

Artikel Pengecualian Java yang Efektif menjelaskan dengan baik kapan harus menggunakan yang tidak dicentang dan kapan harus menggunakan pengecualian yang diperiksa. Berikut adalah beberapa kutipan dari artikel tersebut untuk menyoroti poin utama:

Kontinjensi: Suatu kondisi yang diharapkan menuntut respons alternatif dari suatu metode yang dapat dinyatakan dalam tujuan tujuan metode tersebut. Penelepon metode mengharapkan kondisi seperti ini dan memiliki strategi untuk mengatasinya.

Kesalahan: Suatu kondisi yang tidak direncanakan yang mencegah suatu metode mencapai tujuan yang dimaksudkan yang tidak dapat dijelaskan tanpa mengacu pada implementasi internal metode tersebut.

(SO tidak mengizinkan tabel, jadi Anda mungkin ingin membaca yang berikut dari halaman asli ...)

Kemungkinan

  • Dianggap sebagai: Bagian dari desain
  • Diharapkan terjadi: Secara teratur tetapi jarang
  • Siapa yang peduli tentang hal itu: Kode upstream yang memanggil metode
  • Contoh: Mode pengembalian alternatif
  • Pemetaan Terbaik: Pengecualian yang dicentang

Kesalahan

  • Dianggap sebagai: Kejutan yang tidak menyenangkan
  • Diharapkan terjadi: Tidak pernah
  • Siapa yang peduli tentang itu: Orang-orang yang perlu memperbaiki masalah
  • Contoh: Pemrograman bug, kerusakan perangkat keras, kesalahan konfigurasi, file yang hilang, server tidak tersedia
  • Pemetaan Terbaik: Pengecualian yang tidak dicentang
Esko Luontola
sumber
Saya tahu kapan harus menggunakannya, saya ingin tahu mengapa orang-orang yang tidak mengikuti saran itu ... jangan ikuti saran itu :-)
TofuBeer
Apa itu bug pemrograman dan bagaimana membedakannya dari bug penggunaan ? Apakah itu bug pemrograman jika pengguna memberikan argumen yang salah ke program? Dari sudut pandang Java, itu mungkin bukan bug pemrograman tetapi dari sudut pandang shell script ini adalah bug pemrograman. Jadi, apa argumen yang tidak valid args[]? Apakah itu suatu Kemungkinan atau Kesalahan?
ceving
3
@TofuBeer - Karena perancang perpustakaan Java, memilih untuk meletakkan semua jenis kegagalan tingkat rendah yang tidak dapat dipulihkan sebagai pengecualian yang diperiksa ketika mereka jelas-jelas harus tidak dicentang . FileNotFound adalah satu-satunya IOException yang harus diperiksa, misalnya. Berkenaan dengan JDBC - hanya terhubung ke database, dapat dianggap sebagai kemungkinan . Semua persepsi SQLE lainnya seharusnya gagal dan tidak dicentang. Penanganan kesalahan harus dengan benar berada di level "bisnis" atau "permintaan" - lihat praktik terbaik "melempar lebih awal, terlambat". Pengecualian yang diperiksa adalah penghalang untuk itu.
Thomas W
1
Ada satu kelemahan besar dalam argumen Anda. "Kontingensi" TIDAK harus ditangani melalui pengecualian, tetapi melalui nilai-nilai kode bisnis dan metode pengembalian. Pengecualian untuk, seperti kata kata, situasi LUAR BIASA, karena itu Kesalahan.
Matteo Mosca
@MatteoMosca Kode pengembalian kesalahan cenderung diabaikan dan itu cukup untuk mendiskualifikasi mereka. Sebenarnya, apa pun yang tidak biasa seringkali hanya dapat ditangani di suatu tempat di tumpukan dan itu adalah kasus penggunaan untuk pengecualian. Saya bisa membayangkan sesuatu seperti File#openInputStreamkembali Either<InputStream, Problem>- jika itu yang Anda maksud, maka kami mungkin setuju.
maaartinus
19

Pendeknya:

Pengecualian adalah pertanyaan desain API. -- Tidak lebih, tidak kurang.

Argumen untuk pengecualian yang diperiksa:

Untuk memahami mengapa pengecualian yang diperiksa mungkin bukan hal yang baik, mari balikkan pertanyaan dan tanyakan: Kapan atau mengapa pengecualian yang diperiksa menarik, yaitu mengapa Anda ingin kompiler menerapkan deklarasi pengecualian?

Jawabannya jelas: Terkadang Anda perlu menangkap pengecualian, dan itu hanya mungkin jika kode yang dipanggil menawarkan kelas pengecualian khusus untuk kesalahan yang Anda minati.

Oleh karena itu, argumen untuk pengecualian yang diperiksa adalah bahwa kompiler memaksa pemrogram untuk menyatakan pengecualian yang dilemparkan, dan mudah - mudahan programmer kemudian juga akan mendokumentasikan kelas pengecualian khusus dan kesalahan yang menyebabkannya.

Pada kenyataannya, terlalu sering suatu paket com.acmehanya melempar AcmeExceptionsubclass daripada yang spesifik. Penelepon kemudian perlu menangani, menyatakan, atau memberi sinyal ulang AcmeExceptions, tetapi masih belum dapat memastikan apakah suatu peristiwa AcmeFileNotFoundErrorterjadi atau tidak AcmePermissionDeniedError.

Jadi, jika Anda hanya tertarik pada AcmeFileNotFoundError, solusinya adalah mengajukan permintaan fitur dengan programmer ACME dan menyuruh mereka untuk mengimplementasikan, mendeklarasikan, dan mendokumentasikan subclass tersebut AcmeException.

Jadi mengapa repot?

Oleh karena itu, bahkan dengan pengecualian yang dicentang, kompiler tidak dapat memaksa programmer untuk membuang pengecualian yang berguna . Itu masih hanya masalah kualitas API.

Akibatnya, bahasa tanpa pengecualian yang dicentang biasanya tidak lebih mahal. Pemrogram mungkin tergoda untuk melempar contoh yang tidak spesifik dari Errorkelas umum daripada kelas AcmeException, tetapi jika mereka benar-benar peduli tentang kualitas API mereka, mereka akan belajar untuk memperkenalkannya AcmeFileNotFoundError.

Secara keseluruhan, spesifikasi dan dokumentasi pengecualian tidak jauh berbeda dari spesifikasi dan dokumentasi, katakanlah, metode biasa. Itu juga merupakan pertanyaan desain API, dan jika seorang programmer lupa untuk mengimplementasikan atau mengekspor fitur yang bermanfaat, API perlu ditingkatkan sehingga Anda dapat bekerja dengannya dengan bermanfaat.

Jika Anda mengikuti alur penalaran ini, seharusnya sudah jelas bahwa "kerumitan" menyatakan, menangkap, dan melempar kembali pengecualian yang begitu umum dalam bahasa seperti Java sering menambah sedikit nilai.

Perlu dicatat juga bahwa Java VM tidak memiliki pengecekan pengecualian - hanya Java compiler yang memeriksanya, dan file kelas dengan deklarasi pengecualian yang berubah kompatibel pada saat dijalankan. Keamanan VM Java tidak ditingkatkan dengan pengecualian yang diperiksa, hanya gaya pengkodean.

David Lichteblau
sumber
4
Argumen Anda bertentangan dengan dirinya sendiri. Jika "terkadang Anda perlu menangkap pengecualian" dan kualitas API sering buruk, tanpa pengecualian yang dicentang Anda tidak akan tahu apakah perancang mengabaikan dokumen bahwa metode tertentu melempar pengecualian yang perlu ditangkap. Pasangan itu dengan melempar AcmeExceptiondaripada AcmeFileNotFoundErrordan semoga berhasil mengetahui apa yang Anda lakukan salah dan di mana Anda harus menangkapnya. Pengecualian yang dicentang menyediakan sejumlah kecil perlindungan bagi programmer terhadap desain API yang buruk.
Eva
1
Desain perpustakaan Java membuat kesalahan serius. 'Pengecualian yang dicentang' adalah untuk kemungkinan yang dapat diprediksi & dipulihkan - seperti file yang tidak ditemukan, kegagalan untuk terhubung. Mereka tidak pernah dimaksudkan atau cocok untuk kegagalan sistemik tingkat rendah. Akan baik-baik saja untuk memaksa membuka file yang akan diperiksa, tetapi tidak ada coba lagi masuk akal atau pemulihan karena kegagalan untuk menulis satu byte / jalankan permintaan SQL dll. Coba lagi atau pemulihan ditangani dengan benar di permintaan "bisnis" atau " "Level, yang mengecek pengecualian membuat sulit tanpa titik. literatejava.com/exceptions/…
Thomas W
17

Saya telah bekerja dengan beberapa pengembang dalam tiga tahun terakhir dalam aplikasi yang relatif kompleks. Kami memiliki basis kode yang cukup sering menggunakan Pengecualian Tercatat dengan penanganan kesalahan yang tepat, dan beberapa lainnya tidak.

Sejauh ini, saya merasa lebih mudah untuk bekerja dengan basis kode dengan Pengecualian yang Diperiksa. Ketika saya menggunakan API orang lain, alangkah baiknya saya bisa melihat kondisi kesalahan seperti apa yang bisa saya harapkan ketika saya memanggil kode dan menanganinya dengan benar, baik dengan masuk, menampilkan atau mengabaikan (Ya, ada kasus yang valid untuk mengabaikan pengecualian, seperti implementasi ClassLoader). Itu memberi kode saya menulis kesempatan untuk pulih. Semua pengecualian runtime yang saya sebarkan ke atas sampai di-cache dan ditangani dengan beberapa kode penanganan kesalahan umum. Ketika saya menemukan pengecualian yang diperiksa yang tidak benar-benar ingin saya tangani pada tingkat tertentu, atau bahwa saya menganggap kesalahan logika pemrograman, maka saya membungkusnya menjadi RuntimeException dan membiarkannya muncul. Jangan pernah menelan pengecualian tanpa alasan yang bagus (dan alasan bagus untuk melakukan ini agak langka)

Ketika saya bekerja dengan basis kode yang tidak memeriksa pengecualian, itu membuat saya sedikit lebih sulit untuk mengetahui sebelumnya apa yang dapat saya harapkan ketika memanggil fungsi, yang dapat merusak beberapa hal dengan sangat buruk.

Ini tentu saja masalah preferensi dan keterampilan pengembang. Kedua cara pemrograman dan penanganan kesalahan bisa sama efektif (atau tidak efektif), jadi saya tidak akan mengatakan bahwa ada The One Way.

Secara keseluruhan, saya merasa lebih mudah untuk bekerja dengan Pengecualian Tercentang, khususnya dalam proyek besar dengan banyak pengembang.

Mario Ortegón
sumber
6
Saya lakukan untuk. Bagi saya mereka adalah bagian penting dari kontrak. Tanpa harus mendapatkan detail dalam dokumentasi API, saya dapat dengan cepat mengetahui skenario kesalahan yang paling mungkin terjadi.
Wayne Hartman
2
Setuju. Saya mengalami perlunya pengecekan pengecualian di .Net suatu kali ketika saya mencoba melakukan panggilan jaringan. Mengetahui bahwa gangguan jaringan dapat terjadi kapan saja, saya harus membaca seluruh dokumentasi API untuk mengetahui pengecualian apa yang perlu saya tangkap khusus untuk skenario itu. Jika C # telah mengecek pengecualian, saya akan langsung tahu. Pengembang C # lainnya mungkin hanya akan membiarkan aplikasi crash dari kesalahan jaringan sederhana.
AxiomaticNexus
13

Kategori pengecualian

Ketika berbicara tentang pengecualian, saya selalu merujuk kembali ke artikel blog pengecualian Vexing Eric Lippert . Dia menempatkan pengecualian ke dalam kategori ini:

  • Fatal - Pengecualian ini bukan kesalahan Anda: Anda tidak dapat mencegahnya saat itu, dan Anda tidak dapat menanganinya dengan bijaksana. Misalnya, OutOfMemoryErroratau ThreadAbortException.
  • Boneheaded - Pengecualian ini adalah kesalahan Anda: Anda seharusnya mencegahnya, dan itu merepresentasikan bug dalam kode Anda. Misalnya ArrayIndexOutOfBoundsException,, NullPointerExceptionatau apa saja IllegalArgumentException.
  • Vexing - Pengecualian ini tidak luar biasa , bukan kesalahan Anda, Anda tidak dapat mencegahnya, tetapi Anda harus menghadapinya. Mereka sering merupakan hasil dari keputusan desain yang tidak menguntungkan, seperti melempar NumberFormatExceptiondari Integer.parseIntalih-alih menyediakan Integer.tryParseIntmetode yang mengembalikan boolean false pada kegagalan parse.
  • Eksogen - Pengecualian ini biasanya luar biasa , bukan kesalahan Anda, Anda tidak dapat (secara wajar) mencegahnya, tetapi Anda harus menanganinya . Sebagai contoh FileNotFoundException,.

Pengguna API:

  • tidak boleh menangani pengecualian fatal atau berkepala tebal .
  • harus menangani pengecualian yang menjengkelkan , tetapi tidak boleh terjadi di API yang ideal.
  • harus menangani pengecualian eksogen .

Pengecualian diperiksa

Fakta bahwa pengguna API harus menangani pengecualian tertentu adalah bagian dari kontrak metode antara penelepon dan callee. Kontrak tersebut menetapkan, antara lain: jumlah dan jenis argumen yang diharapkan callee, jenis nilai pengembalian yang bisa diharapkan si penelepon, dan pengecualian yang diharapkan untuk ditangani si penelepon .

Karena pengecualian menjengkelkan tidak boleh ada dalam API, hanya pengecualian eksogen ini yang harus diperiksa pengecualian untuk menjadi bagian dari kontrak metode. Relatif sedikit pengecualian yang eksogen , jadi API apa pun seharusnya memiliki relatif sedikit pengecualian yang diperiksa.

Pengecualian yang diperiksa adalah pengecualian yang harus ditangani . Menangani pengecualian bisa sesederhana menelannya. Sana! Pengecualian ditangani. Titik. Jika pengembang ingin menanganinya seperti itu, baiklah. Tapi dia tidak bisa mengabaikan pengecualian, dan telah diperingatkan.

Masalah API

Tetapi setiap API yang telah memeriksa pengecualian yang menjengkelkan dan fatal (misalnya JCL) akan memberikan tekanan yang tidak perlu pada pengguna API. Pengecualian tersebut harus ditangani, tapi entah pengecualian sangat umum sehingga tidak seharusnya pengecualian di tempat pertama, atau ada yang bisa dilakukan ketika menangani itu. Dan ini menyebabkan pengembang Java membenci pengecualian yang diperiksa.

Juga, banyak API tidak memiliki hierarki kelas pengecualian yang tepat, menyebabkan semua jenis pengecualian non-eksogen menyebabkan diwakili oleh kelas pengecualian tunggal yang diperiksa (mis IOException.). Dan ini juga menyebabkan pengembang Java membenci pengecualian yang diperiksa.

Kesimpulan

Pengecualian eksogen adalah yang bukan salah Anda, tidak bisa dicegah, dan yang harus ditangani. Ini membentuk sebagian kecil dari semua pengecualian yang bisa dilempar. API seharusnya hanya memeriksa pengecualian eksogen , dan semua pengecualian lainnya tidak dicentang. Ini akan membuat API lebih baik, mengurangi ketegangan pada pengguna API, dan karena itu mengurangi kebutuhan untuk menangkap semua, menelan atau mengolah kembali pengecualian yang tidak dicentang.

Jadi jangan membenci Java dan pengecualiannya. Alih-alih, benci API yang terlalu sering mengecek pengecualian.

Daniel AA Pelsmaeker
sumber
Dan menyalahgunakan mereka dengan tidak memiliki hierarki.
TofuBeer
1
FileNotFound dan membangun koneksi JDBC / jaringan adalah kemungkinan dan benar untuk diperiksa pengecualian, karena ini dapat diprediksi dan (mungkin) dapat dipulihkan. Kebanyakan IOExceptions lain, SQLExeption, RemoteException dll adalah kegagalan yang tidak dapat diprediksi & tidak dapat dipulihkan , dan seharusnya merupakan pengecualian runtime. Karena desain perpustakaan Java yang salah, kita semua telah disamakan dengan kesalahan ini & sekarang sebagian besar menggunakan Spring & Hibernate (yang membuat desain mereka benar).
Thomas W
Anda biasanya harus menangani pengecualian bertulang, meskipun Anda mungkin tidak ingin menyebutnya "penanganan". Misalnya, di server web, saya mencatatnya dan menampilkan 500 kepada pengguna. Karena pengecualiannya tidak terduga, ada banyak hal yang dapat saya lakukan sebelum perbaikan bug.
maaartinus
6

Oke ... Pengecualian yang dicentang tidak ideal dan memiliki beberapa peringatan tetapi mereka memang memiliki tujuan. Saat membuat API, ada beberapa kasus kegagalan tertentu yang merupakan kontrak dari API ini. Ketika dalam konteks bahasa yang diketik sangat statis seperti Java jika seseorang tidak menggunakan pengecualian yang diperiksa maka seseorang harus bergantung pada dokumentasi dan konvensi ad-hoc untuk menyampaikan kemungkinan kesalahan. Melakukan hal itu menghilangkan semua manfaat yang dapat dibawa oleh kompiler dalam menangani kesalahan dan Anda sepenuhnya bergantung pada kemauan baik para programmer.

Jadi, satu menghapus pengecualian diperiksa, seperti yang dilakukan dalam C #, lalu bagaimana seseorang bisa secara program dan struktural menyampaikan kemungkinan kesalahan? Bagaimana cara memberi tahu kode klien bahwa kesalahan ini dan itu dapat terjadi dan harus ditangani?

Saya mendengar segala macam kengerian ketika berhadapan dengan pengecualian yang diperiksa, mereka disalahgunakan ini pasti tapi begitu juga pengecualian yang tidak dicentang. Saya katakan tunggu beberapa tahun ketika API ditumpuk banyak lapisan dalam dan Anda akan memohon pengembalian beberapa jenis sarana terstruktur untuk menyampaikan kegagalan.

Ambil kasus ketika pengecualian dilemparkan di suatu tempat di bagian bawah lapisan API dan hanya menggelegak karena tidak ada yang tahu bahwa kesalahan ini mungkin terjadi, ini meskipun jenis kesalahan yang sangat masuk akal ketika kode panggilan melemparkannya (FileNotFoundException misalnya sebagai lawan dari VogonsTrashingEarthExcept ... dalam hal ini tidak masalah jika kita menanganinya atau tidak karena tidak ada yang tersisa untuk menanganinya).

Banyak yang berpendapat bahwa tidak dapat memuat file hampir selalu merupakan akhir dunia untuk prosesnya dan itu harus mati dengan kematian yang mengerikan dan menyakitkan. Jadi ya .. tentu ... ok .. Anda membangun API untuk sesuatu dan memuat file di beberapa titik ... Saya sebagai pengguna kata API hanya dapat menanggapi ... "Siapa yang harus Anda putuskan kapan saya program seharusnya macet! " Tentu Diberi pilihan di mana pengecualian menelan dan tidak meninggalkan jejak atau EletroFlabbingChunkFluxManifoldChuggingException dengan jejak tumpukan lebih dalam dari parit Marianna saya akan mengambil yang terakhir tanpa sedikit keraguan, tetapi apakah ini berarti bahwa itu adalah cara yang diinginkan untuk berurusan dengan pengecualian ? Tidak bisakah kita berada di suatu tempat di tengah, di mana pengecualian akan disusun kembali dan dibungkus setiap kali dilintasi ke tingkat abstraksi baru sehingga benar-benar berarti sesuatu?

Terakhir, sebagian besar argumen yang saya lihat adalah "Saya tidak ingin berurusan dengan pengecualian, banyak orang tidak ingin berurusan dengan pengecualian. Pengecualian yang diperiksa memaksa saya untuk berurusan dengan mereka, jadi saya benci pengecualian yang dicentang" Untuk menghilangkan mekanisme semacam itu sama sekali dan membuangnya ke jurang neraka goto hanya konyol dan tidak memiliki jugement dan visi.

Jika kita menghilangkan pengecekan yang dicentang, kita juga bisa menghilangkan jenis pengembalian untuk fungsi dan selalu mengembalikan variabel "jenis apa pun" ... Itu akan membuat hidup jadi lebih sederhana sekarang bukan?

Newtopian
sumber
2
Pengecualian yang dicek akan berguna jika ada cara deklaratif untuk mengatakan tidak ada pemanggilan metode dalam suatu blok yang diharapkan untuk melempar beberapa (atau apa saja) pengecekan yang dicek, dan setiap pengecualian semacam itu harus secara otomatis dibungkus dan dipasang kembali. Mereka bisa lebih berguna jika panggilan ke metode yang dinyatakan sebagai melempar pengecualian diperiksa memperdagangkan kecepatan panggilan / pengembalian untuk kecepatan penanganan pengecualian (sehingga pengecualian yang diharapkan dapat ditangani hampir secepat aliran program normal). Namun, situasi saat ini tidak berlaku.
supercat
6

Memang, pengecualian yang diperiksa di satu sisi meningkatkan kekokohan dan kebenaran program Anda (Anda dipaksa untuk membuat deklarasi yang benar dari antarmuka Anda - pengecualian metode melempar pada dasarnya adalah tipe pengembalian khusus). Di sisi lain Anda menghadapi masalah yang, karena pengecualian "menggelembung", sangat sering Anda perlu mengubah banyak metode (semua penelepon, dan penelepon penelepon, dan sebagainya) ketika Anda mengubah satu pengecualian metode melempar.

Pengecualian yang diperiksa di Jawa tidak memecahkan masalah yang terakhir; C # dan VB.NET membuang bayi dengan air mandi.

Pendekatan yang bagus yang mengambil jalan tengah dijelaskan dalam makalah OOPSLA 2005 ini (atau laporan teknis terkait .)

Singkatnya, ini memungkinkan Anda untuk mengatakan:, method g(x) throws like f(x)yang berarti bahwa g melempar semua pengecualian untuk melempar. Voila, periksa pengecualian tanpa masalah perubahan kaskade.

Meskipun ini adalah makalah akademis, saya mendorong Anda untuk membaca (bagian dari) itu, karena ia melakukan pekerjaan yang baik untuk menjelaskan apa manfaat dan kerugian dari pengecualian yang diperiksa.

Kurt Schelfthout
sumber
5

Ini bukan argumen yang menentang konsep murni pengecualian yang diperiksa, tetapi hirarki kelas yang digunakan Java untuk mereka adalah pertunjukan yang aneh. Kami selalu menyebut hal-hal itu hanya "pengecualian" - yang benar, karena spesifikasi bahasa memanggilnya juga - tetapi bagaimana pengecualian disebutkan dan diwakili dalam sistem tipe?

Oleh kelas yang Exceptiondibayangkan? Yah tidak, karena Exceptions adalah pengecualian, dan juga pengecualian adalah Exceptions, kecuali untuk pengecualian yang bukan Exception s, karena pengecualian lain sebenarnya adalah Errors, yang merupakan jenis pengecualian lain, sejenis pengecualian luar biasa yang seharusnya tidak pernah terjadi kecuali ketika itu terjadi, dan yang Anda tidak boleh menangkap kecuali kadang-kadang Anda harus. Kecuali itu tidak semua karena Anda juga dapat menentukan pengecualian lain yang bukan Exceptionatau tidak Errortetapi hanya Throwablepengecualian.

Manakah di antara ini yang merupakan pengecualian "dicentang"? Throwables adalah pengecualian yang diperiksa, kecuali jika mereka juga Errors, yang merupakan pengecualian yang tidak dicentang, dan kemudian ada Exceptions, yang juga Throwables dan merupakan jenis utama dari pengecualian yang diperiksa, kecuali ada satu pengecualian untuk itu juga, yaitu jika mereka juga RuntimeExceptions, karena itulah jenis pengecualian lain yang tidak dicentang.

Untuk apa RuntimeException? Yah seperti namanya, itu pengecualian, seperti semua Exceptions, dan mereka terjadi pada saat run-time, seperti semua pengecualian sebenarnya, kecuali bahwa RuntimeExceptionitu luar biasa dibandingkan dengan run-time lain Exceptionkarena mereka tidak seharusnya terjadi kecuali ketika Anda membuat beberapa kesalahan konyol, meskipun RuntimeExceptions tidak pernah Errors, jadi mereka untuk hal-hal yang sangat keliru tetapi sebenarnya bukan Errors. Kecuali untuk RuntimeErrorException, yang sebenarnya adalah RuntimeExceptionuntuk Error. Tapi bukankah semua pengecualian seharusnya mewakili keadaan yang salah? Ya, semuanya. Kecuali untuk ThreadDeath, pengecualian yang sangat tidak biasa, karena dokumentasi menjelaskan bahwa itu adalah "kejadian normal" dan bahwa 'Error

Lagi pula, karena kita membagi semua pengecualian menjadi menengah Error(yang untuk pengecualian eksekusi luar biasa, jadi tidak dicentang) dan Exceptions (yang untuk kesalahan eksekusi kurang luar biasa, jadi periksa kecuali ketika tidak), kita sekarang perlu dua berbagai jenis masing-masing dari beberapa pengecualian. Jadi kita membutuhkan IllegalAccessErrordan IllegalAccessException, InstantiationErrordan InstantiationException, NoSuchFieldErrordan NoSuchFieldException, NoSuchMethodErrordan NoSuchMethodException, dan, dan ZipErrordan ZipException.

Kecuali bahwa bahkan ketika pengecualian diperiksa, selalu ada (cukup mudah) cara untuk menipu kompilator dan membuangnya tanpa diperiksa. Jika Anda melakukannya Anda mungkin mendapatkan UndeclaredThrowableException, kecuali dalam kasus lain, di mana itu bisa muntah sebagai UnexpectedException, atau UnknownException(yang tidak terkait dengan UnknownError, yang hanya untuk "pengecualian serius"), atau ExecutionException,, atau InvocationTargetException, atau, atau ExceptionInInitializerError.

Oh, dan kita tidak boleh melupakan Java 8 yang baru UncheckedIOException, yang merupakan RuntimeExceptionpengecualian yang dirancang untuk membiarkan Anda membuang konsep pemeriksaan pengecualian ke luar jendela dengan membungkus IOExceptionpengecualian yang diperiksa yang disebabkan oleh kesalahan I / O (yang tidak menyebabkan IOErrorpengecualian, meskipun itu ada juga) yang sangat sulit untuk ditangani sehingga Anda membutuhkannya untuk tidak diperiksa.

Jawa terima kasih!

Boann
sumber
2
Sejauh yang bisa saya katakan, jawaban ini hanya mengatakan "Pengecualian Java berantakan" dengan penuh ironi, bisa dibilang lucu. Apa yang tampaknya tidak dilakukan adalah menjelaskan mengapa programmer cenderung menghindari mencoba memahami bagaimana hal-hal ini seharusnya bekerja. Juga, dalam kasus-kasus kehidupan nyata (setidaknya yang saya punya kesempatan untuk berurusan dengan), jika programmer tidak sengaja mencoba untuk membuat hidup mereka lebih sulit, pengecualian tidak sedekat rumit seperti yang telah Anda gambarkan.
CptBartender
5

Masalah

Masalah terburuk yang saya lihat dengan mekanisme penanganan pengecualian adalah ia memperkenalkan duplikasi kode dalam skala besar ! Mari kita jujur: Dalam sebagian besar proyek di 95% dari semua waktu yang benar-benar perlu dilakukan pengembang dengan pengecualian adalah untuk mengkomunikasikannya kepada pengguna (dan, dalam beberapa kasus, ke tim pengembangan juga, misalnya dengan mengirim e -mail dengan jejak stack). Jadi biasanya baris / blok kode yang sama digunakan di setiap tempat pengecualian ditangani.

Mari kita asumsikan bahwa kita melakukan pendataan sederhana di setiap blok tangkapan untuk beberapa jenis pengecualian yang diperiksa:

try{
   methodDeclaringCheckedException();
}catch(CheckedException e){
   logger.error(e);
}

Jika itu pengecualian umum mungkin bahkan ada beberapa ratus blok uji coba seperti itu dalam basis kode yang lebih besar. Sekarang mari kita asumsikan bahwa kita perlu memperkenalkan penanganan pengecualian berbasis popup alih-alih konsol logging atau mulai mengirim e-mail tambahan ke tim pengembangan.

Tunggu sebentar ... apakah kita benar-benar akan mengedit semua itu beberapa ratus lokasi dalam kode ?! Anda mendapatkan poin saya :-).

Solusinya

Apa yang kami lakukan untuk mengatasi masalah itu adalah memperkenalkan konsep penangan pengecualian (yang akan saya sebut sebagai EH) untuk memusatkan penanganan pengecualian. Untuk setiap kelas yang perlu menangani pengecualian, instance handler pengecualian disuntikkan oleh kerangka kerja Ketergantungan kami . Jadi pola khas penanganan pengecualian sekarang terlihat seperti ini:

try{
    methodDeclaringCheckedException();
}catch(CheckedException e){
    exceptionHandler.handleError(e);
}

Sekarang untuk menyesuaikan penanganan pengecualian kami, kami hanya perlu mengubah kode di satu tempat (kode EH).

Tentu saja untuk kasus yang lebih kompleks, kami dapat menerapkan beberapa subkelas EH dan fitur pengungkit yang diberikan kerangka kerja DI kepada kami. Dengan mengubah konfigurasi kerangka DI kita, kita dapat dengan mudah mengalihkan implementasi EH secara global atau memberikan implementasi spesifik EH ke kelas dengan kebutuhan penanganan pengecualian khusus (misalnya menggunakan anotasi Guice @Named).

Dengan begitu kita dapat membedakan perilaku penanganan pengecualian dalam pengembangan dan merilis versi aplikasi (mis. Pengembangan - mencatat kesalahan dan menghentikan aplikasi, menambah kesalahan dengan lebih detail dan membiarkan aplikasi melanjutkan eksekusi) tanpa usaha.

Satu hal terakhir

Terakhir, namun tidak kalah pentingnya, tampaknya sentralisasi yang sama dapat diperoleh dengan hanya melewatkan pengecualian "naik" sampai mereka tiba di beberapa kelas penanganan pengecualian tingkat atas. Tapi itu mengarah pada kekacauan kode dan tanda tangan metode kami dan memperkenalkan masalah pemeliharaan yang disebutkan oleh orang lain di utas ini.

Piotr Sobczyk
sumber
6
Pengecualian diciptakan untuk melakukan sesuatu yang bermanfaat dengannya. Menulisnya ke dalam file log atau membuat jendela yang cantik tidak berguna, karena masalah aslinya tidak dapat diselesaikan dengan ini. Melakukan sesuatu yang bermanfaat memerlukan mencoba strategi solusi yang berbeda. Contoh: Jika saya tidak bisa mendapatkan data dari server AI, coba di server B. Atau jika algoritma A menghasilkan heap overflow, saya mencoba algoritma B yang jauh lebih lambat tetapi mungkin berhasil.
ceving
2
@ceving Ya, itu semua baik dan benar dalam teori. Tapi sekarang mari kita kembali berlatih kata. Tolong jawab dengan jujur, seberapa sering Anda melakukannya dalam proyek kata sebenarnya? Bagian mana dari catchblok dalam proyek nyata ini melakukan sesuatu yang benar-benar "berguna" dengan pengecualian? 10% akan bagus. Masalah biasa yang menghasilkan pengecualian seperti mencoba membaca konfigurasi dari file yang tidak ada, OutOfMemoryErrors, NullPointerExceptions, kesalahan integritas kendala basis data dll, dll. Apakah Anda benar-benar berusaha memulihkan dari semua file tersebut? Saya tidak percaya kamu :). Seringkali tidak ada cara untuk pulih.
Piotr Sobczyk
3
@PiotrSobczyk: Jika suatu program mengambil tindakan sebagai akibat dari permintaan suer, dan operasi gagal dalam beberapa cara yang tidak merusak apa pun dalam keadaan sistem, memberi tahu pengguna bahwa operasi tidak dapat diselesaikan adalah sangat berguna cara menangani situasi. Kegagalan pengecualian terbesar dalam C # dan .net adalah bahwa tidak ada cara yang konsisten untuk memastikan apakah ada sesuatu di sistem yang mungkin rusak.
supercat
4
Benar, @PiotrSobczyk. Paling sering, satu-satunya tindakan yang benar untuk dilakukan sebagai tanggapan terhadap pengecualian adalah memutar balik transaksi & mengembalikan respons kesalahan. Gagasan "memecahkan pengecualian" menyiratkan pengetahuan & otoritas yang tidak (dan tidak seharusnya) kita miliki, dan melanggar enkapsulasi. Jika aplikasi kita bukan DB, kita seharusnya tidak mencoba untuk memperbaiki DB. Gagal bersih & menghindari penulisan data yang salah, ceving, cukup berguna .
Thomas W
@PiotrSobczyk Kemarin, saya berurusan dengan pengecualian "tidak bisa membaca objek" (yang hanya akan terjadi karena database yang mendasari telah diperbarui sebelum perangkat lunak - yang seharusnya tidak pernah terjadi tetapi kemungkinan karena kesalahan manusia) dengan gagal ke versi historis dari database dijamin untuk menunjuk ke versi objek yang lama.
Eponim
4

Anders berbicara tentang perangkap pengecualian yang dicek dan mengapa dia meninggalkannya dari C # dalam episode 97 radio Rekayasa Perangkat Lunak.

Chuck Conway
sumber
4

Langgan saya di c2.com sebagian besar masih tidak berubah dari bentuk aslinya: CheckedExceptionsAreIncompatiblewithVisitorPattern

Singkatnya:

Pola Pengunjung dan kerabatnya adalah kelas antarmuka di mana penelepon tidak langsung dan implementasi antarmuka keduanya tahu tentang pengecualian tetapi antarmuka dan penelepon langsung membentuk perpustakaan yang tidak bisa tahu.

Asumsi mendasar dari CheckedExceptions adalah semua pengecualian yang dinyatakan dapat dilemparkan dari titik mana pun yang memanggil metode dengan deklarasi itu. VisitorPattern mengungkapkan anggapan ini salah.

Hasil akhir dari pengecualian yang diperiksa dalam kasus-kasus seperti ini adalah banyak kode yang tidak berguna yang pada dasarnya menghapus kendala pengecualian yang diperiksa kompilator saat runtime.

Adapun masalah yang mendasarinya:

Gagasan umum saya adalah penangan tingkat atas perlu menafsirkan pengecualian dan menampilkan pesan kesalahan yang sesuai. Saya hampir selalu melihat salah satu pengecualian IO, pengecualian komunikasi (untuk beberapa alasan API membedakan), atau kesalahan fatal (bug program atau masalah parah pada server pendukung), jadi ini seharusnya tidak terlalu sulit jika kita mengizinkan jejak stack untuk yang parah masalah server.

Joshua
sumber
1
Anda harus memiliki sesuatu seperti DAGNodeException di antarmuka, kemudian menangkap IOException dan mengubahnya menjadi DAGNodeException: panggilan public void (DAGNode arg) melempar DAGNodeException;
TofuBeer
2
@ TahuBeer, itu maksud saya. Saya menemukan bahwa pengecualian yang selalu dibungkus dan dibuka lebih buruk daripada menghapus pengecualian yang diperiksa.
Joshua
2
Yah, kami sepenuhnya tidak setuju ... tetapi artikel Anda masih tidak menjawab pertanyaan mendasar yang mendasar tentang bagaimana Anda menghentikan aplikasi Anda dari menampilkan jejak stack kepada pengguna ketika pengecualian runtime dilemparkan.
TofuBeer
1
@TofuBeer - Ketika gagal, memberi tahu pengguna itu gagal benar! Apa alternatif Anda, selain untuk "menutupi" kegagalan dengan data 'nol' atau tidak lengkap / salah? Berpura-pura berhasil adalah kebohongan, yang hanya memperburuk keadaan. Dengan pengalaman 25 tahun dalam sistem dengan keandalan tinggi, coba lagi logika hanya harus digunakan dengan hati-hati & jika perlu. Saya juga berharap seorang Pengunjung akan gagal lagi, tidak peduli berapa kali Anda mencoba kembali. Kecuali jika Anda menerbangkan pesawat, bertukar ke versi kedua dari algoritma yang sama tidak praktis & tidak masuk akal (dan mungkin bisa gagal).
Thomas W
4

Untuk mencoba menjawab pertanyaan yang tidak dijawab:

Jika Anda melempar subclass RuntimeException alih-alih subclass Exception maka bagaimana Anda tahu apa yang seharusnya Anda tangkap?

Pertanyaannya berisi IMHO penalaran bermata-mata. Hanya karena API memberi tahu Anda apa yang dilemparnya tidak berarti Anda menghadapinya dengan cara yang sama dalam semua kasus. Dengan kata lain, pengecualian yang perlu Anda tangkap bervariasi tergantung pada konteks di mana Anda menggunakan komponen yang melempar pengecualian.

Sebagai contoh:

Jika saya menulis tester koneksi untuk database, atau sesuatu untuk memeriksa validitas pengguna memasuki XPath, maka saya mungkin ingin menangkap dan melaporkan semua pengecualian yang dicentang dan tidak dicentang yang dilemparkan oleh operasi.

Namun, jika saya sedang menulis mesin pengolah, saya kemungkinan akan memperlakukan XPathException (dicentang) dengan cara yang sama seperti NPE: Saya akan membiarkannya naik ke atas utas pekerja, lewati sisa batch itu, log masalah (atau kirimkan ke departemen dukungan untuk diagnosis) dan tinggalkan umpan balik bagi pengguna untuk menghubungi dukungan.

Dave Elton
sumber
2
Persis. Mudah dan langsung, seperti penanganan pengecualian. Seperti kata Dave, penanganan pengecualian yang benar biasanya dilakukan pada tingkat tinggi . "Melempar lebih awal, terlambat" adalah prinsipnya. Pengecualian diperiksa membuat itu sulit.
Thomas W
4

Artikel ini adalah bagian terbaik dari teks tentang penanganan pengecualian di Jawa yang pernah saya baca.

Ia lebih memilih tidak terkecuali pada pengecekan terkecuali, tetapi pilihan ini dijelaskan dengan sangat baik dan berdasarkan pada argumen yang kuat.

Saya tidak ingin mengutip terlalu banyak konten artikel di sini (yang terbaik adalah membacanya secara keseluruhan) tetapi mencakup sebagian besar argumen pendukung pengecualian yang tidak diperiksa dari utas ini. Terutama argumen ini (yang tampaknya cukup populer) dibahas:

Ambil kasus ketika pengecualian dilemparkan di suatu tempat di bagian bawah lapisan API dan hanya menggelegak karena tidak ada yang tahu bahwa kesalahan ini mungkin terjadi, ini meskipun jenis kesalahan yang sangat masuk akal ketika kode panggilan melemparkannya (FileNotFoundException misalnya sebagai lawan dari VogonsTrashingEarthExcept ... dalam hal ini tidak masalah jika kita menanganinya atau tidak karena tidak ada yang tersisa untuk menanganinya).

Penulis "tanggapan":

Benar-benar salah untuk mengasumsikan bahwa semua pengecualian runtime tidak boleh ditangkap dan dibiarkan merambat ke bagian paling atas aplikasi. (...) Untuk setiap kondisi luar biasa yang harus ditangani dengan jelas - oleh sistem / persyaratan bisnis - programmer harus memutuskan di mana harus menangkapnya dan apa yang harus dilakukan setelah kondisinya tertangkap. Ini harus dilakukan secara ketat sesuai dengan kebutuhan aplikasi yang sebenarnya, bukan berdasarkan peringatan kompiler. Semua kesalahan lain harus diizinkan untuk menyebar secara bebas ke penangan teratas di mana mereka akan dicatat dan tindakan anggun (mungkin, penghentian) akan dilakukan.

Dan pemikiran atau artikel utamanya adalah:

Ketika datang ke penanganan kesalahan dalam perangkat lunak, satu-satunya asumsi yang aman dan benar yang mungkin pernah dibuat adalah bahwa kegagalan dapat terjadi pada setiap subrutin atau modul yang ada!

Jadi jika " tidak ada yang tahu bahwa kesalahan ini mungkin terjadi " ada sesuatu yang salah dengan proyek itu. Pengecualian seperti itu harus ditangani oleh setidaknya penangan pengecualian yang paling umum (mis. Yang menangani semua Pengecualian yang tidak ditangani oleh penangan yang lebih spesifik) seperti yang disarankan penulis.

Sedih sekali tidak banyak orang yang menemukan artikel hebat ini :-(. Saya merekomendasikan dengan sepenuh hati semua orang yang ragu pendekatan mana yang lebih baik untuk mengambil waktu dan membacanya.

Piotr Sobczyk
sumber
4

Pengecualian yang diperiksa adalah, dalam bentuk aslinya, upaya untuk menangani kemungkinan daripada kegagalan. Tujuan terpuji adalah untuk menyoroti titik-titik tertentu yang dapat diprediksi (tidak dapat terhubung, file tidak ditemukan, dll) & memastikan pengembang menangani ini.

Apa yang tidak pernah dimasukkan dalam konsep asli, adalah untuk memaksa berbagai kegagalan sistemik & tidak dapat dipulihkan untuk diumumkan. Kegagalan ini tidak pernah benar untuk dinyatakan sebagai pengecualian yang diperiksa.

Kegagalan umumnya dimungkinkan dalam kode, dan kontainer EJB, web & Swing / AWT sudah memenuhi ini dengan menyediakan penangan pengecualian "permintaan gagal" terluar. Strategi paling dasar yang benar adalah untuk mengembalikan transaksi & mengembalikan kesalahan.

Satu poin penting, adalah bahwa runtime & pengecekan pengecualian secara fungsional setara.Tidak ada penanganan atau pemulihan yang dapat mengecek pengecualian, bahwa pengecualian runtime tidak bisa.

Argumen terbesar terhadap pengecualian “dicentang” adalah bahwa sebagian besar pengecualian tidak dapat diperbaiki. Fakta sederhananya adalah, kita tidak memiliki kode / subsistem yang rusak. Kami tidak dapat melihat implementasinya, kami tidak bertanggung jawab untuk itu, dan tidak bisa memperbaikinya.

Jika aplikasi kita bukan DB .. kita seharusnya tidak mencoba dan memperbaiki DB. Itu akan melanggar prinsip enkapsulasi .

Terutama bermasalah adalah bidang JDBC (SQLException) dan RMI untuk EJB (RemoteException). Alih-alih mengidentifikasi kontinjensi yang dapat diperbaiki sesuai dengan konsep "pengecualian yang diperiksa" yang asli, masalah keandalan sistemik yang meresap ini, yang sebenarnya tidak dapat diperbaiki, harus dinyatakan secara luas.

Kelemahan parah lain dalam desain Java, adalah bahwa penanganan pengecualian harus ditempatkan dengan benar pada tingkat "bisnis" atau "permintaan" setinggi mungkin. Prinsipnya di sini adalah "melempar lebih awal, terlambat". Pengecualian diperiksa tidak banyak tetapi menghalangi ini.

Kami memiliki masalah nyata di Jawa yang membutuhkan ribuan blok coba-coba-coba-lakukan, dengan proporsi yang signifikan (40% +) yang salah kode. Hampir tidak ada yang menerapkan penanganan atau keandalan yang asli, tetapi memaksakan overhead pengkodean utama.

Terakhir, "pengecualian yang diperiksa" cukup tidak kompatibel dengan pemrograman fungsional FP.

Desakan mereka pada "segera menangani" bertentangan dengan praktik terbaik penanganan pengecualian "catch late", dan setiap struktur FP yang abstrak loop / atau aliran kontrol.

Banyak orang berbicara tentang "menangani" pengecualian yang diperiksa, tetapi berbicara melalui topi mereka. Melanjutkan setelah kegagalan dengan data nol, tidak lengkap, atau salah untuk berpura - pura sukses tidak menangani apa pun. Ini rekayasa / malpraktek keandalan bentuk terendah.

Gagal bersih, adalah strategi paling dasar yang benar untuk menangani pengecualian. Meluruskan kembali transaksi, mencatat kesalahan & melaporkan respons "kegagalan" kepada pengguna adalah praktik yang baik - dan yang paling penting, mencegah kesalahan data bisnis yang dilakukan ke basis data.

Strategi lain untuk penanganan pengecualian adalah "coba lagi", "sambungkan kembali" atau "lewati", di tingkat bisnis, subsistem, atau permintaan. Semua ini adalah strategi keandalan umum, dan bekerja dengan baik / lebih baik dengan pengecualian runtime.

Terakhir, jauh lebih baik gagal, daripada dijalankan dengan data yang salah. Melanjutkan akan menyebabkan kesalahan sekunder, jauh dari penyebab awal & lebih sulit untuk di-debug; atau pada akhirnya akan menghasilkan data yang salah dilakukan. Orang dipecat karena itu.

Lihat:
- http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake/

Thomas W
sumber
1
Maksud saya adalah gagal dengan benar sebagai strategi umum. Pengecualian yang tidak dicentang membantu karena tidak memaksa blok penahan untuk ditempatkan. Penangkapan & penebangan kesalahan kemudian dapat diserahkan kepada beberapa penangan terluar, daripada salah kode ribuan kali di seluruh basis kode (yang sebenarnya menyembunyikan bug) . Untuk kegagalan sewenang-wenang, pengecualian yang tidak diperiksa adalah yang paling benar. Kontinjensi - hasil yang dapat diprediksi seperti dana yang tidak mencukupi - adalah satu-satunya pengecualian yang layak diperiksa.
Thomas W
1
Jawaban saya sudah di atas membahas ini. Pertama dan terutama, 1) Penangan kegagalan terluar harus menangkap segalanya. Di luar itu, hanya untuk situs tertentu yang diidentifikasi, 2) Kontinjensi yang diharapkan khusus dapat ditangkap dan ditangani - di lokasi terdekat mereka dilempar. Itu berarti file tidak ditemukan, dana tidak mencukupi dll pada titik ini dapat dipulihkan dari - tidak lebih tinggi. Prinsip enkapsulasi berarti bahwa lapisan luar tidak dapat / tidak seharusnya bertanggung jawab untuk memahami / pulih dari kegagalan jauh di dalam. Ketiga, 3) Segala sesuatu yang lain harus dibuang ke luar - tidak dicentang jika mungkin.
Thomas W
1
Pawang terluar menangkap Pengecualian, mencatatnya, dan mengembalikan respons "gagal" atau menampilkan dialog kesalahan. Sangat sederhana, tidak sulit untuk didefinisikan sama sekali. Intinya adalah bahwa setiap pengecualian yang tidak secara langsung & dapat dipulihkan secara lokal, merupakan kegagalan yang tidak dapat dipulihkan karena prinsip enkapsulasi. Jika kode yang ingin diketahui tidak dapat memulihkannya, maka keseluruhan permintaan gagal dengan baik & benar. Ini adalah cara yang tepat untuk melakukannya dengan benar.
Thomas W
1
Salah. Tugas pawang terluar adalah untuk gagal bersih & mencatat kesalahan pada batas 'permintaan'. Permintaan rusak gagal dengan benar, pengecualian dilaporkan, utas dapat melanjutkan untuk melayani permintaan berikutnya. Handler terluar seperti itu adalah fitur standar dalam wadah Tomcat, AWT, Spring, EJB & utas 'utama' Java.
Thomas W
1
Mengapa berbahaya melaporkan "bug asli" di batas permintaan atau penangan terluar ??? Saya sering bekerja dalam integrasi & keandalan sistem, di mana rekayasa keandalan yang benar sebenarnya penting, dan menggunakan pendekatan "pengecualian yang tidak dicentang" untuk melakukannya. Saya tidak benar-benar yakin apa yang sebenarnya Anda debat - sepertinya Anda mungkin ingin menghabiskan 3 bulan dengan cara pengecualian yang tidak dicentang, merasakannya, dan kemudian mungkin kita bisa membahas lebih lanjut. Terima kasih.
Thomas W
4

Seperti yang telah dinyatakan orang, pengecualian yang diperiksa tidak ada dalam bytecode Java. Mereka hanyalah mekanisme kompiler, tidak seperti pemeriksaan sintaksis lainnya. Aku lihat pengecualian diperiksa banyak seperti saya melihat compiler mengeluh tentang bersyarat berlebihan: if(true) { a; } b;. Itu membantu tapi saya mungkin sengaja melakukan ini, jadi biarkan saya mengabaikan peringatan Anda.

Faktanya adalah, Anda tidak akan bisa memaksa setiap programmer untuk "melakukan hal yang benar" jika Anda menerapkan pengecualian yang dicek dan semua orang sekarang kerusakan kolateral yang hanya membenci Anda karena aturan yang Anda buat.

Perbaiki program yang buruk di luar sana! Jangan mencoba memperbaiki bahasa untuk tidak mengizinkannya! Bagi kebanyakan orang, "melakukan sesuatu tentang pengecualian" sebenarnya hanya memberi tahu pengguna tentang hal itu. Saya dapat memberi tahu pengguna tentang pengecualian yang tidak dicentang juga, jadi jauhkan kelas pengecualian Anda dari API saya.

Martin
sumber
Benar, saya hanya ingin menekankan perbedaan antara kode yang tidak terjangkau (yang menghasilkan kesalahan) dan persyaratan dengan hasil yang dapat diprediksi. Saya akan menghapus komentar ini nanti.
Holger
3

Masalah dengan pengecualian yang diperiksa adalah bahwa pengecualian sering dilampirkan pada metode antarmuka jika bahkan satu implementasi dari antarmuka itu menggunakannya.

Masalah lain dengan pengecualian yang diperiksa adalah bahwa mereka cenderung disalahgunakan. Contoh sempurna dari ini di java.sql.Connection's close()metode. Itu bisa melempar SQLException, meskipun Anda sudah secara eksplisit menyatakan bahwa Anda sudah selesai dengan Koneksi. Informasi apa yang bisa ditutup () mungkin menyampaikan bahwa Anda peduli?

Biasanya, ketika saya menutup () koneksi *, tampilannya seperti ini:

try {
    conn.close();
} catch (SQLException ex) {
    // Do nothing
}

Juga, jangan mulai dengan berbagai metode parse dan NumberFormatException ... .NET's TryParse, yang tidak melempar pengecualian, jauh lebih mudah untuk digunakan karena harus kembali ke Jawa (kami menggunakan Java dan C # tempat saya bekerja).

*Sebagai komentar tambahan, Sambungan PooledConnection.close () bahkan tidak menutup koneksi, tetapi Anda masih harus menangkap SQLException karena itu menjadi pengecualian yang diperiksa.

Powerlord
sumber
2
Benar, salah satu driver bisa ... pertanyaannya adalah "mengapa programmer harus peduli?" karena dia sudah selesai mengakses database. Dokumen bahkan memperingatkan Anda bahwa Anda harus selalu melakukan () atau mengembalikan () transaksi saat ini sebelum memanggil tutup ().
Powerlord
Banyak orang berpikir bahwa menutup file tidak dapat memberikan pengecualian ... stackoverflow.com/questions/588546/… apakah Anda 100% yakin bahwa tidak ada kasus yang penting?
TofuBeer
Saya 100% yakin bahwa tidak ada kasus yang akan peduli dan bahwa penelepon tidak akan dimasukkan ke dalam mencoba / menangkap.
Martin
1
Contoh yang sangat baik dengan koneksi penutupan, Martin! Saya hanya dapat mengulangi kata-kata Anda: Jika kami hanya secara eksplisit menyatakan bahwa kami sudah selesai dengan koneksi, mengapa harus repot-repot apa yang terjadi ketika kami menutupnya. Ada lebih banyak kasus seperti ini bahwa programmer tidak terlalu peduli jika pengecualian terjadi dan dia benar tentang itu.
Piotr Sobczyk
1
@PiotrSobczyk: Beberapa driver SQL akan mengomel jika seseorang menutup koneksi setelah memulai transaksi tetapi tidak mengkonfirmasikannya atau mengembalikannya. IMHO, squawking lebih baik daripada diam-diam mengabaikan masalah, setidaknya dalam kasus di mana squawking tidak akan menyebabkan pengecualian lain hilang.
supercat
3

Programmer perlu mengetahui semua pengecualian yang dilemparkan suatu metode, agar dapat menggunakannya dengan benar. Jadi, mengalahkannya hanya dengan beberapa pengecualian tidak selalu membantu programmer yang ceroboh menghindari kesalahan.

Manfaat rampingnya melebihi biaya yang membebani (terutama dalam basis kode yang lebih besar dan kurang fleksibel di mana terus-menerus memodifikasi tanda tangan antarmuka tidak praktis).

Analisis statis dapat menjadi hal yang bagus, tetapi analisis statis yang benar-benar andal seringkali tidak fleksibel menuntut kerja keras dari programmer. Ada perhitungan biaya-manfaat, dan bilah perlu ditetapkan tinggi untuk cek yang mengarah ke kesalahan waktu kompilasi. Akan lebih bermanfaat jika IDE mengambil peran untuk mengkomunikasikan pengecualian yang mungkin dilemparkan metode (termasuk yang tidak dapat dihindari). Meskipun mungkin tidak akan dapat diandalkan tanpa deklarasi pengecualian yang dipaksakan, sebagian besar pengecualian masih akan dideklarasikan dalam dokumentasi, dan keandalan peringatan IDE tidak begitu penting.

Aleksandr Dubinsky
sumber
2

Saya pikir ini adalah pertanyaan yang sangat bagus dan sama sekali tidak argumentatif. Saya pikir perpustakaan pihak ke-3 harus (secara umum) membuang pengecualian yang tidak dicentang . Ini berarti bahwa Anda dapat mengisolasi dependensi Anda pada perpustakaan (yaitu Anda tidak harus melemparkan kembali pengecualian atau melempar Exception- biasanya praktik buruk). Lapisan DAO Spring adalah contoh yang sangat baik untuk ini.

Di sisi lain, pengecualian dari inti API Java harus di be umumnya diperiksa jika mereka bisa pernah ditangani. Ambil FileNotFoundExceptionatau (favorit saya) InterruptedException. Kondisi-kondisi ini hampir selalu harus ditangani secara khusus (yaitu reaksi Anda terhadap suatu InterruptedExceptiontidak sama dengan reaksi Anda terhadap suatu IllegalArgumentException). Fakta bahwa pengecualian Anda diperiksa memaksa pengembang untuk memikirkan apakah suatu kondisi dapat ditangani atau tidak. (Yang mengatakan, aku jarang terlihat InterruptedExceptionditangani dengan benar!)

Satu hal lagi - a RuntimeExceptiontidak selalu "di mana pengembang mendapat kesalahan". Pengecualian argumen ilegal dilemparkan ketika Anda mencoba dan membuat enumpenggunaan valueOfdan tidak ada enumnama itu. Ini belum tentu kesalahan oleh pengembang!

oxbow_lakes
sumber
1
Ya, itu adalah kesalahan oleh pengembang. Mereka jelas tidak menggunakan nama yang tepat, jadi mereka harus kembali dan memperbaiki kode mereka.
AxiomaticNexus
@AxiomaticNexus Tidak ada pengembang waras yang menggunakan enumnama anggota, hanya karena mereka menggunakan enumobjek. Jadi nama yang salah hanya bisa datang dari luar, baik itu file impor atau apa pun. Salah satu cara yang mungkin untuk berurusan dengan nama-nama tersebut adalah memanggil MyEnum#valueOfdan menangkap IAE. Cara lain adalah dengan menggunakan pra-diisi Map<String, MyEnum>, tetapi ini adalah detail implementasi.
maaartinus
@maaartinus Ada beberapa kasus di mana nama anggota enum digunakan tanpa string yang datang dari luar. Misalnya, ketika Anda ingin mengulang semua anggota secara dinamis untuk melakukan sesuatu dengan masing-masing anggota. Lebih lanjut, apakah string berasal dari luar atau tidak tidak relevan. Pengembang memiliki semua informasi yang mereka butuhkan, untuk mengetahui apakah meneruskan x string ke "MyEnum # valueOf" akan menghasilkan kesalahan sebelum meneruskannya. Melewatkan x string ke "MyEnum # valueOf" ketika itu akan menyebabkan kesalahan, jelas akan menjadi kesalahan pada bagian pengembang.
AxiomaticNexus
2

Berikut ini satu argumen terhadap pengecualian yang diperiksa (dari joelonsoftware.com):

Alasannya adalah bahwa saya menganggap pengecualian tidak lebih baik daripada "goto", dianggap berbahaya sejak 1960-an, karena mereka membuat lompatan tiba-tiba dari satu titik kode ke titik lainnya. Bahkan mereka secara signifikan lebih buruk daripada goto:

  • Mereka tidak terlihat dalam kode sumber. Melihat sekumpulan kode, termasuk fungsi yang bisa atau tidak melempar pengecualian, tidak ada cara untuk melihat pengecualian mana yang dilempar dan dari mana. Ini berarti bahwa pemeriksaan kode yang teliti sekalipun tidak mengungkapkan potensi bug.
  • Mereka membuat terlalu banyak kemungkinan titik keluar untuk suatu fungsi. Untuk menulis kode yang benar, Anda benar-benar harus memikirkan setiap jalur kode yang mungkin melalui fungsi Anda. Setiap kali Anda memanggil fungsi yang dapat menimbulkan pengecualian dan tidak langsung menangkapnya, Anda menciptakan peluang untuk bug kejutan yang disebabkan oleh fungsi yang diakhiri secara tiba-tiba, meninggalkan data dalam keadaan tidak konsisten, atau jalur kode lain yang tidak Anda temui. memikirkan tentang.
menemukan
sumber
3
+1 Namun Anda mungkin ingin meringkas argumen dalam jawaban Anda? Mereka seperti goto yang tak terlihat dan pintu keluar awal untuk rutinitas Anda, tersebar di seluruh program.
MarkJ
13
Itu lebih merupakan argumen terhadap Pengecualian secara umum.
Ionuț G. Stan
10
sudahkah Anda membaca artikelnya !! Pertama dia berbicara tentang pengecualian secara umum, kedua bagian "Mereka tidak terlihat dalam kode sumber" berlaku khusus untuk pengecualian UNCHECKED. Ini adalah seluruh titik pengecualian diperiksa ... sehingga Anda TAHU kode apa melempar apa di mana
Newtopian
2
@ Eva Mereka tidak sama. Dengan pernyataan goto Anda dapat melihat gotokata kunci. Dengan loop Anda dapat melihat tanda kurung kurawal atau breakatau continuekata kunci. Semuanya melompat ke titik dalam metode saat ini. Tetapi Anda tidak selalu dapat melihat throw, karena seringkali itu bukan dalam metode saat ini tetapi dalam metode lain yang disebutnya (mungkin secara tidak langsung.)
finnw
5
@finnw Fungsi itu sendiri adalah bentuk goto. Anda biasanya tidak tahu fungsi apa yang dipanggil. Jika Anda diprogram tanpa fungsi, Anda tidak akan memiliki masalah dengan pengecualian yang tidak terlihat. Yang berarti bahwa masalahnya tidak secara spesifik terkait dengan pengecualian, dan bukan argumen yang valid terhadap pengecualian secara umum. Anda bisa mengatakan kode kesalahan lebih cepat, bisa dikatakan monad lebih bersih, tetapi argumen goto konyol.
Eva
2

Bukti bagus bahwa Pengecualian Tidak diperlukan adalah:

  1. Banyak kerangka kerja yang berfungsi untuk Java. Seperti Spring yang membungkus pengecualian JDBC dengan pengecualian yang tidak dicentang, melemparkan pesan ke log
  2. Banyak bahasa yang muncul setelah java, bahkan di atas platform java - mereka tidak menggunakannya
  3. Memeriksa pengecualian, ini adalah semacam prediksi tentang bagaimana klien akan menggunakan kode yang melempar pengecualian. Tetapi pengembang yang menulis kode ini tidak akan pernah tahu tentang sistem dan bisnis tempat klien kode bekerja. Sebagai contoh metode Interfcace yang memaksa untuk membuang pengecualian yang diperiksa. Ada 100 implementasi atas sistem, 50 atau bahkan 90 implementasi tidak membuang pengecualian ini, tetapi klien masih harus menangkap pengecualian ini jika ia merujuk ke antarmuka itu. 50 atau 90 implementasi tersebut cenderung menangani pengecualian di dalam dirinya sendiri, menempatkan pengecualian pada log (dan ini adalah perilaku yang baik bagi mereka). Apa yang harus kita lakukan dengan itu? Saya lebih baik memiliki beberapa logika latar belakang yang akan melakukan semua pekerjaan itu - mengirim pesan ke log. Dan jika saya, sebagai klien kode, akan merasa saya perlu menangani pengecualian - saya akan melakukannya.
  4. Contoh lain ketika saya bekerja dengan I / O di java, itu memaksa saya untuk memeriksa semua pengecualian, jika file tidak ada? apa yang harus saya lakukan dengan itu? Jika tidak ada, sistem tidak akan pergi ke langkah berikutnya. Klien dari metode ini, tidak akan mendapatkan konten yang diharapkan dari file itu - ia dapat menangani Runtime Exception, jika tidak saya harus terlebih dahulu memeriksa Checked Exception, memasukkan pesan ke log, lalu membuang pengecualian dari form metode. Tidak ... tidak - saya lebih baik melakukannya secara otomatis dengan RuntimeEception, yang melakukannya / menyala secara otomatis. Tidak ada gunanya menanganinya secara manual - Saya akan senang melihat pesan kesalahan di log (AOP dapat membantu dengan itu .. sesuatu yang memperbaiki java). Jika, pada akhirnya, saya menganggap sistem harus menampilkan pesan pop-up kepada pengguna akhir - saya akan menunjukkannya, bukan masalah.

Saya senang jika java akan memberi saya pilihan apa yang harus digunakan, ketika bekerja dengan core libs, seperti I / O. Like menyediakan dua salinan dari kelas yang sama - satu dibungkus dengan RuntimeEception. Lalu kita bisa membandingkan apa yang akan digunakan orang . Namun, untuk saat ini, banyak orang akan lebih memilih kerangka kerja di atas pada java, atau bahasa yang berbeda. Seperti Scala, JRuby terserah. Banyak yang percaya bahwa SUN itu benar.

ses
sumber
1
Daripada memiliki dua versi kelas, harus ada cara ringkas untuk menentukan tidak ada panggilan metode yang dilakukan oleh blok kode tidak diharapkan untuk melempar pengecualian dari jenis tertentu, dan bahwa pengecualian semacam itu harus dibungkus melalui beberapa cara yang ditentukan dan rethrown (secara default, buat yang baru RuntimeExceptiondengan pengecualian dalam yang sesuai). Sangat disayangkan bahwa lebih singkat untuk memiliki metode luar throwspengecualian dari metode batin, daripada memilikinya membungkus pengecualian dari metode batin, ketika tindakan yang terakhir akan lebih sering benar.
supercat
2

Satu hal penting yang tidak disebutkan adalah bagaimana hal itu mengganggu antarmuka dan ekspresi lambda.

Katakanlah Anda mendefinisikan a MyAppException extends Exception. Ini adalah pengecualian tingkat atas yang diwarisi oleh semua pengecualian yang dilemparkan oleh aplikasi Anda. Setiap metode menyatakan throws MyAppExceptionmana yang sedikit nuissance, tetapi dapat dikelola. Exception handler mencatat pengecualian dan memberi tahu pengguna entah bagaimana.

Semua tampak OK sampai Anda ingin mengimplementasikan beberapa antarmuka yang bukan milik Anda. Jelas itu tidak menyatakan niat untuk melempar MyApException, jadi kompiler tidak memungkinkan Anda untuk melemparkan pengecualian dari sana.

Namun, jika pengecualian Anda meluas RuntimeException, tidak akan ada masalah dengan antarmuka. Anda dapat secara sukarela menyebutkan pengecualian di JavaDoc jika Anda mau. Tapi selain itu hanya diam-diam menggelegak melalui apa pun, untuk terjebak dalam lapisan penanganan pengecualian Anda.

Vlasec
sumber
1

Kami telah melihat beberapa referensi ke kepala arsitek C #.

Berikut ini sudut pandang alternatif dari seorang pria Jawa tentang kapan harus menggunakan pengecualian yang diperiksa. Dia mengakui banyak hal negatif yang disebutkan orang lain: Pengecualian Efektif

Jacob Toronto
sumber
2
Masalah dengan pengecualian yang diperiksa di Jawa berasal dari masalah yang lebih dalam, yaitu terlalu banyak informasi yang dienkapsulasi dalam JENIS pengecualian, alih-alih dalam properti instance. Akan bermanfaat untuk memeriksa pengecualian, jika "diperiksa" adalah atribut situs lempar / tangkap, dan jika seseorang dapat secara deklaratif menentukan apakah pengecualian yang diperiksa yang lolos dari blok kode harus tetap sebagai pengecualian yang diperiksa, atau dilihat oleh blok penutup apa pun sebagai pengecualian yang tidak dicentang; demikian juga blok tangkap harus dapat menentukan bahwa mereka hanya ingin pengecualian diperiksa.
supercat
1
Misalkan rutinitas pencarian kamus ditentukan untuk membuang beberapa jenis pengecualian tertentu jika suatu upaya dilakukan untuk mengakses kunci yang tidak ada. Mungkin masuk akal jika kode klien menangkap pengecualian semacam itu. Namun, jika beberapa metode yang digunakan oleh rutin pencarian terjadi untuk melemparkan jenis pengecualian yang sama dengan cara yang tidak diharapkan oleh rutinitas pencarian, kode klien mungkin tidak boleh menangkapnya. Memiliki check-ness menjadi milik instance pengecualian , membuang situs, dan menangkap situs, akan menghindari masalah tersebut. Klien akan menangkap pengecualian 'dicentang' dari jenis itu, menghindari yang tidak terduga.
supercat
0

Saya sudah membaca banyak tentang penanganan pengecualian, bahkan jika (sebagian besar waktu) saya tidak bisa mengatakan saya senang atau sedih tentang keberadaan pengecualian yang diperiksa, inilah yang saya ambil: pengecualian yang diperiksa dalam kode tingkat rendah (IO, networking , OS, dll) dan pengecualian yang tidak dicentang di API tingkat tinggi / tingkat aplikasi.

Bahkan jika tidak ada begitu mudah untuk menarik garis di antara mereka, saya menemukan bahwa itu benar-benar menjengkelkan / sulit untuk mengintegrasikan beberapa API / perpustakaan di bawah atap yang sama tanpa membungkus semua waktu banyak pengecualian diperiksa tetapi di sisi lain, kadang-kadang berguna / lebih baik dipaksa untuk menangkap beberapa pengecualian dan memberikan yang berbeda yang lebih masuk akal dalam konteks saat ini.

Proyek yang saya kerjakan membutuhkan banyak perpustakaan dan mengintegrasikannya di bawah API yang sama, API yang sepenuhnya didasarkan pada pengecualian yang tidak dicentang. Kerangka kerja ini menyediakan API tingkat tinggi yang pada awalnya penuh dengan pengecualian yang diperiksa dan hanya beberapa yang tidak dicentang. pengecualian (Pengecualian Inisialisasi, ConfigurationException, dll) dan saya harus mengatakan itu tidak sangat ramah . Sebagian besar waktu Anda harus menangkap atau melempar pengecualian yang Anda tidak tahu bagaimana menangani, atau Anda bahkan tidak peduli (jangan bingung dengan Anda harus mengabaikan pengecualian), terutama di sisi klien di mana satu klik bisa melempar 10 kemungkinan (dicentang) pengecualian.

Versi saat ini (versi ke-3) hanya menggunakan pengecualian yang tidak dicentang, dan memiliki pengendali pengecualian global yang bertanggung jawab untuk menangani apa pun yang tidak tertangkap. API menyediakan cara untuk mendaftarkan penangan pengecualian, yang akan memutuskan apakah pengecualian dianggap sebagai kesalahan (sebagian besar waktu ini terjadi) yang berarti mencatat & memberi tahu seseorang, atau dapat berarti sesuatu yang lain - seperti pengecualian ini, AbortException, yang berarti memutus utas eksekusi saat ini dan jangan mencatat kesalahan apa pun karena memang diinginkan untuk tidak melakukannya. Tentu saja, untuk menyelesaikan semua utas khusus harus menangani metode run () dengan tangkapan {...} tangkapan (semua).

public void run () {

try {
     ... do something ...
} catch (Throwable throwable) {
     ApplicationContext.getExceptionService().handleException("Handle this exception", throwable);
}

}

Ini tidak perlu jika Anda menggunakan WorkerService untuk menjadwalkan pekerjaan (Runnable, Callable, Worker), yang menangani segalanya untuk Anda.

Tentu saja ini hanya pendapat saya, dan itu mungkin bukan yang benar, tetapi sepertinya pendekatan yang baik untuk saya. Saya akan melihat setelah saya akan merilis proyek jika apa yang saya pikir itu baik untuk saya, itu baik untuk orang lain juga ... :)

adrian.tarau
sumber