Ruby'de Exception => e`yi kurtarmak neden kötü bir stil?


895

Ryan Davis'ten Ruby QuickRef diyor ki (açıklama yapmadan):

İstisna kurtarma. HİÇ. yoksa seni bıçaklayacağım.

Neden olmasın? Yapılacak doğru şey nedir?


35
Sonra muhtemelen kendi yazabilirsiniz? :)
Sergio Tulentsev

65
Burada şiddet çağrısından çok rahatsızım. Sadece programlama.
Darth Egregious

1
Ruby Exception'daki bu makaleye güzel bir Ruby Exception Hiyerarşisi ile göz atın .
Atul Khanduri

2
Çünkü Ryan Davis seni bıçaklayacak. Çocuklar. İstisnaları asla kurtarmayın.
Mugen

7
@DarthEgregious Şaka yapıp yapmadığınızı gerçekten söyleyemem. Ama bence çok komik. (Ve açıkçası ciddi bir tehdit değil). Şimdi İstisna yakalamayı her düşündüğümde, internetteki rastgele bir adam tarafından bıçaklanmaya değip değmeyeceğini düşünüyorum.
Steve Sether

Yanıtlar:


1375

TL; DR : StandardErrorGenel istisna yakalama için kullanın . Orijinal kural dışı durum yeniden yükseltildiğinde (örneğin yalnızca kural dışı durumun günlüğe kaydedilmesi için kurtarırken), kurtarma işlemi Exceptionmuhtemelen iyidir.


Exceptionköküdür Ruby'nin istisna hiyerarşisi , bu yüzden rescue Exceptionsizden kurtarmak her şeyi gibi alt sınıfları da dahil olmak üzere, SyntaxError, LoadErrorve Interrupt.

Kurtarma Interrupt, kullanıcının CTRLCprogramdan çıkmak için kullanmasını engeller .

Kurtarma SignalException, programın sinyallere doğru yanıt vermesini önler. Haricinde kullanılamaz kill -9.

Kurtarmak , başarısız olanların sessizce yapacağını SyntaxErrorifade eder eval.

Bunların hepsi bu programı çalıştırarak CTRLCveya yapmaya çalışarak gösterilebilir kill:

loop do
  begin
    sleep 1
    eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
  rescue Exception
    puts "I refuse to fail or be stopped!"
  end
end

Buradan kurtarma Exceptionvarsayılan bile değildir. iş

begin
  # iceberg!
rescue
  # lifeboats
end

kurtarmaz Exception, kurtarır StandardError. Genellikle varsayılandan daha spesifik bir şey belirtmelisiniz StandardError, ancak kapsamı daraltmak yerine Exception genişletir ve felaketle sonuçlanabilir ve böcek avını son derece zorlaştırabilirsiniz.


Kurtarmak istediğiniz bir durumunuz varsa StandardErrorve istisna dışında bir değişkene ihtiyacınız varsa, bu formu kullanabilirsiniz:

begin
  # iceberg!
rescue => e
  # lifeboats
end

eşdeğer:

begin
  # iceberg!
rescue StandardError => e
  # lifeboats
end

Kurtarmanın akılcı olduğu birkaç yaygın durumdan Exceptionbiri, günlük kaydı / raporlama amaçlıdır, bu durumda istisnayı hemen yeniden yükseltmelisiniz:

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

129
bu yüzden Throwablejava yakalamak gibi
cırcır ucube

53
Bu tavsiye temiz bir Ruby ortamı için iyidir. Ancak maalesef bazı taşlar doğrudan İstisnadan inen istisnalar yarattı. Ortamımızda 30 tane vardır: örn. OpenID :: Sunucu :: EncodingError, OAuth :: InvalidRequest, HTMLTokenizerSample. Bunlar, standart kurtarma bloklarında çok fazla yakalamak isteyeceğiniz istisnalardır. Ne yazık ki, Ruby'deki hiçbir şey değerli taşların doğrudan İstisnadan miras kalmasını engellemez veya cesaretini kırmaz - adlandırma bile kasıtsızdır.
Jonathan Swartz

20
@JonathanSwartz İstisnadan değil, bu alt sınıflardan kurtulun. Daha spesifik olan neredeyse her zaman daha iyi ve daha nettir.
Andrew Marshall

22
@JonathanSwartz - Mücevher yaratıcılarının istisnalarının miras aldıklarını değiştirmek için hata yapardım. Şahsen, taşlarımın tüm istisnaların MyGemException'dan almasını istiyorum, böylece isterseniz kurtarabilirsiniz.
Nathan Long

12
Ayrıca ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error, Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]ve sonrarescue *ADAPTER_ERRORS => e
j_mcnally

83

