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ć?

Author: Andrew Marshall, 2012-04-06

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 Exceptionratujesz 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 evalS, 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
 1261
Author: Andrew Marshall,
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:

  1. być ignorowane (domyślne)
  2. 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:

  1. 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, lub ps | grep ruby, poszukaj PID swojego programu, a następnie uruchom kill -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".
  2. 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:

    %W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
    
    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!
  3. 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.

 76
Author: Michael Slade,
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ę.

Jesteś na moście i zdajesz sobie sprawę, że idziesz trochę w stronę barierki, więc skręcasz w lewo.
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.

 51
Author: Ben Aubin,
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.

 44
Author: Sergio Tulentsev,
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.

 9
Author: Russell Borogove,
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