Melempar pengecualian di dalam akhirnya

27

Analisis kode statis seperti Fortify "mengeluh" ketika pengecualian mungkin dilemparkan ke dalam finallyblok, 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 writertidak 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 writerdan 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 finallyblok?

superM
sumber
4
Jika ini adalah Java, dan Anda dapat menggunakan Java 7, periksa apakah blok ARM dapat menyelesaikan masalah Anda.
Landei
@ Landei, ini menyelesaikannya, tapi sayangnya kami tidak menggunakan Java 7.
superM
Saya akan mengatakan bahwa kode yang Anda tunjukkan tidak "Menggunakan pernyataan melempar di dalam akhirnya blok" dan dengan demikian perkembangan logis baik-baik saja.
Mike
@ Mike, saya telah menggunakan ringkasan standar yang ditunjukkan oleh Fortify, tetapi secara langsung atau tidak langsung ada pengecualian yang dimasukkan di dalamnya.
superM
Sayangnya, coba-dengan-sumber daya blok juga terdeteksi oleh Fortify sebagai pengecualian dilemparkan ke dalam akhirnya .. itu terlalu cerdas, sial .. masih tidak yakin bagaimana cara mengatasinya, sepertinya alasan untuk mencoba-dengan-sumber daya adalah untuk memastikan penutupan sumber daya akhirnya, dan sekarang setiap pernyataan seperti itu dilaporkan oleh Fortify sebagai ancaman keamanan ..
mmona

Jawaban:

18

Pada dasarnya, ada finallyklausul 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 di finallyblok itu akan menyembunyikannya. Ini akan terlihat seperti kesalahan yang disebabkan oleh panggilan close, bukan karena alasan sebenarnya.

Beberapa orang mengikuti pola buruk penangan pengecualian bersarang, menelan setiap pengecualian yang dilemparkan ke dalam finallyblok.

SomeFileWriter writer = null; 
try { 
     //init the writer
     //write into the file
} finally {
    if (writer!= null) {
        try {
            writer.close();
        } catch (...) {
        }
    }
}

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 finallyblok.

Taman Travis
sumber
1
+1 Meskipun saya termasuk orang-orang yang menggunakan kode jahat itu. Tetapi untuk pembenaran saya, saya akan mengatakan bahwa saya tidak selalu melakukan ini, hanya ketika pengecualiannya tidak kritis.
superM
2
Saya menemukan diri saya melakukannya juga. Beberapa kali membuat semua manajer sumber daya (anonim atau tidak) mengurangi tujuan kode.
Travis Parks
+1 dari saya juga; Anda pada dasarnya mengatakan dengan tepat apa yang akan saya lakukan tetapi lebih baik. Perlu dicatat adalah bahwa beberapa implementasi Stream di Jawa tidak bisa benar-benar melempar Pengecualian pada tutup (), tetapi antarmuka menyatakannya karena beberapa di antaranya melakukannya. Jadi, dalam beberapa keadaan Anda mungkin menambahkan blok tangkap yang sebenarnya tidak pernah dibutuhkan.
vaughandroid
1
Apa yang buruk tentang menggunakan blok coba / tangkap di dalam blok akhirnya? Saya lebih suka melakukan itu daripada mengasapi semua kode saya dengan harus membungkus semua koneksi dan stream saya, dll. Hanya agar mereka dapat ditutup dengan tenang - yang, dengan sendirinya, memperkenalkan masalah baru karena tidak mengetahui apakah menutup koneksi atau streaming ( atau apa pun) menyebabkan pengecualian - yang merupakan sesuatu yang Anda mungkin benar-benar ingin tahu tentang atau melakukan sesuatu tentang ...
larangan geoengineering
10

Apa yang dikatakan Travis Parks benar bahwa pengecualian di finallyblok akan mengkonsumsi nilai kembali atau pengecualian dari try...catchblok.

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

FileWriter writer = null;
try {
  writer = new FileWriter("myFile.txt");
  writer.write("hello");
} catch(...) {
  // return/throw new exception
} finally {
  writer.close(); // an exception would consume the catch block's return/exception
}

Untuk

try (FileWriter writer = new FileWriter("myFile.txt")) {
  writer.write("hello");
} catch(...) {
  // return/throw new exception, always gets returned even if writer fails to close
}

http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

clintonmonk
sumber
1
Ini terkadang membantu. Tetapi jika saya benar-benar perlu membuat pengecualian ketika file tidak dapat ditutup, pendekatan ini menyembunyikan masalah.
superM
1
@superM Sebenarnya, coba-dengan-sumber daya tidak menyembunyikan pengecualian close(). "setiap pengecualian yang terjadi saat menutupnya akan ditekan, memungkinkan nilai pengembalian asli atau pengecualian untuk dilewatkan" - ini sama sekali tidak benar . Pengecualian dari close()metode akan ditekan hanya ketika pengecualian lain akan dilempar dari blok coba / tangkap. Jadi, jika tryblok tidak membuang pengecualian, pengecualian dari close()metode akan dilempar (dan nilai pengembalian tidak akan dikembalikan). Bahkan dapat ditangkap dari catchblok saat ini .
Ruslan Stelmachenko
0

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.

drekka
sumber
0

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, finallytangkap logika yang dijalankan di tengah pemulihan dari kesalahan, sebelum dipulihkan sepenuhnya (sebelum kami mencapai catchtujuan 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 finallyblok. 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 finallyblok 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 SomeFileWriterbisa melempar ke dalam close, 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