Gerçek kural şudur: istisnalar kenara atma. Alıntı yaptığınız yazarın nesnelliği tartışmalı, bununla bittiği gerçeği

yoksa seni bıçaklayacağım

Tabii ki, sinyallerin (varsayılan olarak) istisnalar attığını ve normalde uzun süren işlemlerin bir sinyal yoluyla sonlandığını unutmayın, bu nedenle İstisna yakalamak ve sinyal istisnalarını sonlandırmamak programınızın durmasını zorlaştıracaktır. Yani bunu yapma:

#! /usr/bin/ruby

while true do
  begin
    line = STDIN.gets
    # heavy processing
  rescue Exception => e
    puts "caught exception #{e}! ohnoes!"
  end
end

Hayır, gerçekten yapma. Çalışıp çalışmadığını görmek için bile çalıştırmayın.

Ancak, dişli bir sunucunuz olduğunu ve tüm istisnaların olmamasını istediğinizi varsayalım:

  1. yoksayılır (varsayılan)
  2. sunucuyu durdurun (söylerseniz olur thread.abort_on_exception = true).

O zaman bu bağlantı taşıma iş parçacığında mükemmel kabul edilebilir:

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

Yukarıda, programınızı da öldürmemesi avantajı ile Ruby'nin varsayılan istisna işleyicisinin bir varyasyonu için çalışır. Rails bunu istek işleyicisinde yapar.

Ana istisnada sinyal istisnaları ortaya çıkar. Arka plan konuları onları alamaz, bu yüzden onları orada yakalamaya çalışmanın bir anlamı yoktur.

Bu do bir üretim ortamında, özellikle yararlıdır değil bir şeyler ters gittiğinde sadece durdurmak için program istiyorum. Daha sonra, günlüklerinizdeki yığın dökümlerini alabilir ve çağrı zincirinin aşağısında ve daha zarif bir şekilde özel istisnalarla başa çıkmak için kodunuza ekleyebilirsiniz.

Aynı etkiye sahip başka bir Ruby deyimi olduğunu da unutmayın:

a = do_something rescue "something else"

Bu satırda, do_somethingbir istisna ortaya çıkarsa , Ruby tarafından yakalanır, atılır ve aatanır "something else".

Genellikle endişelenmenize gerek olmadığını bildiğiniz özel durumlar dışında bunu yapmayın. Bir örnek:

debugger rescue nil

debuggerFonksiyon kodunuzda kesme noktası ayarlamak için oldukça güzel yoldur, ama bir hata ayıklayıcı dışında çalışan ve Raylar, eğer bir özel durum oluşturur. Şimdi teorik olarak programınızda hata ayıklama kodu bırakmamalısınız (pff! Kimse bunu yapmaz!) Ama bir süre orada tutmak isteyebilirsiniz, ancak hata ayıklayıcınızı sürekli çalıştırmayın.

Not:

  1. Sinyal istisnalarını yakalayan ve yok sayan başka birinin programını çalıştırdıysanız (yukarıdaki kodu söyleyin):

    • Linux'ta bir kabukta yazın pgrep rubyveya ps | grep rubyrahatsız edici programınızın PID'sini arayın ve çalıştırın kill -9 <PID>.
    • Windows'da Görev Yöneticisi'ni ( CTRL- SHIFT- ESC) kullanın, "işlemler" sekmesine gidin, işleminizi bulun, sağ tıklayın ve "İşlemi sonlandır" ı seçin.
  2. Herhangi bir nedenle, bu görmezden gelme bloklarıyla biberlenmiş başka bir programla çalışıyorsanız, bunu ana hattın en üstüne koymak olası bir dışlamadır:

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

    Bu, programın normal sonlandırma sinyallerine, özel durum işleyicilerini baypas ederek ve herhangi bir temizleme işlemi yapmadan hemen sonlandırarak yanıt vermesine neden olur . Böylece veri kaybına veya benzerine neden olabilir. Dikkatli ol!

  3. Bunu yapmanız gerekiyorsa:

    begin
      do_something
    rescue Exception => e
      critical_cleanup
      raise
    end
    

    aslında bunu yapabilirsiniz:

    begin
      do_something
    ensure
      critical_cleanup
    end
    

    İkinci durumda, critical cleanupbir istisna atılıp atılmasa da her seferinde çağrılacaktır.


21
Üzgünüm, bu yanlış. Sunucu asla Exception'ı kurtarmamalı ve günlüğe kaydetmekten başka bir şey yapmamalıdır . Bu, hariç olmak üzere onu kilimsiz hale getirecektir kill -9.
John

8
Not 3'teki örnekleriniz denk ensuredeğildir, bir istisna olup olmadığına bakılmaksızın çalışır, ancak rescueyalnızca bir istisna ortaya çıkarsa çalışır.
Andrew Marshall

