it-swarm.com.de

Warum ist es schlecht, in Ruby Exception => e zu retten?

Ryan Davis ' Ruby QuickRef sagt (ohne Erklärung):

Rette Exception nicht. JE. oder ich werde dich erstechen.

Warum nicht? Was ist das Richtige?

864
John

TL; DR : Verwenden Sie stattdessen StandardError, um allgemeine Ausnahmen abzufangen. Wenn die ursprüngliche Ausnahme erneut ausgelöst wird (z. B. wenn nur die Ausnahme protokolliert werden soll), ist die Rettung von Exception wahrscheinlich in Ordnung.


Exception ist die Wurzel von Rubys Ausnahmehierarchie , also retten Sie, wenn Sie rescue Exception von allem , einschließlich Unterklassen wie SyntaxError, LoadError und Interrupt.

Die Rettung von Interrupt verhindert die Verwendung durch den Benutzer CTRLC um das Programm zu verlassen.

Die Rettung von SignalException verhindert, dass das Programm korrekt auf Signale reagiert. Es kann nur von kill -9 getötet werden.

Die Rettung von SyntaxError bedeutet, dass evals, die fehlschlagen, dies stillschweigend tun.

All dies kann gezeigt werden, indem Sie dieses Programm ausführen und es versuchen CTRLC oder kill it:

loop do
  begin
    sleep 1
    eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
  rescue Exception
    puts "I refuse to fail or be stopped!"
  end
end

Die Rettung von Exception ist nicht einmal die Standardeinstellung. Tun

begin
  # iceberg!
rescue
  # lifeboats
end

rettet nicht aus Exception, es rettet aus StandardError. Sie sollten im Allgemeinen etwas Spezifischeres als die Standardeinstellung StandardError angeben, aber durch die Rettung von Exception wird der Geltungsbereich erweitert , anstatt ihn einzuschränken. Dies kann katastrophale Folgen haben die Fehlersuche extrem erschweren.


Wenn Sie eine Situation haben, in der Sie aus StandardError retten möchten und eine Variable mit der Ausnahme benötigen, können Sie das folgende Formular verwenden:

begin
  # iceberg!
rescue => e
  # lifeboats
end

was äquivalent ist zu:

begin
  # iceberg!
rescue StandardError => e
  # lifeboats
end

Einer der wenigen Fälle, in denen eine Rettung von Exception sinnvoll ist, ist die Protokollierung/Berichterstellung. In diesem Fall sollten Sie die Ausnahme sofort erneut auslösen:

begin
  # iceberg?
rescue Exception => e
  # do some logging
  raise e  # not enough lifeboats ;)
end
1337
Andrew Marshall

Die echte Regel lautet: Wirf keine Ausnahmen weg. Die Objektivität des Autors Ihres Zitats ist fragwürdig, wie die Tatsache belegt, dass es mit endet

oder ich werde dich erstechen

Beachten Sie natürlich, dass Signale (standardmäßig) Ausnahmen auslösen, und dass normalerweise lange laufende Prozesse durch ein Signal beendet werden. Wenn Sie also die Ausnahmebedingung aktivieren und nicht bei Signalausnahmen beenden, kann Ihr Programm nur schwer gestoppt werden. Also mach das nicht:

#! /usr/bin/Ruby

while true do
  begin
    line = STDIN.gets
    # heavy processing
  rescue Exception => e
    puts "caught exception #{e}! ohnoes!"
  end
end

Nein, wirklich, tu es nicht. Führen Sie das nicht einmal aus, um zu sehen, ob es funktioniert.

Angenommen, Sie haben einen Thread-Server und möchten, dass alle Ausnahmen nicht:

  1. ignoriert werden (die Standardeinstellung)
  2. stoppen Sie den Server (was passiert, wenn Sie thread.abort_on_exception = true sagen).

Dann ist dies in Ihrem Verbindungshandling-Thread durchaus akzeptabel:

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

Das oben Genannte ist eine Variation des Standard-Ausnahmehandlers von Ruby, mit dem Vorteil, dass dadurch auch Ihr Programm nicht beendet wird. Rails erledigt dies in seinem Request-Handler.

Signalausnahmen werden im Hauptthread ausgelöst. Hintergrund-Threads werden sie nicht bekommen, daher ist es sinnlos, sie dort zu fangen.

Dies ist besonders nützlich in einer Produktionsumgebung, in der Sie nicht möchten, dass Ihr Programm einfach gestoppt wird, wenn etwas schief geht. Anschließend können Sie die Stapelspeicherauszüge in Ihre Protokolle aufnehmen und zu Ihrem Code hinzufügen, um bestimmte Ausnahmen weiter in der Aufrufkette und auf eine elegantere Weise zu behandeln.

