Ryana Davisa Ruby QuickRef mówi (bez wyjaśnienia):
Nie ratuj wyjątku. ZAWSZE. albo cię dźgnę.
Dlaczego nie? Co należy zrobić?
Ryana Davisa Ruby QuickRef mówi (bez wyjaśnienia):
Nie ratuj wyjątku. ZAWSZE. albo cię dźgnę.
Dlaczego nie? Co należy zrobić?
Odpowiedzi:
TL; DR : Użyj StandardError
zamiast tego do wychwytywania wyjątków ogólnych. Kiedy pierwotny wyjątek zostanie ponownie zgłoszony (np. Podczas ratowania w celu zarejestrowania wyjątku), ratowanie Exception
jest prawdopodobnie w porządku.
Exception
jest korzeniem hierarchii wyjątków Ruby , więc gdy rescue Exception
ratowanie od wszystkiego , w tym podklas, takich jak SyntaxError
, LoadError
i Interrupt
.
Uratowanie Interrupt
uniemożliwia użytkownikowi CTRLCzamknięcie programu.
Ratowanie SignalException
zapobiega prawidłowej reakcji programu na sygnały. Będzie to niemożliwe do zabicia, chyba że przez kill -9
.
Ratowanie SyntaxError
oznacza, eval
że porażka zrobi to po cichu.
Wszystkie one mogą być wyświetlane przez ten program działa, i stara się CTRLCbądź kill
to:
loop do
begin
sleep 1
eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
rescue Exception
puts "I refuse to fail or be stopped!"
end
end
Ratowanie przed Exception
nie jest nawet domyślne. Robić
begin
# iceberg!
rescue
# lifeboats
end
nie ratuje Exception
, ratuje StandardError
. Powinieneś ogólnie określić coś bardziej szczegółowego niż domyślny StandardError
, ale ratowanie przed Exception
rozszerzeniem zakresu zamiast zawężeniem go, może mieć katastrofalne skutki i bardzo utrudnić polowanie na błędy.
Jeśli masz sytuację, w której chcesz uratować StandardError
i potrzebujesz zmiennej z wyjątkiem, możesz użyć tego formularza:
begin
# iceberg!
rescue => e
# lifeboats
end
co jest równoważne z:
begin
# iceberg!
rescue StandardError => e
# lifeboats
end
Jednym z niewielu typowych przypadków, w których warto ratować, Exception
jest rejestrowanie / raportowanie, w którym to przypadku należy natychmiast ponownie zgłosić wyjątek:
begin
# iceberg?
rescue Exception => e
# do some logging
raise # not enough lifeboats ;)
end
Throwable
w java
ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error, Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]
a następnierescue *ADAPTER_ERRORS => e
Prawdziwa zasada: Nie wyrzucać wyjątki. Obiektywność autora cytatu jest wątpliwa, o czym świadczy fakt, że kończy się on
albo cię dźgnę
Oczywiście należy pamiętać, że sygnały (domyślnie) generują wyjątki, a zwykle długotrwałe procesy są kończone przez sygnał, więc wychwycenie wyjątku i nie zakończenie wyjątków sygnału sprawi, że program będzie bardzo trudny do zatrzymania. Więc nie rób tego:
#! /usr/bin/ruby
while true do
begin
line = STDIN.gets
# heavy processing
rescue Exception => e
puts "caught exception #{e}! ohnoes!"
end
end
Nie, naprawdę, nie rób tego. Nawet nie uruchamiaj tego, aby zobaczyć, czy to działa.
Załóżmy jednak, że masz serwer wątkowy i chcesz, aby wszystkie wyjątki nie:
thread.abort_on_exception = true
). Jest to całkowicie dopuszczalne w wątku obsługi połączenia:
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
Powyższe działa w odmianie domyślnej procedury obsługi wyjątków Ruby, z tą zaletą, że nie zabija również twojego programu. Rails robi to w module obsługi żądań.
Wyjątki sygnałowe są zgłaszane w głównym wątku. Nici w tle ich nie dostaną, więc nie ma sensu próbować ich tam łapać.
Jest to szczególnie przydatne w środowisku produkcyjnym, w którym nie chcesz, aby Twój program po prostu zatrzymywał się, gdy coś pójdzie nie tak. Następnie możesz wykonać zrzuty stosu w dziennikach i dodać do kodu, aby zająć się określonym wyjątkiem w dalszej części łańcucha połączeń i w bardziej wdzięczny sposób.
Zauważ też, że istnieje inny idiom Ruby, który ma podobny efekt:
a = do_something rescue "something else"
W tej linii, jeśli do_something
podniesie wyjątek, zostaje złapany przez Ruby, wyrzucony i a
przypisany "something else"
.
Zasadniczo nie rób tego, z wyjątkiem szczególnych przypadków, w których wiesz , że nie musisz się martwić. Jeden przykład:
debugger rescue nil
The debugger
funkcja jest raczej dobrym sposobem na ustawienie punktu przerwania w kodzie, ale jeśli działa poza debuggerem i Railsami, zgłasza wyjątek. Teraz teoretycznie nie powinieneś zostawiać kodu debugującego leżącego w twoim programie (pff! Nikt tego nie robi!), Ale możesz chcieć go tam zatrzymać z jakiegoś powodu, ale nie uruchamiać debuggera w sposób ciągły.
Uwaga:
Jeśli uruchomiłeś program innej osoby, który przechwytuje wyjątki sygnałów i ignoruje je (powiedz kod powyżej), to:
pgrep ruby
lub ps | grep ruby
wyszukaj PID programu, który go narusza, a następnie uruchom kill -9 <PID>
. Jeśli pracujesz z programem innej osoby, który z jakiegoś powodu jest obsadzony tymi blokami wyjątków ignorowania, umieszczenie tego na górze głównej linii jest jednym z możliwych sposobów:
%W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
Powoduje to, że program reaguje na normalne sygnały zakończenia, natychmiast kończąc, omijając procedury obsługi wyjątków, bez czyszczenia . Może to spowodować utratę danych lub podobne. Bądź ostrożny!
Jeśli musisz to zrobić:
begin
do_something
rescue Exception => e
critical_cleanup
raise
end
możesz to zrobić:
begin
do_something
ensure
critical_cleanup
end
W drugim przypadku critical cleanup
będzie wywoływany za każdym razem, niezależnie od tego, czy zostanie zgłoszony wyjątek.
kill -9
.
ensure
będą działać niezależnie od tego, czy wystąpił wyjątek, czy nie, a rescue
będą działać tylko wtedy, gdy wyjątek został zgłoszony.
Nie rób rescue Exception => e
wyjątku (i nie podnosz go ponownie) - możesz też zjechać z mostu.
Powiedzmy, że jesteś w samochodzie (z Ruby). Niedawno zainstalowałeś nową kierownicę z bezprzewodowym systemem uaktualnień (który wykorzystuje eval
), ale nie wiedziałeś, że jeden z programistów pomylił się co do składni.
Jesteś na moście i zdajesz sobie sprawę, że idziesz trochę w stronę balustrady, więc skręcasz w lewo.
def turn_left
self.turn left:
end
ups! To pewnie Not Good ™, na szczęście Ruby podnosi SyntaxError
.
Samochód powinien natychmiast się zatrzymać - prawda?
Nie.
begin
#...
eval self.steering_wheel
#...
rescue Exception => e
self.beep
self.log "Caught #{e}.", :warn
self.log "Logged Error - Continuing Process.", :info
end
sygnał dźwiękowy
Ostrzeżenie: Caught SyntaxError Exception.
Informacje: zarejestrowany błąd - kontynuacja procesu.
Można zauważyć, że coś jest nie tak, i zatrzasnąć na przerwach awaryjnych ( ^C
: Interrupt
)
sygnał dźwiękowy
Ostrzeżenie: wyjątek Caught Interrupt.
Informacje: zarejestrowany błąd - kontynuacja procesu.
Tak - to niewiele pomogło. Jesteś dość blisko szyny, więc ustawiasz samochód na parkingu ( kill
ing:) SignalException
.
sygnał dźwiękowy
Ostrzeżenie: Caught SignalException Exception.
Informacje: zarejestrowany błąd - kontynuacja procesu.
W ostatniej sekundzie wyciągasz klawisze ( kill -9
), a samochód zatrzymuje się, rzucasz się do kierownicy (poduszka powietrzna nie może się napompować, ponieważ nie zatrzymałeś programu z wdziękiem - zakończyłeś go), a komputer z tyłu samochodu wbija się w siedzenie przed nim. Na papier wypełnia się do połowy pełna puszka coli. Artykuły spożywcze z tyłu są kruszone, a większość z nich jest pokryta żółtkiem jaja i mlekiem. Samochód wymaga poważnej naprawy i czyszczenia. (Utrata danych)
Mam nadzieję, że masz ubezpieczenie (kopie zapasowe). O tak - ponieważ poduszka powietrzna się nie napompowała, prawdopodobnie jesteś ranny (wyrzucenie z pracy itp.).
Ale poczekaj! Jestwięcejpowody, dla których możesz chcieć użyć rescue Exception => e
!
Powiedzmy, że jesteś tym samochodem i chcesz się upewnić, że poduszka powietrzna się napompuje, jeśli samochód przekroczy swój bezpieczny moment zatrzymania.
begin
# do driving stuff
rescue Exception => e
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
raise
end
Oto wyjątek od reguły: Możesz złapać Exception
tylko wtedy, gdy podniesiesz wyjątek . Lepszą zasadą jest, aby nigdy nie połykać Exception
i zawsze podnosić błąd.
Ale dodanie ratunku jest łatwe do zapomnienia w języku takim jak Ruby, a umieszczenie oświadczenia ratunkowego tuż przed ponownym podniesieniem problemu wydaje się trochę pozbawione SUSZENIA. I nie chcesz zapomnieć raise
oświadczenia. A jeśli tak, powodzenia w znalezieniu tego błędu.
Na szczęście Ruby jest niesamowity, możesz po prostu użyć ensure
słowa kluczowego, co zapewnia uruchomienie kodu. Słowo ensure
kluczowe uruchomi kod bez względu na wszystko - jeśli zostanie zgłoszony wyjątek, jeśli nie jest, jedynym wyjątkiem jest koniec świata (lub inne mało prawdopodobne zdarzenia).
begin
# do driving stuff
ensure
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
end
Bum! Ten kod i tak powinien działać. Jedynym powodem, dla którego powinieneś użyć rescue Exception => e
jest to, że potrzebujesz dostępu do wyjątku lub jeśli chcesz, aby kod działał tylko na wyjątku. I pamiętaj, aby ponownie podnieść błąd. Każdego razu.
Uwaga: Jak wskazał @Niall, upewnij się, że zawsze działa. Jest to dobre, ponieważ czasami twój program może cię okłamywać i nie rzucać wyjątków, nawet gdy występują problemy. W przypadku krytycznych zadań, takich jak nadmuchiwanie poduszek powietrznych, musisz upewnić się, że tak się stanie, bez względu na wszystko. Z tego powodu dobrym pomysłem jest sprawdzanie za każdym razem, kiedy samochód się zatrzymuje, czy wyjątek jest zgłaszany. Chociaż nadmuchiwanie poduszek powietrznych jest nieco rzadkim zadaniem w większości kontekstów programowania, w rzeczywistości jest to dość powszechne w przypadku większości zadań czyszczenia.
ensure
która jest alternatywą dla, rescue Exception
jest myląca - przykład sugeruje, że są one równoważne, ale jak stwierdzono, ensure
stanie się to bez względu na to, czy istnieje wyjątek, czy nie, więc teraz twoje poduszki powietrzne się napompują, ponieważ przekroczyłeś 5 mil na godzinę, chociaż nic nie poszło źle.
Ponieważ obejmuje to wszystkie wyjątki. Jest mało prawdopodobne, że Twój program może odzyskać od któregokolwiek z nich.
Powinieneś obsługiwać tylko wyjątki, z których wiesz, jak odzyskać. Jeśli nie spodziewasz się pewnego rodzaju wyjątku, nie obsługuj go, głośno upaść (zapisz szczegóły w dzienniku), a następnie zdiagnozuj dzienniki i napraw kod.
Połknięcie wyjątków jest złe, nie rób tego.
Jest to szczególny przypadek reguły, że nie należy wychwytywać żadnych wyjątków, z którymi nie można sobie poradzić. Jeśli nie wiesz, jak sobie z tym poradzić, zawsze lepiej jest pozwolić, aby inna część systemu go złapała i obsługiwała.
Właśnie przeczytałem świetny wpis na blogu na stronie honeybadger.io :
Dlaczego nie powinieneś ratować wyjątku?
Problem z ratowaniem wyjątku polega na tym, że faktycznie ratuje on każdy wyjątek, który dziedziczy po wyjątku. Który to .... wszyscy z nich!
Jest to problem, ponieważ istnieją pewne wyjątki, które są używane wewnętrznie przez Ruby. Nie mają nic wspólnego z Twoją aplikacją, a ich połknięcie spowoduje złe rzeczy.
Oto kilka dużych:
SignalException :: Interrupt - Jeśli to uratujesz, nie możesz wyjść z aplikacji, naciskając Control-C.
ScriptError :: SyntaxError - Połknięcie błędów składniowych oznacza, że rzeczy takie jak puts („Forgot something”) zawiodą po cichu.
NoMemoryError - Chcesz wiedzieć, co się stanie, gdy Twój program będzie działał po zużyciu całej pamięci RAM? Ja też nie.
begin do_something() rescue Exception => e # Don't do this. This will swallow every single exception. Nothing gets past it. end
Zgaduję, że tak naprawdę nie chcesz połknąć żadnego z tych wyjątków na poziomie systemu. Chcesz tylko wyłapać wszystkie błędy na poziomie aplikacji. Wyjątki spowodowały TWÓJ kod.
Na szczęście jest na to prosty sposób.