Pertanyaan Mengapa gaya buruk untuk `rescue Exception => e` di Ruby?


Ryan Davis Ruby QuickRef mengatakan (tanpa penjelasan):

Jangan selamatkan Pengecualian. PERNAH. atau aku akan menusukmu.

Kenapa tidak? Apa hal yang benar untuk dilakukan?


800
2018-04-06 19:17


asal


Jawaban:


TL; DR: Gunakan StandardError bukan untuk menangkap pengecualian umum. Ketika pengecualian asli dinaikkan kembali (mis. Saat menyelamatkan untuk mencatat pengecualian), menyelamatkan Exception mungkin baik-baik saja.


Exception adalah akar dari Hirarki perkecualian Ruby, jadi ketika kamu rescue Exception Anda menyelamatkan dari segala sesuatu, termasuk subclass seperti SyntaxError, LoadError, dan Interrupt.

Menyelamatkan Interrupt mencegah pengguna menggunakan CTRLC untuk keluar dari program.

Menyelamatkan SignalException mencegah program merespon dengan benar terhadap sinyal. Ini tidak akan bisa dikerjakan kecuali oleh kill -9.

Menyelamatkan SyntaxError maksudnya evals yang gagal akan melakukannya diam-diam.

Semua ini dapat ditunjukkan dengan menjalankan program ini, dan berusaha CTRLC atau kill saya t:

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 standarnya. Perbuatan

begin
  # iceberg!
rescue
  # lifeboats
end

tidak menyelamatkan dari Exception, ia menyelamatkan dari StandardError. Anda umumnya harus menentukan sesuatu yang lebih spesifik daripada default StandardError, tetapi menyelamatkan dari Exception  meluas ruang lingkup daripada mempersempitnya, dan dapat memiliki hasil bencana dan membuat perburuan bug sangat sulit.


Jika Anda memiliki situasi di mana Anda ingin menyelamatkannya StandardError dan Anda memerlukan 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 di mana Anda harus menyelamatkannya Exception adalah untuk keperluan logging / pelaporan, dalam hal ini Anda harus segera kembali menaikkan pengecualian:

begin
  # iceberg?
rescue Exception => e
  # do some logging
  raise e  # not enough lifeboats ;)
end

1240
2018-04-06 19:38



Itu nyata aturannya adalah: Jangan membuang pengecualian. Objektivitas penulis kutipan Anda dipertanyakan, sebagaimana dibuktikan oleh fakta bahwa itu berakhir dengan

atau aku akan menusukmu

Tentu saja, ketahuilah bahwa sinyal (secara default) membuang pengecualian, dan proses yang biasanya berjalan lama diakhiri melalui sinyal, sehingga menangkap Eksepsi dan tidak mengakhiri pada pengecualian sinyal akan membuat program Anda sangat sulit dihentikan. 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 berhasil.

Namun, katakanlah Anda memiliki server berulir dan Anda ingin semua pengecualian tidak:

  1. diabaikan (default)
  2. hentikan server (yang terjadi jika Anda mengatakannya thread.abort_on_exception = true).

Maka ini dapat diterima di thread 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 bekerja untuk variasi pengontrol pengecualian standar Ruby, dengan keuntungan yang tidak juga membunuh program Anda. Rails melakukan ini di handler permintaannya.

Pengecualian sinyal muncul di utas utama. Latar belakang tidak akan mendapatkannya, jadi tidak ada gunanya mencoba menangkapnya di sana.

Ini sangat berguna dalam lingkungan produksi, di mana Anda melakukannya tidak ingin program Anda berhenti begitu saja setiap kali terjadi kesalahan. Kemudian Anda dapat mengambil tumpukan tumpukan di log Anda dan menambahkan kode Anda untuk menangani pengecualian khusus di rantai panggilan lebih jauh dan dengan cara yang lebih anggun.

Perhatikan juga bahwa ada idiom Ruby lain yang memiliki banyak 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-kasus khusus di mana Anda tahu kamu tidak perlu khawatir. Satu contoh:

debugger rescue nil

Itu debugger fungsi adalah cara yang agak bagus untuk mengatur breakpoint dalam kode Anda, tetapi jika berjalan di luar debugger, dan Rails, itu menimbulkan pengecualian. Sekarang secara teoritis Anda tidak boleh meninggalkan kode debug di dalam 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:

  1. Jika Anda telah menjalankan program orang lain yang menangkap pengecualian sinyal dan mengabaikannya, (katakan kode di atas) maka:

    • di Linux, dalam sebuah shell, ketik pgrep ruby, atau ps | grep ruby, cari PID program Anda yang melanggar, lalu jalankan kill -9 <PID>.
    • di Windows, gunakan Task Manager (CTRL-BERGESER-ESC), buka tab "proses", temukan proses Anda, klik kanan dan pilih "Akhiri proses".
  2. Jika Anda bekerja dengan program orang lain yang, untuk alasan apa pun, dibumbui dengan blok pengecualian-pengecualian ini, maka menempatkan ini di atas garis utama adalah satu kemungkinan cop-out:

    %W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
    

    Ini menyebabkan program untuk menanggapi sinyal terminasi normal dengan segera mengakhiri, melewati penangan pengecualian, tanpa pembersihan. Sehingga bisa menyebabkan kehilangan data atau serupa. Hati-hati!

  3. 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 saat, apakah pengecualian atau tidak dilemparkan.


