Dlaczego jest to zły styl ' rescue Exception = > e` w Ruby?
Ryan Davis ' s Ruby QuickRef says (without explanation):
Nie ratujcie WYJĄTKÓW. Nigdy. albo cię dźgnę.
Dlaczego nie? Co należy zrobić?
5 answers
TL; DR: Użyj StandardError
zamiast ogólnego wyłapywania WYJĄTKÓW. Gdy oryginalny wyjątek zostanie ponownie podniesiony (np. podczas ratowania, aby zapisać tylko wyjątek), ratowanie Exception
jest prawdopodobnie w porządku.
Exception
jest korzeniem hierarchii WYJĄTKÓW Ruby, więc kiedy rescue Exception
ratujesz od Wszystko, w tym podklasy takie jak SyntaxError
, LoadError
, i Interrupt
.
Ratowanie Interrupt
uniemożliwia użytkownikowi użycie CTRLC aby wyjść z program.
Ratowanie SignalException
uniemożliwia programowi poprawne reagowanie na sygnały. Będzie nie do zabicia z wyjątkiem kill -9
.
Ratowanie SyntaxError
oznacza, że eval
S, które zawiodą, zrobią to po cichu.
Wszystko to można wyświetlić uruchamiając ten program i próbując CTRLC lub kill
it:
loop do
begin
sleep 1
eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
rescue Exception
puts "I refuse to fail or be stopped!"
end
end
Ratowanie z Exception
nie jest nawet domyślne. Doing
begin
# iceberg!
rescue
# lifeboats
end
Nie ratuje z Exception
, ratuje z StandardError
. Ogólnie należy określ coś bardziej szczegółowego niż domyślne StandardError
, ale Exception
rozszerza zakres, a nie zawęża, i może mieć katastrofalne skutki i sprawić, że polowanie na owady będzie niezwykle trudne.
Jeśli masz sytuację, w której chcesz uratować z StandardError
i potrzebujesz zmiennej z wyjątkiem, możesz użyć tego formularza:
begin
# iceberg!
rescue => e
# lifeboats
end
Co jest równoważne:
begin
# iceberg!
rescue StandardError => e
# lifeboats
end
Jeden z niewielu typowych przypadków, w których ratowanie jest rozsądne Exception
w tym przypadku należy natychmiast ponownie podnieść wyjątek: {]}
begin
# iceberg?
rescue Exception => e
# do some logging
raise e # not enough lifeboats ;)
end
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-26 21:00:47
prawdziwa zasada brzmi: nie wyrzucaj WYJĄTKÓW. Obiektywizm autora cytatu jest wątpliwy, o czym świadczy fakt, że kończy się na
Albo cię dźgnę
Oczywiście, należy pamiętać, że sygnały (domyślnie) rzucają wyjątki, a zwykle długotrwałe procesy są zakończone przez sygnał, więc przechwycenie wyjątków i nie zakończenie na wyjątkach spowoduje, że twój program będzie bardzo trudny do zatrzymania. Więc nie rób tego. to:
#! /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 sprawdzaj, czy działa.
Jednak powiedzmy, że masz serwer z wątkami i chcesz, aby wszystkie wyjątki Nie:
- być ignorowane (domyślne)
- zatrzymaj serwer (co się dzieje, jeśli powiesz
thread.abort_on_exception = true
).
Wtedy jest to całkowicie dopuszczalne w Twoim wątku obsługi połączeń:
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 na odmianę domyślnej obsługi wyjątków Rubiego, z tą zaletą, że nie zabija też Twojego programu. Rails robi to w swojej obsłudze żądań.
Wyjątki sygnałowe są podnoszone w głównym wątku. Wątki w tle ich nie dostaną, więc nie ma sensu próbować ich złapać.
Jest to szczególnie przydatne w środowisku produkcyjnym, gdzie robisz , a nie chcesz, aby twój program po prostu się zatrzymał, gdy coś pójdzie nie tak. Następnie możesz usunąć zrzuty stosu w dziennikach i dodać do kodu, aby poradzić sobie z określonym wyjątkiem dalej łańcuch połączeń i w bardziej wdzięczny sposób.
Zauważ również, że istnieje inny idiom Ruby, który ma taki sam efekt:
a = do_something rescue "something else"
W tej linii, Jeśli do_something
wywoła wyjątek, jest on przechwytywany przez Ruby, odrzucany, a a
jest przypisany "something else"
.
Ogólnie, nie rób tego, z wyjątkiem szczególnych przypadków, w których wiesz nie musisz się martwić. Jeden przykład:
debugger rescue nil
Funkcja debugger
jest dość dobrym sposobem na ustawienie punktu przerwania w kodzie, ale jeśli wychodząc poza debugger i Rails, podnosi wyjątek. Teraz teoretycznie nie powinieneś zostawiać kodu debugującego w swoim programie (pff! nikt tego nie robi!) ale z jakiegoś powodu możesz chcieć zachować go tam przez jakiś czas, ale nie ciągle uruchamiać debuggera.
Uwaga:
-
Jeśli uruchomiłeś czyjś program, który wychwytuje wyjątki sygnałowe i je ignoruje, (powiedzmy kod powyżej), to:
- w Linuksie, w powłoce, wpisz
pgrep ruby
, lubps | grep ruby
, poszukaj PID swojego programu, a następnie uruchomkill -9 <PID>
. - w systemie Windows Użyj Menedżera zadań (CTRL-SHIFT-ESC ), przejdź do zakładki "Procesy", Znajdź swój proces, kliknij go prawym przyciskiem myszy i wybierz "Zakończ proces".
- w Linuksie, w powłoce, wpisz
-
Jeśli pracujesz z cudzym programem, który z jakiegokolwiek powodu jest usiany blokami ignorowania WYJĄTKÓW, umieszczenie tego na górze linii głównej jest możliwe cop-out:
Powoduje to, że program reaguje na normalne sygnały zakończenia poprzez natychmiastowe zakończenie, omijając procedury obsługi wyjątków, bez czyszczenia. Więc może to spowodować utratę danych lub podobne. Ostrożnie!%W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
-
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ływane za każdym razem, niezależnie od tego, czy wyjątek zostanie wyrzucony.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-30 21:27:27
Powiedzmy, że jesteś w samochodzie (bieganie Ruby). Niedawno zainstalowałeś nową kierownicę z systemem over-the-air upgrade (który używa eval
), ale nie wiedziałeś, że jeden z programistów pomylił składnię.
def turn_left
self.turn left:
end
UPS! To prawdopodobnie nie jest dobre ™, 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
Beep beep
Warning: Caught SyntaxError Exception.
Info: Zalogowany Błąd-Kontynuowanie Procesu.
Zauważysz, że coś jest nie tak, i trzaskasz w awaryjnych przerwach (^C
: Interrupt
)
Beep beep
Ostrzeżenie: Przechwycony Wyjątek Przerwania.
Info: Zalogowany Błąd-Kontynuowanie Procesu.
Tak - to nie pomogło much. Jesteś dość blisko torów, więc zaparkuj samochód (kill
ing: SignalException
).
Beep beep
Warning: Caught Signalexception Exception.
Info: Zalogowany Błąd-Kontynuowanie Procesu.
W ostatniej chwili wyciągasz kluczyki (kill -9
), a samochód się zatrzymuje, uderzasz w kierownicę (poduszka powietrzna nie może się napompować, bo nie zatrzymałeś programu - wyłączyłeś go), a komputer w tył twojego samochodu uderza w siedzenie przed nim. Pół pełna puszka coli rozlewa się po papierach. Artykuły spożywcze z tyłu są zmiażdżone, a większość 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 - bo poduszka powietrzna się nie napompowała, pewnie jesteś ranny (wyleciał itp).
Ale czekaj! Jest więcej powodów, dla których warto użyć rescue Exception => e
!
Let ' s powiedz, że jesteś tym samochodem i chcesz się upewnić, że poduszka powietrzna napompuje się, jeśli samochód przekracza 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 jeśli ponownie podniesiesz wyjątek . Więc, lepszą zasadą jest nigdy nie połykać Exception
i zawsze podnosić błąd.
Ale dodanie rescue jest zarówno łatwe do zapomnienia w języku takim jak Ruby, a umieszczenie instrukcji rescue tuż przed ponownym podnoszeniem problemu wydaje się trochę nie suche. A Ty nie chcesz zapomnieć Oświadczenia. A jeśli tak, powodzenia w znalezieniu tego błędu.
Na szczęście Ruby jest świetny, możesz po prostu użyć słowa kluczowego ensure
, co zapewnia, że kod działa. Słowo kluczowe ensure
uruchomi kod bez względu na wszystko - jeśli wyjątek zostanie wyrzucony, jeśli nie, 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
Boom! I ten kod i tak powinien działać. Jedynym powodem, dla którego powinieneś użyć rescue Exception => e
, jest to, że jeśli potrzebujesz dostęp do wyjątku lub jeśli chcesz, aby Kod był uruchamiany tylko na wyjątku. I pamiętaj, aby ponownie podnieść błąd. Za każdym razem.
Uwaga: Jak zauważył @Niall, upewnij się, że zawsze Działa. Jest to dobre, ponieważ czasami twój program może kłamać i nie rzucać WYJĄTKÓW, nawet gdy występują problemy. W przypadku krytycznych zadań, takich jak nadmuchiwanie poduszek powietrznych, musisz upewnić się, że dzieje się to bez względu na wszystko. Z tego powodu sprawdzanie za każdym razem, gdy samochód zatrzymuje się, czy wyjątek jest wyrzucony, czy nie, jest dobry pomysł. Mimo że nadmuchiwanie poduszek powietrznych jest rzadkim zadaniem w większości kontekstów programistycznych, jest to w rzeczywistości dość powszechne w przypadku większości zadań czyszczenia.
TL;DR
Nie rescue Exception => e
(i nie podnoś ponownie wyjątku)-albo Możesz zjechać z mostu.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-03 19:40:37
Ponieważ to rejestruje wszystkie wyjątki. Jest mało prawdopodobne, aby twój program mógł odzyskać z dowolnego z nich.
Powinieneś obsługiwać tylko wyjątki, z których wiesz, jak się odzyskać. Jeśli nie przewidujesz pewnego rodzaju wyjątku, nie obsługuj go, głośno się awarię( napisz szczegóły do dziennika), a następnie zdiagnozuj logi i napraw kod.
Połykanie wyjątków jest złe, nie rób tego.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-06 19:21:19
Jest to szczególny przypadek reguły, której nie powinieneś łapać żadnego wyjątku, z którym nie wiesz, jak sobie radzić. Jeśli nie wiesz, jak sobie z tym poradzić, zawsze lepiej pozwolić, aby inna część systemu go złapała i poradziła sobie z tym.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-06 23:54:05