1
Tam olarak / eşdeğer değiller ama eşdeğerliği çirkin olmayan bir şekilde nasıl özlü bir şekilde ifade edeceğimizi anlayamıyorum.
Michael Slade

3
İlk örnekte, başlatma / kurtarma bloğundan sonra başka bir kritik_temiz çağrısı ekleyin. En zarif kodu kabul etmiyorum, ama açıkçası ikinci örnek bunu yapmanın zarif yoludur, bu yüzden küçük bir beceriksizlik örneğin sadece bir parçasıdır.
gtd

3
"Çalışıp çalışmadığını görmek için bile çalıştırmayın." kodlama için kötü bir tavsiye gibi görünüyor ... Aksine, onu çalıştırmanızı, başarısız olduğunu görmenizi ve başkalarına körü körüne inanmak yerine başarısız olup olmadığını kendiniz anlamanızı tavsiye ederim. Yine de büyük cevap :)
huelbois

69

TL; DR

Yapmayın rescue Exception => e(istisnayı yeniden yükseltmeyin) - yoksa köprüden çıkabilirsiniz .


Diyelim ki bir arabadasın (Ruby çalışıyor). Kısa bir süre önce, havadan yükseltme sistemi (kullanan eval) ile yeni bir direksiyon taktınız , ancak programcılardan birinin sözdizimine karıştığını bilmiyordunuz.

Bir köprünün üzerindesin ve korkuluklara doğru gittiğini fark et, böylece sola dönersin.

def turn_left
  self.turn left:
end

ayy! Muhtemelen İyi Değil ™, Neyse ki, Ruby a SyntaxError.

Araba hemen durmalı - değil mi?

Hayır!

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

Uyarı: SyntaxError İstisnası Yakalandı.

Bilgi: Günlüğe Kaydedilen Hata - Devam Ediyor.

Sen bir şey yanlış fark ve acil sonları slam ( ^C: Interrupt)

bip bip

Uyarı: Yakalama Kesme İstisnası.

Bilgi: Günlüğe Kaydedilen Hata - Devam Ediyor.

Evet - bu pek yardımcı olmadı. Demiryoluna oldukça yakınsınız, o yüzden arabayı parka koyuyorsunuz ( killing:) SignalException.

bip bip

Uyarı: SignalException İstisnası Yakalandı.

Bilgi: Günlüğe Kaydedilen Hata - Devam Ediyor.

Son saniyede, tuşları ( kill -9) çıkarırsınız ve araba durur, direksiyon simidine doğru ilerlersiniz (hava yastığı şişemez, çünkü programı zarif bir şekilde durdurmadınız - sonlandırdınız) ve bilgisayar arabanın arkasında önündeki koltuğa çarpıyor. Yarım dolu kola kağıtların üzerine dökülür. Arkadaki yiyecekler ezilir ve çoğu yumurta sarısı ve sütle kaplıdır. Araç ciddi bir onarım ve temizliğe ihtiyaç duyar. (Veri kaybı)

Umarım sigortanız vardır (Yedekler). Oh evet - çünkü hava yastığı şişmedi, muhtemelen incindiniz (kovuluyor vb.).


Fakat bekle! OradaDahakullanmak isteyebileceğinizin nedenleri rescue Exception => e!

Diyelim ki o araba sizsiniz ve araç güvenli durma momentumunu aşarsa hava yastığının şiştiğinden emin olmak istiyorsunuz.

 begin 
    # do driving stuff
 rescue Exception => e
    self.airbags.inflate if self.exceeding_safe_stopping_momentum?
    raise
 end

Kuralın istisnası şöyledir: Exception Yalnızca istisnayı yeniden yükseltirseniz yakalayabilirsiniz . Bu nedenle, daha iyi bir kural asla yutmamak Exceptionve hatayı her zaman yeniden yükseltmektir.

Ancak, Ruby gibi bir dilde kurtarma eklemek hem unutmak kolaydır, hem de bir sorunu yeniden gündeme getirmeden hemen önce bir kurtarma ifadesi koymak biraz KURU olmayan hisseder. Ve yok unutmak istiyorum raisedeyimi. Ve eğer yaparsanız, bu hatayı bulmaya çalışırken iyi şanslar.

Neyse ki, Ruby harika, sadece ensurekodun çalışmasını sağlayan anahtar kelimeyi kullanabilirsiniz . ensureAnahtar kelime ne olursa olsun kod çalışacaktır - bir özel durum ise aynı değilse, tek istisna ise olmanın dünyanın sonudur (ya da diğer olası olaylar).

 begin 
    # do driving stuff
 ensure
    self.airbags.inflate if self.exceeding_safe_stopping_momentum?
 end