Beachten Sie auch, dass es eine andere Ruby -Idiom gibt, die in etwa den gleichen Effekt hat:

a = do_something rescue "something else"

Wenn in dieser Zeile do_something eine Ausnahme auslöst, wird sie von Ruby abgefangen, weggeworfen und a wird "something else" zugewiesen.

Tun Sie das im Allgemeinen nicht, außer in besonderen Fällen, in denen Sie wissen , dass Sie sich keine Sorgen machen müssen. Ein Beispiel:

debugger rescue nil

Die debugger -Funktion ist eine gute Möglichkeit, einen Haltepunkt in Ihrem Code festzulegen. Wenn sie jedoch außerhalb eines Debuggers und von Rails ausgeführt wird, wird eine Ausnahme ausgelöst. Jetzt sollten Sie theoretisch keinen Debug-Code in Ihrem Programm herumliegen lassen (pff! Das macht niemand!), Aber Sie möchten ihn vielleicht aus irgendeinem Grund eine Weile dort lassen, aber Ihren Debugger nicht kontinuierlich ausführen.

Hinweis:

  1. Wenn Sie ein anderes Programm ausgeführt haben, das Signalausnahmen abfängt und diese ignoriert (sagen Sie den obigen Code), dann:

    • geben Sie unter Linux in einer Shell pgrep Ruby oder ps | grep Ruby ein, suchen Sie nach der PID Ihres fehlerhaften Programms und führen Sie dann kill -9 <PID> aus.
    • verwenden Sie in Windows den Task-Manager (CTRL-SHIFT-ESC), gehen Sie zur Registerkarte "Prozesse", suchen Sie Ihren Prozess, klicken Sie mit der rechten Maustaste darauf und wählen Sie "Prozess beenden".
  2. Wenn Sie mit einem anderen Programm arbeiten, das aus irgendeinem Grund mit diesen Ignore-Exception-Blöcken übersät ist, ist dies ein möglicher Ausweg:

    %W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
    

    Dadurch reagiert das Programm auf die normalen Beendigungssignale, indem es die Ausnahmebehandlungsroutinen ohne Bereinigung sofort beendet. Dies kann zu Datenverlust oder Ähnlichem führen. Achtung!

  3. Wenn Sie dies tun müssen:

    begin
      do_something
    rescue Exception => e
      critical_cleanup
      raise
    end
    

    sie können dies tatsächlich tun:

    begin
      do_something
    ensure
      critical_cleanup
    end
    

    Im zweiten Fall wird jedes Mal critical cleanup aufgerufen, unabhängig davon, ob eine Ausnahme ausgelöst wurde oder nicht.

79
Michael Slade

Angenommen, Sie sitzen in einem Auto (mit Ruby). Sie haben kürzlich ein neues Lenkrad mit dem drahtlosen Upgrade-System (das eval verwendet) installiert, aber Sie wussten nicht, dass einer der Programmierer die Syntax durcheinander gebracht hat.

Sie befinden sich auf einer Brücke und stellen fest, dass Sie ein Stück in Richtung Geländer fahren. Biegen Sie links ab.

def turn_left
  self.turn left:
end

hoppla! Das ist wahrscheinlich Nicht gut ™, zum Glück wirft Ruby ein SyntaxError.

Das Auto sollte sofort anhalten - oder?

Nee.

begin
  #...
  eval self.steering_wheel
  #...
rescue Exception => e
  self.beep
  self.log "Caught #{e}.", :warn
  self.log "Logged Error - Continuing Process.", :info
end

Piepton Piepton

Warnung: SyntaxError-Ausnahme abgefangen.

Info: Protokollierter Fehler - Fortsetzung des Vorgangs.

Sie bemerken, dass etwas nicht in Ordnung ist, und Sie schlagen auf die Notpausen zu (^C: Interrupt)

Piepton Piepton

Warnung: Interrupt-Ausnahme abgefangen.

Info: Protokollierter Fehler - Fortsetzung des Vorgangs.

Ja - das hat nicht viel geholfen. Du bist ziemlich nah an der Reling, also stellst du das Auto in den Park (killing: SignalException).

Piepton Piepton

Warnung: SignalException-Ausnahme abgefangen.

Info: Protokollierter Fehler - Fortsetzung des Vorgangs.

