Analisis kode statis seperti Fortify "mengeluh" ketika pengecualian mungkin dilemparkan ke dalam finally
blok, mengatakan itu Using a throw statement inside a finally block breaks the logical progression through the try-catch-finally
. Biasanya saya setuju dengan ini. Namun baru-baru ini saya menemukan kode ini:
SomeFileWriter writer = null;
try {
//init the writer
//write into the file
} catch (...) {
//exception handling
} finally {
if (writer!= null) writer.close();
}
Sekarang jika writer
tidak dapat ditutup dengan benar, writer.close()
metode ini akan melempar pengecualian. Pengecualian harus dilemparkan karena (kemungkinan besar) file tidak disimpan setelah menulis.
Saya bisa mendeklarasikan variabel tambahan, mengaturnya jika ada kesalahan menutup writer
dan melemparkan pengecualian setelah blok akhirnya. Tetapi kode ini berfungsi dengan baik dan saya tidak yakin apakah akan mengubahnya atau tidak.
Apa kelemahan melempar pengecualian di dalam finally
blok?
Jawaban:
Pada dasarnya, ada
finally
klausul untuk memastikan pelepasan sumber daya secara tepat. Namun, jika pengecualian dilemparkan ke dalam blok akhirnya, jaminan itu hilang. Lebih buruk lagi, jika blok kode utama Anda melempar pengecualian, pengecualian yang muncul difinally
blok itu akan menyembunyikannya. Ini akan terlihat seperti kesalahan yang disebabkan oleh panggilanclose
, bukan karena alasan sebenarnya.Beberapa orang mengikuti pola buruk penangan pengecualian bersarang, menelan setiap pengecualian yang dilemparkan ke dalam
finally
blok.Di versi Java yang lebih lama, Anda dapat "menyederhanakan" kode ini dengan membungkus sumber daya di kelas yang melakukan pembersihan "aman" ini untuk Anda. Seorang teman baik saya membuat daftar jenis anonim, masing-masing yang menyediakan logika untuk membersihkan sumber daya mereka. Kemudian kodenya hanya loop di atas daftar dan memanggil metode buang di dalam
finally
blok.sumber
Apa yang dikatakan Travis Parks benar bahwa pengecualian di
finally
blok akan mengkonsumsi nilai kembali atau pengecualian daritry...catch
blok.Namun, jika Anda menggunakan Java 7, masalahnya dapat diselesaikan dengan menggunakan blok coba-dengan-sumber daya . Menurut dokumen, selama sumber daya Anda mengimplementasikan
java.lang.AutoCloseable
(kebanyakan penulis / pembaca perpustakaan melakukannya sekarang), blok coba-dengan-sumber daya akan menutupnya untuk Anda. Manfaat tambahan di sini adalah bahwa setiap pengecualian yang terjadi saat menutupnya akan ditekan, sehingga nilai pengembalian atau pengecualian asli untuk dilewatkan.Dari
Untuk
http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
sumber
close()
. "setiap pengecualian yang terjadi saat menutupnya akan ditekan, memungkinkan nilai pengembalian asli atau pengecualian untuk dilewatkan" - ini sama sekali tidak benar . Pengecualian dariclose()
metode akan ditekan hanya ketika pengecualian lain akan dilempar dari blok coba / tangkap. Jadi, jikatry
blok tidak membuang pengecualian, pengecualian dariclose()
metode akan dilempar (dan nilai pengembalian tidak akan dikembalikan). Bahkan dapat ditangkap daricatch
blok saat ini .Saya pikir ini adalah sesuatu yang Anda perlu atasi berdasarkan kasus per kasus. Dalam beberapa kasus apa yang dikatakan analis itu benar dalam hal kode yang Anda miliki tidak terlalu bagus dan perlu dipikirkan kembali. Tetapi mungkin ada kasus lain ketika melempar atau bahkan melempar kembali mungkin merupakan hal terbaik. Itu bukan sesuatu yang bisa Anda mandat.
sumber
Ini akan menjadi jawaban konseptual mengapa peringatan ini mungkin ada bahkan dengan pendekatan coba-dengan-sumber daya. Sayangnya, ini juga bukan solusi mudah yang mungkin Anda harapkan untuk dicapai.
Pemulihan Kesalahan Tidak Dapat Gagal
finally
model aliran kontrol pasca transaksi yang dieksekusi terlepas dari apakah transaksi berhasil atau gagal.Dalam kasus gagal,
finally
tangkap logika yang dijalankan di tengah pemulihan dari kesalahan, sebelum dipulihkan sepenuhnya (sebelum kami mencapaicatch
tujuan kami ).Bayangkan masalah konseptual yang dihadirkannya untuk menemui kesalahan di tengah pemulihan dari kesalahan.
Bayangkan sebuah server basis data tempat kami mencoba melakukan transaksi, dan gagal setengah jalan (misalnya server kehabisan memori di tengah). Sekarang server ingin memutar kembali transaksi ke titik seolah-olah tidak ada yang terjadi. Namun bayangkan itu menemukan kesalahan lain dalam proses mundur. Sekarang kita memiliki transaksi setengah berkomitmen pada basis data - atomisitas dan sifat transaksi yang tidak dapat dibagi sekarang rusak, dan integritas basis data sekarang akan dikompromikan.
Masalah konseptual ini ada dalam bahasa apa pun yang berurusan dengan kesalahan apakah itu C dengan propagasi kode kesalahan manual, atau C ++ dengan pengecualian dan destruktor, atau Java dengan pengecualian dan
finally
.finally
tidak dapat gagal dalam bahasa yang menyediakannya dengan cara yang sama destruktor tidak bisa gagal dalam C ++ dalam proses menemukan pengecualian.Satu-satunya cara untuk menghindari masalah konseptual dan sulit ini adalah untuk memastikan bahwa proses memutar kembali transaksi dan melepaskan sumber daya di tengah tidak mungkin menemukan pengecualian / kesalahan rekursif.
Jadi satu-satunya desain yang aman di sini adalah desain yang
writer.close()
tidak mungkin gagal. Biasanya ada cara-cara dalam desain untuk menghindari skenario di mana hal-hal seperti itu bisa gagal di tengah-tengah pemulihan, membuatnya mustahil.Sayangnya itu satu-satunya cara - pemulihan kesalahan tidak bisa gagal. Cara termudah untuk memastikan ini adalah membuat fungsi "pelepasan sumber daya" dan "efek samping terbalik" semacam itu tidak mampu gagal. Itu tidak mudah - pemulihan kesalahan yang tepat sulit dan juga sayangnya sulit untuk diuji. Tetapi cara untuk mencapainya adalah memastikan bahwa setiap fungsi yang "menghancurkan", "menutup", "mematikan", "memutar kembali", dll. Tidak mungkin mengalami kesalahan eksternal dalam proses, karena fungsi seperti itu sering perlu dipanggil di tengah memulihkan dari kesalahan yang ada.
Contoh: Logging
Katakanlah Anda ingin mencatat barang di dalam
finally
blok. Ini sering menjadi masalah besar kecuali penebangan tidak bisa gagal . Penebangan hampir pasti dapat gagal, karena mungkin ingin menambahkan lebih banyak data ke file, dan itu dapat dengan mudah menemukan banyak alasan untuk gagal.Jadi solusinya di sini adalah membuatnya sehingga fungsi logging apa pun yang digunakan dalam
finally
blok tidak bisa melempar ke pemanggil (mungkin bisa gagal, tetapi tidak mau membuang). Bagaimana mungkin kita melakukan itu? Jika bahasa Anda memungkinkan melempar dalam konteks yang akhirnya asalkan ada blok coba / tangkap bersarang, itu akan menjadi salah satu cara untuk menghindari melempar ke pemanggil dengan menelan pengecualian dan mengubahnya menjadi kode kesalahan, mis. Mungkin penebangan dapat dilakukan secara terpisah proses atau utas yang dapat gagal secara terpisah dan di luar tumpukan pemulihan kesalahan pemulihan yang ada. Selama Anda dapat berkomunikasi dengan proses itu tanpa kemungkinan mengalami kesalahan, itu juga akan menjadi pengecualian-aman, karena masalah keselamatan hanya ada dalam skenario ini jika kita melempar secara rekursif dari dalam utas yang sama.Dalam hal ini, kita bisa lolos dari kegagalan penebangan asalkan tidak membuang karena gagal login dan tidak melakukan apa-apa bukanlah akhir dunia (itu tidak membocorkan sumber daya apa pun atau gagal memutar kembali efek samping, misalnya).
Lagi pula, saya yakin Anda sudah bisa mulai membayangkan betapa sulitnya untuk benar-benar membuat perangkat lunak menjadi pengecualian. Mungkin tidak perlu mencari ini pada tingkat tertinggi di semua kecuali perangkat lunak yang paling penting. Tetapi perlu dicatat bagaimana cara benar-benar mencapai keamanan pengecualian, karena bahkan penulis perpustakaan yang sangat umum sekalipun sering gagal di sini dan menghancurkan seluruh keamanan aplikasi Anda menggunakan perpustakaan.
SomeFileWriter
Jika
SomeFileWriter
bisa melempar ke dalamclose
, maka saya akan mengatakan itu umumnya tidak kompatibel dengan penanganan pengecualian, kecuali jika Anda tidak pernah mencoba untuk menutupnya dalam konteks yang melibatkan pemulihan dari pengecualian yang ada. Jika kode untuk itu berada di luar kendali Anda, kami mungkin SOL tetapi layak memberi tahu penulis tentang masalah keselamatan pengecualian yang mencolok ini. Jika itu dalam kendali Anda, rekomendasi utama saya adalah memastikan bahwa menutupnya tidak mungkin gagal dengan cara apa pun yang diperlukan.Bayangkan jika sistem operasi benar-benar gagal untuk menutup file. Sekarang setiap program yang mencoba untuk menutup file saat dimatikan akan gagal ditutup . Apa yang harus kita lakukan sekarang, biarkan aplikasi tetap terbuka dan dalam limbo (mungkin tidak), bocor sumber daya file dan abaikan masalahnya (mungkin baik-baik saja jika tidak terlalu kritis)? Desain teraman: buat jadi tidak mungkin untuk gagal menutup file.
sumber