Boom! Ve bu kod zaten çalışmalı. Kullanmanız gereken tek neden rescue Exception => e, istisnaya erişmeniz gerektiğinde veya yalnızca bir istisnada kodun çalışmasını istiyorsanız. Ve hatayı tekrar yükseltmeyi unutmayın. Her zaman.

Not: @Niall'in işaret ettiği gibi, her zaman çalıştığından emin olun . Bu iyidir, çünkü bazen programınız size yalan söyleyebilir ve sorunlar ortaya çıktığında bile istisnalar atmayabilir. Hava yastıklarını şişirmek gibi kritik görevlerde, ne olursa olsun bunun gerçekleştiğinden emin olmanız gerekir. Bu nedenle, bir istisna atıp atılmasa da, otomobilin her duruşunda kontrol etmek iyi bir fikirdir. Hava yastıklarının şişirilmesi çoğu programlama bağlamında biraz nadir bir görev olsa da, aslında çoğu temizleme görevinde oldukça yaygındır.


12
Hahahaha! Bu harika bir cevap. Kimsenin yorum yapmadığı için şok oldum. Her şeyi gerçekten anlaşılır kılan net bir senaryo veriyorsunuz. Şerefe! :-)
James Milani

@JamesMilani Teşekkür ederim!
Ben Aubin

3
+ 💯 bu cevap için. Keşke birden fazla oy verebilseydim! 😂
engineerDave

1
Cevabınızı beğendim!
Atul Vaibhav

3
Bu cevap, mükemmel anlaşılabilir ve doğru kabul edilen cevaptan 4 yıl sonra geldi ve gerçekçi olmaktan çok eğlenceli olacak şekilde tasarlanmış saçma bir senaryo ile tekrar açıkladı. Bir buzzkill olduğum için üzgünüm, ama bu reddit değil - cevapların özlü ve doğru olması komik olmaktan daha önemlidir. Ayrıca, ensurebir alternatif olarak bölüm rescue Exceptionyanıltıcıdır - örnek, eşdeğer olduklarını ima eder, ancak belirtildiği gibi, ensurebir İstisna olsun ya da olmasın gerçekleşecektir, bu yüzden şimdi hava yastıklarınız şişecek çünkü hiçbir şey yanlış gitmedi bile.
Niall

47

Çünkü bu tüm istisnaları yakalar. Programınızın bunlardan herhangi birinden kurtulabilmesi olası değildir .

Yalnızca nasıl kurtarılacağını bildiğiniz istisnaları ele almalısınız. Belirli bir istisna öngörmüyorsanız, işlemeyin, yüksek sesle çökün (günlüğe ayrıntılar yazın), sonra günlükleri teşhis edin ve kodu düzeltin.

İstisnaları yutmak kötüdür, bunu yapma.


10

Bu, nasıl ele alınacağını bilmediğiniz bir istisnayı yakalamamanız gereken kuralın özel bir örneğidir . Bununla nasıl başa çıkacağınızı bilmiyorsanız, sistemin başka bir kısmının yakalayıp işlemesine izin vermek her zaman daha iyidir.


0

Ben sadece honeybadger.io bu konuda harika bir blog yazısı okudum :

Ruby'nin Exception ve StandardError: Farkı nedir?

Neden İstisna'yı Kurtarmamalısınız?

İstisna kurtarma ile ilgili problem aslında İstisnadan devralan her istisnayı kurtarmasıdır. Hangisi ... hepsi!

Bu bir sorun, çünkü Ruby tarafından dahili olarak kullanılan bazı istisnalar var. Uygulamanızla hiçbir ilgisi yoktur ve onları yutmak kötü şeylerin olmasına neden olur.

İşte büyük olanlardan birkaçı:

  • SignalException :: Interrupt - Bunu kurtarırsanız, control-c öğesine basarak uygulamanızdan çıkamazsınız.

  • ScriptError :: SyntaxError - Sözdizimi hatalarını yutmak, koyar ("Bir şeyi unuttum) gibi şeylerin sessizce başarısız olacağı anlamına gelir.

  • NoMemoryError - Programınız tüm RAM'i kullandıktan sonra çalışmaya devam ettiğinde ne olacağını bilmek ister misiniz? Ben de değil.

begin
  do_something()
rescue Exception => e
  # Don't do this. This will swallow every single exception. Nothing gets past it. 
end

Bu sistem düzeyindeki istisnaların hiçbirini gerçekten yutmak istemediğinizi tahmin ediyorum. Yalnızca tüm uygulama düzeyi hatalarınızı yakalamak istiyorsunuz. İstisnalar SİZİN kodunuza neden oldu.

Neyse ki, bunun kolay bir yolu var.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.