Ruby QuickRef dari Ryan Davis berkata (tanpa penjelasan):
Jangan menyelamatkan Exception. PERNAH. atau aku akan menusukmu.
Kenapa tidak? Apa hal yang benar untuk dilakukan?
Ruby QuickRef dari Ryan Davis berkata (tanpa penjelasan):
Jangan menyelamatkan Exception. PERNAH. atau aku akan menusukmu.
Kenapa tidak? Apa hal yang benar untuk dilakukan?
Jawaban:
TL; DR : Gunakan StandardError
sebagai ganti untuk menangkap pengecualian umum. Saat pengecualian asli dimunculkan kembali (mis. Saat menyelamatkan hanya untuk mencatat pengecualian), penyelamatan Exception
mungkin tidak apa-apa.
Exception
adalah akar dari hirarki pengecualian Ruby , jadi ketika Anda rescue Exception
Anda menyelamatkan dari segala sesuatu , termasuk subclass seperti SyntaxError
, LoadError
, dan Interrupt
.
Menyelamatkan Interrupt
mencegah pengguna CTRLCuntuk keluar dari program.
Menyelamatkan SignalException
mencegah program merespon dengan benar terhadap sinyal. Ini tidak akan dapat dikerjakan kecuali oleh kill -9
.
Menyelamatkan SyntaxError
berarti bahwa eval
yang gagal akan melakukannya secara diam-diam.
Semua ini dapat ditampilkan dengan menjalankan program ini, dan mencoba CTRLCatau kill
melakukannya:
loop do
begin
sleep 1
eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
rescue Exception
puts "I refuse to fail or be stopped!"
end
end
Menyelamatkan dari Exception
bahkan bukan default. Perbuatan
begin
# iceberg!
rescue
# lifeboats
end
tidak menyelamatkan dari Exception
, itu menyelamatkan dari StandardError
. Anda biasanya harus menentukan sesuatu yang lebih spesifik daripada standar StandardError
, tetapi menyelamatkan dari Exception
memperluas ruang lingkup daripada mempersempitnya, dan dapat memiliki hasil bencana dan membuat perburuan bug sangat sulit.
Jika Anda memiliki situasi di mana Anda ingin menyelamatkan StandardError
dan Anda membutuhkan variabel dengan pengecualian, Anda dapat menggunakan formulir ini:
begin
# iceberg!
rescue => e
# lifeboats
end
yang setara dengan:
begin
# iceberg!
rescue StandardError => e
# lifeboats
end
Salah satu dari beberapa kasus umum yang harus diselamatkan Exception
adalah untuk tujuan logging / pelaporan, dalam hal ini Anda harus segera mengajukan kembali pengecualian:
begin
# iceberg?
rescue Exception => e
# do some logging
raise # not enough lifeboats ;)
end
Throwable
di java
ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error, Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]
dan kemudianrescue *ADAPTER_ERRORS => e
The nyata aturan adalah: Jangan membuang pengecualian. Objektivitas penulis kutipan Anda dipertanyakan, sebagaimana dibuktikan oleh fakta bahwa itu berakhir dengan
atau aku akan menusukmu
Tentu saja, perlu diketahui bahwa sinyal (secara default) melempar pengecualian, dan proses yang biasanya berjalan lama diakhiri melalui sinyal, jadi menangkap Pengecualian dan tidak berhenti pada pengecualian sinyal akan membuat program Anda sangat sulit untuk berhenti. Jadi jangan lakukan ini:
#! /usr/bin/ruby
while true do
begin
line = STDIN.gets
# heavy processing
rescue Exception => e
puts "caught exception #{e}! ohnoes!"
end
end
Tidak, sungguh, jangan lakukan itu. Bahkan jangan jalankan itu untuk melihat apakah itu berfungsi.
Namun, katakan Anda memiliki server berulir dan Anda ingin semua pengecualian tidak:
thread.abort_on_exception = true
). Maka ini dapat diterima di utas penanganan koneksi Anda:
begin
# do stuff
rescue Exception => e
myLogger.error("uncaught #{e} exception while handling connection: #{e.message}")
myLogger.error("Stack trace: #{backtrace.map {|l| " #{l}\n"}.join}")
end
Di atas berfungsi untuk variasi penangan pengecualian standar Ruby, dengan keuntungan bahwa itu tidak juga membunuh program Anda. Rails melakukan ini dalam penangan permintaannya.
Pengecualian sinyal dimunculkan di utas utama. Utas latar belakang tidak akan mendapatkannya, jadi tidak ada gunanya mencoba menangkapnya di sana.
Ini sangat berguna dalam lingkungan produksi, di mana Anda tidak ingin program Anda berhenti begitu saja ketika ada masalah. Kemudian Anda dapat mengambil tumpukan dump di log Anda dan menambahkan ke kode Anda untuk menangani pengecualian spesifik lebih lanjut di rantai panggilan dan dengan cara yang lebih anggun.
Perhatikan juga bahwa ada idiom Ruby lain yang memiliki efek yang sama:
a = do_something rescue "something else"
Di baris ini, jika do_something
memunculkan pengecualian, itu ditangkap oleh Ruby, dibuang, dan a
ditugaskan "something else"
.
Secara umum, jangan lakukan itu, kecuali dalam kasus khusus di mana Anda tahu Anda tidak perlu khawatir. Satu contoh:
debugger rescue nil
The debugger
Fungsi adalah cara yang agak bagus untuk mengatur breakpoint dalam kode Anda, tetapi jika berjalan di luar debugger, dan Rails, hal itu menimbulkan pengecualian. Sekarang secara teoritis Anda seharusnya tidak meninggalkan kode debug di sekitar program Anda (pff! Tidak ada yang melakukan itu!) Tetapi Anda mungkin ingin menyimpannya di sana untuk sementara waktu untuk beberapa alasan, tetapi tidak terus menjalankan debugger Anda.
catatan:
Jika Anda menjalankan program orang lain yang menangkap pengecualian sinyal dan mengabaikannya, (katakan kode di atas), maka:
pgrep ruby
, atau ps | grep ruby
, cari PID program Anda yang menyinggung, dan kemudian jalankan kill -9 <PID>
. Jika Anda bekerja dengan program orang lain yang, untuk alasan apa pun, dibumbui dengan blok pengabaian ini, maka menempatkan ini di bagian atas garis utama adalah salah satu cara untuk keluar:
%W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
Hal ini menyebabkan program merespons sinyal terminasi normal dengan segera menghentikan, melewati penangan pengecualian, tanpa pembersihan . Sehingga bisa menyebabkan kehilangan data atau sejenisnya. Hati-hati!
Jika Anda perlu melakukan ini:
begin
do_something
rescue Exception => e
critical_cleanup
raise
end
Anda benar-benar dapat melakukan ini:
begin
do_something
ensure
critical_cleanup
end
Dalam kasus kedua, critical cleanup
akan dipanggil setiap kali, apakah ada pengecualian atau tidak.
kill -9
.
ensure
akan berjalan terlepas dari apakah ada pengecualian yang dimunculkan atau tidak, sementara rescue
itu hanya akan berjalan jika pengecualian muncul.
Jangan rescue Exception => e
(dan jangan naikkan kembali pengecualian) - atau Anda bisa mengusir jembatan.
Katakanlah Anda berada di dalam mobil (menjalankan Ruby). Anda baru-baru ini menginstal roda kemudi baru dengan sistem peningkatan over-the-air (yang menggunakan eval
), tetapi Anda tidak tahu salah satu programmer mengacaukan sintaks.
Anda berada di jembatan, dan menyadari bahwa Anda akan sedikit ke arah pagar, jadi Anda belok kiri.
def turn_left
self.turn left:
end
Ups! Itu mungkin Tidak Bagus ™, untungnya, Ruby memunculkan SyntaxError
.
Mobil harus segera berhenti - bukan?
Nggak.
begin
#...
eval self.steering_wheel
#...
rescue Exception => e
self.beep
self.log "Caught #{e}.", :warn
self.log "Logged Error - Continuing Process.", :info
end
bip bip
Peringatan: Pengecualian Tertangkap Sintaks.
Info: Kesalahan Tercatat - Proses Berlanjut.
Anda melihat sesuatu yang salah, dan Anda membanting pada istirahat darurat ( ^C
: Interrupt
)
bip bip
Peringatan: Pengecualian Terperangkap Terperangkap.
Info: Kesalahan Tercatat - Proses Berlanjut.
Ya - itu tidak banyak membantu. Anda cukup dekat dengan rel, jadi Anda menempatkan mobil di parkir ( kill
ing:) SignalException
.
bip bip
Peringatan: Pengecualian SignalException Tertangkap.
Info: Kesalahan Tercatat - Proses Berlanjut.
Pada detik terakhir, Anda mengeluarkan kunci ( kill -9
), dan mobil berhenti, Anda membanting ke depan ke roda kemudi (airbag tidak dapat mengembang karena Anda tidak menghentikan program dengan anggun - Anda menghentikannya), dan komputer di belakang mobil Anda terbanting ke kursi di depannya. Satu kaleng penuh Coke tumpah di atas kertas. Bahan makanan di bagian belakang dihancurkan, dan sebagian besar ditutupi dengan kuning telur dan susu. Mobil itu perlu perbaikan dan pembersihan yang serius. (Data hilang)
Semoga Anda memiliki asuransi (Cadangan). Oh ya - karena airbagnya tidak mengembang, Anda mungkin terluka (dipecat, dll).
Tapi tunggu! Adalebihalasan mengapa Anda mungkin ingin menggunakan rescue Exception => e
!
Katakanlah Anda adalah mobil itu, dan Anda ingin memastikan airbag mengembang jika mobil melebihi momentum berhenti yang aman.
begin
# do driving stuff
rescue Exception => e
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
raise
end
Ini pengecualian untuk aturan: Anda Exception
hanya dapat menangkap jika Anda menaikkan kembali pengecualian . Jadi, aturan yang lebih baik adalah tidak pernah menelan Exception
, dan selalu meningkatkan kembali kesalahan.
Namun menambahkan penyelamatan mudah untuk dilupakan dalam bahasa seperti Ruby, dan menempatkan pernyataan penyelamatan tepat sebelum mengangkat kembali masalah terasa sedikit tidak kering. Dan Anda tidak ingin melupakan raise
pernyataan itu. Dan jika Anda melakukannya, semoga berhasil mencari kesalahan itu.
Untungnya, Ruby luar biasa, Anda bisa menggunakan ensure
kata kunci, yang memastikan kode berjalan. Kata ensure
kunci akan menjalankan kode apa pun yang terjadi - jika ada pengecualian, jika tidak ada, satu-satunya pengecualian adalah jika dunia berakhir (atau peristiwa tidak terduga lainnya).
begin
# do driving stuff
ensure
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
end
Ledakan! Dan kode itu harus dijalankan. Satu-satunya alasan yang harus Anda gunakan rescue Exception => e
adalah jika Anda membutuhkan akses ke pengecualian, atau jika Anda hanya ingin kode dijalankan pada pengecualian. Dan ingatlah untuk menaikkan kembali kesalahan. Setiap saat.
Catatan: Seperti yang ditunjukkan @Niall, pastikan selalu berjalan. Ini bagus karena kadang-kadang program Anda bisa berbohong kepada Anda dan tidak melempar pengecualian, bahkan ketika masalah terjadi. Dengan tugas-tugas penting, seperti menggembungkan kantung udara, Anda harus memastikan itu terjadi apa pun yang terjadi. Karena itu, memeriksa setiap kali mobil berhenti, apakah ada pengecualian atau tidak, adalah ide yang bagus. Meskipun menggembungkan airbag sedikit tugas yang tidak biasa dalam kebanyakan konteks pemrograman, ini sebenarnya cukup umum dengan sebagian besar tugas pembersihan.
ensure
sebagai alternatif untuk rescue Exception
menyesatkan - contohnya menyiratkan mereka setara, tetapi sebagaimana dinyatakan ensure
akan terjadi apakah ada Pengecualian atau tidak, jadi sekarang kantung udara Anda akan mengembang karena Anda melebihi 5 mph meskipun tidak ada yang salah.
Karena ini menangkap semua pengecualian. Tidak mungkin program Anda dapat pulih dari apa pun satunya.
Anda harus menangani hanya pengecualian yang Anda tahu cara memulihkannya. Jika Anda tidak mengantisipasi jenis pengecualian tertentu, jangan menanganinya, crash dengan keras (tulis detail ke log), lalu diagnosa log dan perbaiki kode.
Menelan pengecualian itu buruk, jangan lakukan ini.
Itu kasus spesifik aturan bahwa Anda tidak harus menangkap setiap pengecualian Anda tidak tahu bagaimana menangani. Jika Anda tidak tahu cara menanganinya, selalu lebih baik membiarkan bagian lain dari sistem menangkap dan menanganinya.
Saya baru saja membaca posting blog yang bagus tentang itu di honeybadger.io :
Mengapa Anda tidak harus menyelamatkan Exception
Masalah dengan menyelamatkan Exception adalah bahwa itu benar-benar menyelamatkan setiap pengecualian yang mewarisi dari Exception. Yang mana .... semuanya!
Itu masalah karena ada beberapa pengecualian yang digunakan secara internal oleh Ruby. Mereka tidak ada hubungannya dengan aplikasi Anda, dan menelannya akan menyebabkan hal-hal buruk terjadi.
Berikut adalah beberapa yang besar:
SignalException :: Interrupt - Jika Anda menyelamatkan ini, Anda tidak dapat keluar dari aplikasi Anda dengan menekan control-c.
ScriptError :: SyntaxError - Menelan kesalahan sintaks berarti bahwa hal-hal seperti menempatkan ("Lupa sesuatu) akan gagal diam-diam.
NoMemoryError - Ingin tahu apa yang terjadi ketika program Anda terus berjalan setelah menggunakan semua RAM? Aku juga tidak.
begin do_something() rescue Exception => e # Don't do this. This will swallow every single exception. Nothing gets past it. end
Saya menduga Anda tidak benar-benar ingin menelan semua pengecualian tingkat sistem ini. Anda hanya ingin menangkap semua kesalahan level aplikasi Anda. Pengecualian menyebabkan kode ANDA.
Untungnya, ada cara mudah untuk ini.