In der letzten Sekunde ziehen Sie die Schlüssel heraus (kill -9), und das Auto hält an, Sie stoßen ins Lenkrad vor (der Airbag kann sich nicht aufblasen, weil Sie das Programm nicht ordnungsgemäß gestoppt haben - Sie haben es beendet ), und der Computer auf der Rückseite Ihres Autos knallt in den Sitz davor. Eine halbe Dose Cola läuft über die Papiere. Die Lebensmittel im Rücken sind zerdrückt und die meisten mit Eigelb und Milch bedeckt. Das Auto muss dringend repariert und gereinigt werden. (Datenverlust)

Hoffentlich haben Sie eine Versicherung (Backups). Oh ja - weil der Airbag nicht aufgeblasen ist, sind Sie wahrscheinlich verletzt (z. B. gefeuert werden).


Aber warte! Da ist es mehr Gründe, warum Sie vielleicht rescue Exception => e verwenden möchten!

Angenommen, Sie sind das Auto und möchten sicherstellen, dass der Airbag aufgeblasen wird, wenn das Auto seinen sicheren Haltemoment überschreitet.

 begin 
    # do driving stuff
 rescue Exception => e
    self.airbags.inflate if self.exceeding_safe_stopping_momentum?
    raise
 end

Hier ist die Ausnahme von der Regel: Sie können Exception nur abfangen, wenn Sie die Ausnahme erneut auslösen . Daher ist es eine bessere Regel, niemals Exception zu schlucken und den Fehler immer erneut auszulösen.

In einer Sprache wie Ruby ist das Hinzufügen von Rettungsmaßnahmen jedoch leicht zu vergessen, und es fühlt sich ein wenig ungeeignet an, eine Rettungserklärung unmittelbar vor dem erneuten Auslösen eines Problems abzulegen. Und Sie möchten die raise -Anweisung nicht vergessen . Und wenn ja, viel Glück beim Versuch, diesen Fehler zu finden.

Zum Glück ist Ruby fantastisch. Sie können einfach das ensure-Schlüsselwort verwenden, das sicherstellt, dass der Code ausgeführt wird. Das Schlüsselwort ensure führt den Code aus, egal was passiert - wenn eine Ausnahme ausgelöst wird oder nicht, ist die einzige Ausnahme, wenn die Welt endet (oder andere unwahrscheinliche Ereignisse).

 begin 
    # do driving stuff
 ensure
    self.airbags.inflate if self.exceeding_safe_stopping_momentum?
 end

Boom! Und dieser Code sollte sowieso laufen. Der einzige Grund, warum Sie rescue Exception => e verwenden sollten, ist, wenn Sie Zugriff auf die Ausnahme benötigen oder nur Code für eine Ausnahme ausführen möchten. Und denken Sie daran, den Fehler erneut auszulösen. Jedes Mal.

Hinweis: Stellen Sie sicher, dass immer ausgeführt wird, wie @Niall betont hat. Dies ist gut, da Ihr Programm Sie manchmal anlügen und keine Ausnahmen auslösen kann, selbst wenn Probleme auftreten. Bei kritischen Aufgaben, wie dem Aufblasen von Airbags, müssen Sie sicherstellen, dass dies auf jeden Fall geschieht. Aus diesem Grund ist es eine gute Idee, jedes Mal zu überprüfen, wenn das Auto anhält, ob eine Ausnahme ausgelöst wird oder nicht. Auch wenn das Aufblasen von Airbags in den meisten Programmierkontexten etwas ungewöhnlich ist, ist dies bei den meisten Bereinigungsaufgaben durchaus üblich.


TL; DR

Nicht rescue Exception => e (und die Ausnahme nicht erneut auslösen) - oder Sie könnten von einer Brücke fahren.

62
Ben Aubin

Weil dies alle Ausnahmen erfasst. Es ist unwahrscheinlich, dass sich Ihr Programm von jedem von ihnen erholen kann.

Sie sollten nur Ausnahmen behandeln, von denen Sie wissen, wie Sie sich erholen können. Wenn Sie eine bestimmte Art von Ausnahme nicht vorhersehen, gehen Sie nicht damit um, stürzen Sie laut ab (schreiben Sie Details in das Protokoll), diagnostizieren Sie die Protokolle und korrigieren Sie den Code.

Ausnahmen zu schlucken ist schlimm, tu das nicht.

45

Dies ist ein spezieller Fall der Regel, den Sie nicht abfangen sollten jede Ausnahme, mit der Sie nicht umgehen können. Wenn Sie nicht wissen, wie Sie damit umgehen sollen, ist es immer besser, einen anderen Teil des Systems abfangen und damit umgehen zu lassen.

9