73
2018-04-07 05:30



Katakanlah Anda berada di mobil (menjalankan Ruby). Anda baru-baru ini memasang kemudi baru dengan sistem peningkatan melalui udara (yang menggunakan eval), tetapi Anda tidak tahu salah satu programmer mengacaukan sintaks.

Anda berada di jembatan, dan menyadari bahwa Anda akan sedikit mengarah ke pagar, jadi Anda belok kiri.

def turn_left
  self.turn left:
end

oops! Itu mungkin Tidak baik, untungnya, Ruby memunculkan SyntaxError.

Mobil harus segera berhenti - benar?

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: Tertangkap SyntaxError Exception.

Info: Kesalahan Dicatat - Proses Berkelanjutan.

Anda melihat ada sesuatu yang salah, dan Anda membanting pada jeda darurat (^C: Interrupt)

bip bip

Peringatan: Tertangkap Interupsi Pengecualian.

Info: Kesalahan Dicatat - Proses Berkelanjutan.

Ya - itu tidak banyak membantu. Anda cukup dekat dengan rel, jadi Anda menaruh mobil di taman (killing: SignalException).

bip bip

Peringatan: Penangkapan Eksepsi SignalException.

Info: Kesalahan Dicatat - Proses Berkelanjutan.

Pada detik terakhir, Anda menarik kunci (kill -9), dan mobil berhenti, Anda membanting ke depan ke setir (kantung udara tidak dapat mengembang karena Anda tidak menghentikan program dengan anggun - Anda menghentikannya), dan komputer di belakang mobil Anda terbanting ke kursi di di depannya. Setengah kaleng Coke tumpah di atas kertas. Bahan makanan di belakang hancur, dan sebagian besar tertutup kuning telur dan susu. Mobil membutuhkan perbaikan dan pembersihan yang serius. (Data hilang)

Semoga Anda memiliki asuransi (Backup). Oh ya - karena airbag tidak mengembang, Anda mungkin terluka (dipecat, dll).


Tapi tunggu! Ada lebih alasan mengapa Anda mungkin ingin menggunakannya rescue Exception => e!

Katakanlah Anda mobil itu, dan Anda ingin memastikan kantung udara mengembang jika mobil melaju lebih dari 5 mph sebelum berhenti.

 begin 
    # do driving stuff
 rescue Exception => e
    self.airbags.inflate if self.speed >= 5.mph 
    raise
 end

Inilah pengecualian untuk aturan: Anda dapat menangkap Exception  hanya jika Anda kembali menaikkan pengecualian. Jadi, aturan yang lebih baik adalah tidak pernah menelan Exception, dan selalu kembali menaikkan kesalahan.

Namun menambahkan penyelamatan mudah dilupakan dalam bahasa seperti Ruby, dan menempatkan pernyataan penyelamatan tepat sebelum memunculkan kembali masalah terasa sedikit non-KERING. Dan kau tidak ingin melupakan raise pernyataan. Dan jika Anda melakukannya, semoga berhasil menemukan kesalahan itu.

Untungnya, Ruby luar biasa, Anda bisa menggunakan ensure kata kunci, yang memastikan kode berjalan. Itu ensure kata kunci akan menjalankan kode tidak peduli apa - jika pengecualian dilemparkan, jika tidak ada, satu-satunya pengecualian adalah jika dunia berakhir (atau kejadian yang tidak mungkin terjadi).

 begin 
    # do driving stuff
 ensure
    self.airbags.inflate if self.speed >= 5.mph 
 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 berjalan pada pengecualian. Dan ingat untuk kembali menaikkan kesalahan. Setiap saat. Atau Anda akan memiliki 3 orang yang menikam Anda (termasuk bos Anda).


TL; DR

Jangan rescue Exception => e (dan tidak kembali menaikkan pengecualian) - atau Anda mungkin keluar dari jembatan.


46
2018-01-31 23:55



Karena ini menangkap semua pengecualian. Tidak mungkin program Anda dapat pulih apa saja dari mereka.

Anda hanya perlu menangani pengecualian yang Anda tahu cara memulihkannya. Jika Anda tidak mengantisipasi pengecualian jenis tertentu, jangan mengatasinya, tabrak keras (tuliskan detail ke log), lalu diagnosis log dan perbaiki kode.

Menelan pengecualian itu buruk, jangan lakukan ini.


43
2018-04-06 19:21



Itu adalah kasus spesifik dari aturan yang tidak seharusnya Anda tangkap apa saja kecuali Anda tidak tahu cara mengatasinya. Jika Anda tidak tahu cara mengatasinya, selalu lebih baik untuk membiarkan beberapa bagian lain dari sistem menangkap dan menanganinya.


8
2018-04-06 23:54



Ini juga akan menyembunyikan bug dari Anda, misalnya jika Anda salah ketik nama metode:

def my_fun
  "my_fun"
end

begin
 # you mistypped my_fun to my_func
 my_func # my_func()
rescue Exception
  # rescued NameError (or NoMethodError if you called method with parenthesis)
end

1
2018-03-25 09:11