Saya ditugaskan untuk memelihara aplikasi yang ditulis beberapa waktu lalu oleh pengembang yang lebih terampil. Saya menemukan potongan kode ini:
public Configuration retrieveUserMailConfiguration(Long id) throws MailException {
try {
return translate(mailManagementService.retrieveUserMailConfiguration(id));
} catch (Exception e) {
rethrow(e);
}
throw new RuntimeException("cannot reach here");
}
Saya ingin tahu apakah melempar RuntimeException("cannot reach here")
dibenarkan. Saya mungkin kehilangan sesuatu yang jelas mengetahui bahwa kode ini berasal dari kolega yang lebih berpengalaman.
EDIT: Ini adalah badan rethrow yang beberapa jawaban disebut. Saya menganggap itu tidak penting dalam pertanyaan ini.
private void rethrow(Exception e) throws MailException {
if (e instanceof InvalidDataException) {
InvalidDataException ex = (InvalidDataException) e;
rethrow(ex);
}
if (e instanceof EntityAlreadyExistsException) {
EntityAlreadyExistsException ex = (EntityAlreadyExistsException) e;
rethrow(ex);
}
if (e instanceof EntityNotFoundException) {
EntityNotFoundException ex = (EntityNotFoundException) e;
rethrow(ex);
}
if (e instanceof NoPermissionException) {
NoPermissionException ex = (NoPermissionException) e;
rethrow(ex);
}
if (e instanceof ServiceUnavailableException) {
ServiceUnavailableException ex = (ServiceUnavailableException) e;
rethrow(ex);
}
LOG.error("internal error, original exception", e);
throw new MailUnexpectedException();
}
private void rethrow(ServiceUnavailableException e) throws
MailServiceUnavailableException {
throw new MailServiceUnavailableException();
}
private void rethrow(NoPermissionException e) throws PersonNotAuthorizedException {
throw new PersonNotAuthorizedException();
}
private void rethrow(InvalidDataException e) throws
MailInvalidIdException, MailLoginNotAvailableException,
MailInvalidLoginException, MailInvalidPasswordException,
MailInvalidEmailException {
switch (e.getDetail()) {
case ID_INVALID:
throw new MailInvalidIdException();
case LOGIN_INVALID:
throw new MailInvalidLoginException();
case LOGIN_NOT_ALLOWED:
throw new MailLoginNotAvailableException();
case PASSWORD_INVALID:
throw new MailInvalidPasswordException();
case EMAIL_INVALID:
throw new MailInvalidEmailException();
}
}
private void rethrow(EntityAlreadyExistsException e)
throws MailLoginNotAvailableException, MailEmailAddressAlreadyForwardedToException {
switch (e.getDetail()) {
case LOGIN_ALREADY_TAKEN:
throw new MailLoginNotAvailableException();
case EMAIL_ADDRESS_ALREADY_FORWARDED_TO:
throw new MailEmailAddressAlreadyForwardedToException();
}
}
private void rethrow(EntityNotFoundException e) throws
MailAccountNotCreatedException,
MailAliasNotCreatedException {
switch (e.getDetail()) {
case ACCOUNT_NOT_FOUND:
throw new MailAccountNotCreatedException();
case ALIAS_NOT_FOUND:
throw new MailAliasNotCreatedException();
}
}
java
programming-practices
Sok Pomaranczowy
sumber
sumber
rethrow
gagal sebenarnyathrow
pengecualian. (yang mungkin terjadi suatu hari, jika implementasinya berubah)Jawaban:
Pertama, terima kasih telah memperkirakan pertanyaan Anda dan menunjukkan kepada kami apa yang
rethrow
bisa. Jadi, sebenarnya, apa yang dilakukannya adalah mengubah pengecualian dengan properti menjadi kelas pengecualian yang lebih halus. Lebih lanjut tentang ini nanti.Karena saya tidak benar-benar menjawab pertanyaan utama pada awalnya, ini dia: ya, umumnya gaya buruk untuk melempar pengecualian runtime dalam kode yang tidak terjangkau; Anda sebaiknya menggunakan pernyataan, atau bahkan lebih baik, menghindari masalah. Seperti yang sudah ditunjukkan, kompiler di sini tidak dapat memastikan bahwa kode tidak pernah keluar dari
try/catch
blok. Anda dapat memperbaiki kode Anda dengan memanfaatkan ...Kesalahan adalah nilai
(Tidak mengherankan, itu terkenal di mana saja )
Mari kita gunakan contoh yang lebih sederhana, yang saya gunakan sebelum edit: jadi bayangkan Anda mencatat sesuatu dan membuat pengecualian wrapper seperti dalam jawaban Konrad . Sebut saja
logAndWrap
.Alih-alih melempar pengecualian sebagai efek samping
logAndWrap
, Anda bisa membiarkannya bekerja sebagai efek samping dan membuatnya mengembalikan pengecualian (setidaknya, yang diberikan dalam input). Anda tidak perlu menggunakan obat generik, hanya fungsi dasar:Kemudian, Anda dengan
throw
jelas, dan kompiler Anda senang:Bagaimana jika Anda lupa
throw
?Seperti dijelaskan dalam komentar Joe23 , cara pemrograman defensif untuk memastikan bahwa pengecualian selalu dilontarkan akan terdiri dari melakukan secara eksplisit
throw new CustomWrapperException(exception)
pada akhirlogAndWrap
, seperti yang dilakukan oleh Guava . Dengan begitu, Anda tahu bahwa pengecualian akan dilempar, dan penganalisa tipe Anda senang. Namun, pengecualian khusus Anda harus berupa pengecualian yang tidak dicentang, yang tidak selalu memungkinkan. Juga, saya menilai risiko pengembang hilang untuk menulisthrow
menjadi sangat rendah: pengembang harus melupakannya dan metode sekitarnya tidak boleh mengembalikan apa pun, jika tidak kompiler akan mendeteksi pengembalian yang hilang. Ini adalah cara yang menarik untuk melawan sistem tipe, dan itu berhasil.Rethrow
Sebenarnya
rethrow
dapat ditulis sebagai fungsi juga, tapi saya punya masalah dengan implementasinya saat ini:Ada banyak gips yang tidak berguna sepertiPemain sebenarnya diperlukan (lihat komentar):Saat melempar / mengembalikan pengecualian baru, yang lama dibuang; dalam kode berikut, a
MailLoginNotAvailableException
tidak mengizinkan saya untuk mengetahui login mana yang tidak tersedia, yang tidak nyaman; selain itu, stacktraces tidak akan lengkap:Mengapa kode asal tidak melempar pengecualian khusus itu di tempat pertama? Saya menduga bahwa
rethrow
ini digunakan sebagai lapisan kompatibilitas antara subsistem (pengiriman) dan logika bisnis (mungkin tujuannya adalah untuk menyembunyikan detail implementasi seperti pengecualian yang dilemparkan dengan menggantinya dengan pengecualian khusus). Bahkan jika saya setuju bahwa akan lebih baik memiliki kode bebas-tangkap, seperti yang disarankan dalam jawaban Pete Becker , saya tidak berpikir Anda akan memiliki kesempatan untuk menghapuscatch
danrethrow
kode di sini tanpa refactoring besar.sumber
logAndWrap
metode Anda, Anda bisa melempar pengecualian alih-alih mengembalikannya, tetapi simpan jenis kembali(Runtime)Exception
. Dengan cara ini catch catch block dapat tetap seperti yang Anda tulis (tanpa membuang kode yang tidak dapat dijangkau). Tetapi memiliki keuntungan tambahan yang tidak dapat Anda lupakanthrow
. Jika Anda mengembalikan pengecualian dan lupa melempar, ini akan menyebabkan pengecualian tertelan secara diam-diam. Melempar sambil memiliki tipe kembali RuntimeException akan membuat kompiler senang sementara juga menghindari kesalahan ini. Begitulah cara JambuThrowables.propagate
diterapkan.logAndWrap
tetapi tidak melakukan apa pun dengan pengecualian yang dikembalikan? Itu menarik. Di dalam fungsi yang harus mengembalikan nilai, kompiler akan melihat bahwa ada jalan tanpa nilai yang dikembalikan, tetapi ini berguna untuk metode yang tidak mengembalikan apa pun. Terima kasih lagi.rethrow(e);
Fungsi ini melanggar prinsip yang mengatakan bahwa dalam keadaan normal, suatu fungsi akan kembali, sementara dalam keadaan luar biasa, suatu fungsi akan melempar pengecualian. Fungsi ini melanggar prinsip ini dengan melemparkan pengecualian dalam keadaan normal. Itulah sumber dari semua kebingungan.Compiler mengasumsikan bahwa fungsi ini akan kembali dalam keadaan normal, sehingga sejauh yang bisa dikompilasi oleh kompiler, eksekusi dapat mencapai akhir dari
retrieveUserMailConfiguration
fungsi, di mana saat itu merupakan kesalahan untuk tidak memilikireturn
pernyataan. TheRuntimeException
dilemparkan ada seharusnya meringankan kekhawatiran ini dari kompilator, tetapi merupakan cara yang agak kikuk melakukannya. Cara lain untuk mencegahfunction must return a value
kesalahan adalah dengan menambahkanreturn null; //to keep the compiler happy
pernyataan, tapi itu juga sama kikuk menurut saya.Jadi, secara pribadi, saya akan mengganti ini:
dengan ini:
atau, lebih baik lagi, (seperti yang disarankan coredump ,) dengan ini:
Dengan demikian, aliran kontrol akan dibuat jelas bagi kompiler, sehingga tugas akhir Anda
throw new RuntimeException("cannot reach here");
tidak hanya menjadi berlebihan, tetapi sebenarnya bahkan tidak dapat dikompilasi, karena akan ditandai oleh kompiler sebagai kode yang tidak dapat dijangkau.Itu cara yang paling elegan dan sebenarnya juga paling sederhana untuk keluar dari situasi buruk ini.
sumber
throw reportAndTransform(e)
sebuah beberapa menit setelah saya posting jawaban saya sendiri. Mungkin Anda memodifikasi pertanyaan Anda secara independen tentang itu, dan saya minta maaf sebelumnya jika memang demikian, tetapi sebaliknya, memberikan sedikit kredit adalah sesuatu yang biasanya dilakukan orang.throw reportAndTransform(e)
. Banyak pola desain yang merupakan fitur bahasa yang hilang. Ini salah satunya.The
throw
mungkin ditambahkan untuk berkeliling "metode harus kembali nilai" kesalahan yang mungkin terjadi - aliran data Java analyzer cukup cerdas untuk memahami bahwa tidak adareturn
perlu setelahthrow
, tapi tidak setelah kustom Andarethrow()
metode, dan tidak ada@NoReturn
penjelasan yang bisa Anda gunakan untuk memperbaikinya.Namun demikian, membuat Pengecualian baru dalam kode yang tidak terjangkau tampaknya berlebihan. Saya hanya akan menulis
return null
, mengetahui bahwa hal itu, pada kenyataannya, tidak pernah terjadi.sumber
throw new...
garis dan mengeluh tentang tidak ada pernyataan kembali. Apakah pemrograman itu buruk? Apakah ada konvensi tentang mengganti pernyataan pengembalian yang tidak terjangkau? Saya akan membiarkan pertanyaan ini tidak terjawab untuk sementara waktu untuk mendorong lebih banyak masukan. Meskipun demikian terima kasih atas jawaban Anda.rethrow()
metode menyembunyikan fakta bahwacatch
ini rethrowing pengecualian. Saya akan menghindari melakukan hal seperti itu. Plus, tentu saja,rethrow()
sebenarnya mungkin gagal untuk memikirkan kembali pengecualian, di mana hal lain terjadi.return null
. Dengan pengecualian, jika seseorang di masa depan memecah kode sehingga baris terakhir entah bagaimana menjadi terjangkau, akan segera jelas ketika pengecualian dilemparkan. Jika Anda sebaliknyareturn null
, sekarang Anda memilikinull
nilai mengambang di aplikasi Anda yang tidak diharapkan. Siapa yang tahu di mana dalam aplikasi ituNullPointerException
akan muncul? Kecuali jika Anda beruntung dan sedang setelah panggilan ke fungsi ini, akan sangat sulit untuk melacak masalah sebenarnya.return null
sangat mungkin untuk menutupi bug karena Anda tidak dapat membedakan kasus-kasus lain di mana ia kembalinull
dari bug ini.Itu
pernyataan menjelaskan kepada PERSON yang membaca kode apa yang terjadi, jadi jauh lebih baik daripada mengembalikan nol misalnya. Ini juga memudahkan untuk debug jika kode diubah dengan cara yang tidak terduga.
Namun
rethrow(e)
sepertinya salah! Jadi dalam kasus Anda, saya pikir refactoring kode adalah pilihan yang lebih baik. Lihat jawaban lain (saya suka coredump terbaik) untuk cara memilah kode Anda.sumber
Saya tidak tahu apakah ada konvensi.
Bagaimanapun, trik lain adalah dengan melakukannya:
Mengizinkan ini:
Dan tidak
RuntimeException
perlu lagi buatan . Meskipunrethrow
tidak pernah benar-benar mengembalikan nilai apa pun, itu cukup baik untuk kompiler sekarang.Itu mengembalikan nilai dalam teori (metode tanda tangan), dan kemudian itu dibebaskan dari benar-benar melakukannya karena ia melempar pengecualian.
Ya, itu mungkin terlihat aneh, tapi sekali lagi - melempar hantu
RuntimeException
, atau mengembalikan nol yang tidak akan pernah terlihat di dunia ini - itu juga bukan keindahan.Berusaha keras agar mudah dibaca, Anda dapat mengganti nama
rethrow
dan memiliki sesuatu seperti:sumber
Jika Anda menghapus
try
blok sepenuhnya maka Anda tidak perlurethrow
atauthrow
. Kode ini melakukan hal yang persis sama dengan aslinya:Jangan biarkan fakta bahwa itu berasal dari pengembang yang lebih berpengalaman menipu Anda. Ini adalah kode yang membusuk, dan ini terjadi setiap saat. Perbaiki saja.
EDIT: Saya salah membaca
rethrow(e)
karena hanya melemparkan kembali pengecualiane
. Jikarethrow
metode itu benar-benar melakukan sesuatu selain melemparkan kembali pengecualian, maka menyingkirkannya memang mengubah semantik metode ini.sumber
catch(Exception)
yang merupakan bau kode menjijikkan.rethrow
tidak berguna (dan ini bukan intinya di sini), tetapi Anda tidak bisa begitu saja menyingkirkan kode yang tidak Anda sukai dan mengatakan itu setara dengan aslinya ketika jelas tidak. Ada efek samping dalamrethrow
fungsi khusus .