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?
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?
Yanıtlar:
TL; DR : StandardError
Genel 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 Exception
muhtemelen iyidir.
Exception
köküdür Ruby'nin istisna hiyerarşisi , bu yüzden rescue Exception
sizden kurtarmak her şeyi gibi alt sınıfları da dahil olmak üzere, SyntaxError
, LoadError
ve 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ı SyntaxError
ifade 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 Exception
varsayı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 StandardError
ve 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 Exception
biri, 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
Throwable
java yakalamak gibi
ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error, Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]
ve sonrarescue *ADAPTER_ERRORS => e
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:
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_something
bir istisna ortaya çıkarsa , Ruby tarafından yakalanır, atılır ve a
atanır "something else"
.
Genellikle endişelenmenize gerek olmadığını bildiğiniz özel durumlar dışında bunu yapmayın. Bir örnek:
debugger rescue nil
debugger
Fonksiyon 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:
Sinyal istisnalarını yakalayan ve yok sayan başka birinin programını çalıştırdıysanız (yukarıdaki kodu söyleyin):
pgrep ruby
veya ps | grep ruby
rahatsız edici programınızın PID'sini arayın ve çalıştırın kill -9 <PID>
. 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!
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 cleanup
bir istisna atılıp atılmasa da her seferinde çağrılacaktır.
kill -9
.
ensure
değildir, bir istisna olup olmadığına bakılmaksızın çalışır, ancak rescue
yalnızca bir istisna ortaya çıkarsa çalışır.
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 ( kill
ing:) 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 Exception
ve 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 raise
deyimi. Ve eğer yaparsanız, bu hatayı bulmaya çalışırken iyi şanslar.
Neyse ki, Ruby harika, sadece ensure
kodun çalışmasını sağlayan anahtar kelimeyi kullanabilirsiniz . ensure
Anahtar 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.
ensure
bir alternatif olarak bölüm rescue Exception
yanıltıcıdır - örnek, eşdeğer olduklarını ima eder, ancak belirtildiği gibi, ensure
bir İ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.
Çü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.
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.
Ben sadece honeybadger.io bu konuda harika bir blog yazısı okudum :
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.