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 StandardErrorzamiast 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 Exceptionjest prawdopodobnie w porządku.
Exceptionjest korzeniem hierarchii wyjątków Ruby , więc gdy rescue Exceptionratowanie od wszystkiego , w tym podklas, takich jak SyntaxError, LoadErrori Interrupt.
Uratowanie Interruptuniemożliwia użytkownikowi CTRLCzamknięcie programu.
Ratowanie SignalExceptionzapobiega prawidłowej reakcji programu na sygnały. Będzie to niemożliwe do zabicia, chyba że przez kill -9.
Ratowanie SyntaxErroroznacza, evalże porażka zrobi to po cichu.
Wszystkie one mogą być wyświetlane przez ten program działa, i stara się CTRLCbądź killto:
loop do
begin
sleep 1
eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
rescue Exception
puts "I refuse to fail or be stopped!"
end
end
Ratowanie przed Exceptionnie 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ć StandardErrori 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ć, Exceptionjest 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
Throwablew 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_somethingpodniesie wyjątek, zostaje złapany przez Ruby, wyrzucony i aprzypisany "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 rubylub ps | grep rubywyszukaj 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 cleanupbędzie wywoływany za każdym razem, niezależnie od tego, czy zostanie zgłoszony wyjątek.
kill -9.
ensurebędą działać niezależnie od tego, czy wystąpił wyjątek, czy nie, a rescuebędą działać tylko wtedy, gdy wyjątek został zgłoszony.
Nie rób rescue Exception => ewyją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 ( killing:) 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ć Exceptioni 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ć raiseoświadczenia. A jeśli tak, powodzenia w znalezieniu tego błędu.
Na szczęście Ruby jest niesamowity, możesz po prostu użyć ensuresłowa kluczowego, co zapewnia uruchomienie kodu. Słowo ensurekluczowe 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 => ejest 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.
ensurektóra jest alternatywą dla, rescue Exceptionjest myląca - przykład sugeruje, że są one równoważne, ale jak stwierdzono, ensurestanie 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. endZgaduję, ż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.