Kami menggunakan SonarQube untuk menganalisis kode Java kami dan memiliki aturan ini (set ke kritis):
Metode publik harus membuang paling banyak satu pengecualian yang diperiksa
Menggunakan pengecualian yang diperiksa memaksa pemanggil metode untuk menangani kesalahan, baik dengan menyebarkannya atau dengan menanganinya. Ini membuat pengecualian tersebut sepenuhnya menjadi bagian dari API metode ini.
Agar kompleksitas penelepon masuk akal, metode tidak boleh melempar lebih dari satu jenis pengecualian yang diperiksa. "
Bit lain di Sonar memiliki ini :
Metode publik harus membuang paling banyak satu pengecualian yang diperiksa
Menggunakan pengecualian yang diperiksa memaksa pemanggil metode untuk menangani kesalahan, baik dengan menyebarkannya atau dengan menanganinya. Ini membuat pengecualian tersebut sepenuhnya menjadi bagian dari API metode ini.
Agar kompleksitas penelepon masuk akal, metode tidak boleh melempar lebih dari satu jenis pengecualian yang diperiksa.
Kode berikut:
public void delete() throws IOException, SQLException { // Non-Compliant /* ... */ }
harus di refactored menjadi:
public void delete() throws SomeApplicationLevelException { // Compliant /* ... */ }
Metode utama tidak dicentang oleh aturan ini dan diizinkan untuk melemparkan beberapa pengecualian.
Saya tidak pernah menemukan aturan / rekomendasi ini dalam bacaan saya tentang penanganan pengecualian dan telah mencoba menemukan beberapa standar, diskusi, dll. Tentang topik tersebut. Satu-satunya hal yang saya temukan adalah ini dari CodeRach: Berapa banyak pengecualian yang harus dilontarkan metode paling banyak?
Apakah ini standar yang diterima dengan baik?
sumber
Jawaban:
Mari kita pertimbangkan situasi di mana Anda memiliki kode yang disediakan:
Bahayanya di sini adalah bahwa kode yang Anda tulis untuk dihubungi
delete()
akan terlihat seperti:Ini juga buruk. Dan itu akan ditangkap dengan aturan lain bahwa bendera menangkap kelas Exception dasar.
Kuncinya adalah untuk tidak menulis kode yang membuat Anda ingin menulis kode yang buruk di tempat lain.
Aturan yang Anda temui adalah aturan yang agak umum. Checkstyle memilikinya dalam aturan desainnya:
Ini persis menggambarkan masalah dan apa masalahnya dan mengapa Anda tidak harus melakukannya. Ini adalah standar yang diterima dengan baik bahwa banyak alat analisis statis akan mengidentifikasi dan menandai.
Dan sementara Anda dapat melakukannya sesuai dengan desain bahasa, dan mungkin ada saat-saat itu adalah hal yang benar untuk dilakukan, itu adalah sesuatu yang harus Anda lihat dan segera lanjutkan "um, mengapa saya melakukan ini?" Mungkin dapat diterima untuk kode internal di mana setiap orang cukup disiplin untuk tidak pernah
catch (Exception e) {}
, tetapi lebih sering daripada tidak saya pernah melihat orang mengambil jalan pintas terutama dalam situasi internal.Jangan membuat orang menggunakan kelas Anda ingin menulis kode yang buruk.
Saya harus menunjukkan bahwa pentingnya ini dikurangi dengan Java SE 7 dan kemudian karena pernyataan tangkap tunggal dapat menangkap beberapa pengecualian ( Menangkap Beberapa Jenis Pengecualian dan Memotong Kembali Pengecualian dengan Memeriksa Jenis yang Ditingkatkan dari Oracle).
Dengan Java 6 dan sebelumnya, Anda akan memiliki kode yang terlihat seperti:
dan
atau
Tak satu pun dari opsi ini dengan Java 6 yang ideal. Pendekatan pertama melanggar KERING . Beberapa blok melakukan hal yang sama, berulang-ulang - satu kali untuk setiap pengecualian. Anda ingin mencatat pengecualian dan melakukan rethrow? Baik. Baris kode yang sama untuk setiap pengecualian.
Opsi kedua lebih buruk karena beberapa alasan. Pertama, itu berarti Anda menangkap semua pengecualian. Null pointer tertangkap di sana (dan seharusnya tidak). Selain itu, Anda rethrowing sebuah
Exception
yang berarti bahwa metode tanda tangan akandeleteSomething() throws Exception
yang hanya membuat berantakan lebih jauh stack sebagai orang yang menggunakan kode Anda sekarang dipaksa untukcatch(Exception e)
.Dengan Java 7, ini tidak seperti yang penting karena Anda malah dapat melakukan:
Selanjutnya, jenis memeriksa jika salah satu tidak menangkap jenis pengecualian yang dilemparkan:
Pemeriksa tipe akan mengenali bahwa
e
mungkin hanya tipeIOException
atauSQLException
. Saya masih tidak terlalu antusias tentang penggunaan gaya ini, tapi itu tidak menyebabkan kode seburuk di bawah Java 6 (di mana itu akan memaksa Anda untuk memiliki metode tanda tangan menjadi superclass yang pengecualiannya diperluas).Terlepas dari semua perubahan ini, banyak alat analisis statis (Sonar, PMD, Checkstyle) masih menerapkan panduan gaya Java 6. Itu bukan hal yang buruk. Saya cenderung setuju dengan peringatan ini agar tetap diberlakukan, tetapi Anda dapat mengubah prioritasnya menjadi mayor atau minor sesuai dengan cara tim Anda memprioritaskannya.
Jika pengecualian harus diperiksa atau dicentang ... yang adalah masalah g r e a t debat yang satu dapat dengan mudah menemukan posting blog yang tak terhitung jumlahnya mengambil setiap sisi argumen. Namun, jika Anda bekerja dengan pengecualian yang diperiksa, Anda mungkin harus menghindari melempar beberapa jenis, setidaknya di bawah Java 6.
sumber
foo.delete()
? Apakah masih ketahuan dan di pasang kembali?delete
melempar pengecualian yang diperiksa selain IOException atau SQLException dalam contoh itu. Poin kunci yang saya coba buat adalah bahwa metode yang memanggil rethrowException masih akan mendapatkan tipe Exception di Java 7. Di Java 6, semua itu akan digabung menjadiException
tipe umum yang membuat analisis statis dan coders lain sedih.catch (Exception e)
dan memaksanya untuk menjadicatch (IOException e)
ataucatch (SQLException e)
sebaliknya.catch(IOException|SQLException ex)
. Tetapi, jika Anda hanya akan memikirkan kembali pengecualian, memungkinkan pemeriksa tipe untuk menyebarkan tipe aktual pengecualian akan menyederhanakan kode bukanlah hal yang buruk.Alasan bahwa Anda, idealnya, hanya ingin melemparkan satu jenis pengecualian adalah karena melakukan sebaliknya kemungkinan melanggar prinsip Pertanggungjawaban Tunggal dan Ketergantungan . Mari kita gunakan contoh untuk menunjukkan.
Katakanlah kita memiliki metode yang mengambil data dari kegigihan, dan kegigihan itu adalah sekumpulan file. Karena kita berurusan dengan file, kita dapat memiliki
FileNotFoundException
:Sekarang, kami memiliki perubahan dalam persyaratan, dan data kami berasal dari basis data. Alih-alih a
FileNotFoundException
(karena kita tidak berurusan dengan file), kita sekarang melemparSQLException
:Kita sekarang harus melalui semua kode yang menggunakan metode kita dan mengubah pengecualian yang harus kita periksa, kalau tidak kode tidak akan dikompilasi. Jika metode kita dipanggil jauh dan luas, itu bisa berarti banyak untuk mengubah / meminta orang lain berubah. Butuh banyak waktu, dan orang tidak akan bahagia.
Inversi ketergantungan mengatakan bahwa kita benar-benar tidak boleh membuang salah satu dari pengecualian ini karena mereka mengekspos detail implementasi internal yang sedang kami coba enkapsulasi. Kode panggilan perlu tahu jenis kegigihan apa yang kita gunakan, padahal seharusnya hanya khawatir jika catatan itu dapat diambil. Alih-alih, kita harus melempar pengecualian yang menyampaikan kesalahan pada tingkat abstraksi yang sama seperti yang kami paparkan melalui API kami:
Sekarang, jika kita mengubah implementasi internal, kita bisa membungkus pengecualian itu dalam
InvalidRecordException
dan meneruskannya (atau tidak membungkusnya, dan hanya membuang yang baruInvalidRecordException
). Kode eksternal tidak tahu atau peduli jenis kegigihan apa yang sedang digunakan. Semuanya dienkapsulasi.Adapun Tanggung Jawab Tunggal, kita perlu berpikir tentang kode yang melempar beberapa, pengecualian yang tidak terkait. Katakanlah kita memiliki metode berikut:
Apa yang bisa kita katakan tentang metode ini? Kita bisa tahu hanya dari tanda tangan bahwa itu membuka file dan mem - parsingnya. Ketika kita melihat konjungsi, seperti "dan" atau "atau" dalam deskripsi metode, kita tahu bahwa itu melakukan lebih dari satu hal; ia memiliki lebih dari satu tanggung jawab . Metode dengan lebih dari satu tanggung jawab sulit untuk dikelola karena dapat berubah jika ada yang berubah. Sebagai gantinya, kita harus memecah metode sehingga mereka memiliki satu tanggung jawab:
Kami telah mengekstrak tanggung jawab membaca file dari tanggung jawab mengurai data. Salah satu efek sampingnya adalah sekarang kita dapat meneruskan data String apa pun ke data parse dari sumber apa pun: dalam memori, file, jaringan, dll. Kita juga dapat menguji
parse
lebih mudah sekarang karena kita tidak memerlukan file pada disk untuk menjalankan tes terhadap.Kadang-kadang benar-benar ada dua (atau lebih) pengecualian yang bisa kita lemparkan dari suatu metode, tetapi jika kita berpegang teguh pada SRP dan DIP, saat-saat kita menghadapi situasi ini menjadi lebih jarang.
sumber
update
metode Anda adalah atomik seperti yang Anda bisa lakukan, dan pengecualian berada pada tingkat abstraksi yang tepat, maka ya, sepertinya Anda memang perlu melempar dua jenis pengecualian, karena ada dua kasus luar biasa. Itu harus menjadi ukuran berapa banyak pengecualian yang dapat dilemparkan, daripada aturan-aturan tipis (kadang-kadang sewenang-wenang) ini.catch (IOException | SQLException ex)
) karena masalah sebenarnya ada dalam model / desain program.Saya ingat bermain-main dengan ini sedikit ketika bermain dengan Java beberapa waktu lalu, tapi saya tidak benar-benar menyadari perbedaan antara diperiksa dan tidak dicentang sampai saya membaca pertanyaan Anda. Saya menemukan artikel ini di Google cukup cepat, dan masuk ke beberapa kontroversi yang jelas:
http://tutorials.jenkov.com/java-exception-handling/checked-or-unchecked-exceptions.html
Yang sedang berkata, salah satu masalah yang orang ini sebutkan dengan pengecualian diperiksa adalah bahwa (dan saya pribadi mengalami ini sejak awal dengan Jawa) jika Anda terus menambahkan banyak pengecualian diperiksa untuk
throws
klausa dalam deklarasi metode Anda, tidak hanya apakah Anda harus memasukkan kode boilerplate lebih banyak untuk mendukungnya saat Anda pindah ke metode tingkat yang lebih tinggi, tetapi itu juga hanya membuat kompatibilitas yang lebih besar dan merusak istirahat ketika Anda mencoba memperkenalkan lebih banyak jenis pengecualian untuk metode tingkat yang lebih rendah. Jika Anda menambahkan tipe pengecualian yang dicentang ke metode level yang lebih rendah, maka Anda harus menjalankan kembali kode Anda dan menyesuaikan beberapa deklarasi metode lainnya juga.Salah satu poin mitigasi yang disebutkan dalam artikel - dan penulis tidak suka ini secara pribadi - adalah membuat pengecualian kelas dasar, membatasi
throws
klausa Anda untuk hanya menggunakannya, dan kemudian hanya menaikkan subkelasnya secara internal. Dengan begitu Anda bisa membuat tipe pengecualian yang baru diperiksa tanpa harus menjalankan kembali semua kode Anda.Penulis artikel ini mungkin tidak terlalu menyukai ini, tetapi masuk akal dalam pengalaman pribadi saya (terutama jika Anda dapat melihat apa semua subclass itu), dan saya yakin itulah sebabnya saran yang Anda berikan adalah untuk tutup semua untuk satu jenis pengecualian yang diperiksa masing-masing. Terlebih lagi adalah bahwa saran yang Anda sebutkan sebenarnya memungkinkan untuk beberapa jenis pengecualian diperiksa dalam metode non-publik, yang masuk akal jika ini adalah motif mereka (atau bahkan sebaliknya). Jika itu hanya metode pribadi atau yang serupa, Anda tidak akan menjalankan setengah dari basis kode ketika Anda mengubah satu hal kecil.
Sebagian besar Anda memang bertanya apakah ini merupakan standar yang dapat diterima, tetapi di antara riset yang Anda sebutkan, artikel yang cukup masuk akal ini, dan hanya berbicara dari pengalaman pemrograman pribadi, sepertinya tidak terlalu menonjol.
sumber
Throwable
, dan selesai dengan itu, alih-alih menciptakan semua hierarki Anda sendiri?Melemparkan beberapa pengecualian yang diperiksa masuk akal ketika ada beberapa hal yang masuk akal untuk dilakukan.
Misalnya katakanlah Anda memiliki metode
ini melanggar aturan pengecualian pne, tetapi masuk akal.
Sayangnya, apa yang biasanya terjadi adalah metode
Di sini ada sedikit kesempatan bagi penelepon untuk melakukan sesuatu yang spesifik berdasarkan jenis pengecualian. Jadi jika kita ingin mendorongnya untuk menyadari bahwa metode ini BISA dan kadang-kadang AKAN salah, melempar SomethingMightGoWrongException hanya enogh dan lebih baik.
Karenanya aturan paling banyak satu pengecualian diperiksa.
Tetapi jika proyek Anda menggunakan desain di mana ada beberapa pengecualian yang bermakna diperiksa, aturan ini seharusnya tidak berlaku.
Sidenote: Sesuatu sebenarnya bisa salah hampir di mana-mana, jadi orang bisa berpikir untuk menggunakan? memperluas RuntimeException, tetapi ada perbedaan antara "kita semua melakukan kesalahan" dan "ini berbicara sistem eksternal dan kadang-kadang AKAN turun, atasi itu".
sumber