Ryan Davis ' Ruby QuickRef sagt (ohne Erklärung):
Rettung der Ausnahme nicht. JE. oder ich werde dich erstechen.
Warum nicht? Was ist das Richtige?
Ryan Davis ' Ruby QuickRef sagt (ohne Erklärung):
Rettung der Ausnahme nicht. JE. oder ich werde dich erstechen.
Warum nicht? Was ist das Richtige?
Antworten:
TL; DR : Verwenden Sie StandardError
stattdessen für das allgemeine Abfangen von Ausnahmen. Wenn die ursprüngliche Ausnahme erneut ausgelöst wird (z. B. beim Speichern, um nur die Ausnahme zu protokollieren), Exception
ist das Speichern wahrscheinlich in Ordnung.
Exception
ist die Wurzel der Ausnahme Hierarchie Ruby so, wenn du rescue Exception
dich von Rettung alles , einschließlich der Unterklassen wie SyntaxError
, LoadError
, und Interrupt
.
Die Rettung Interrupt
verhindert, dass der Benutzer CTRLCdas Programm beendet.
Die Rettung SignalException
verhindert, dass das Programm korrekt auf Signale reagiert. Es wird nicht tötbar sein, außer durch kill -9
.
Rettung SyntaxError
bedeutet, dass eval
s, die fehlschlagen, dies stillschweigend tun.
All dies kann angezeigt werden, indem Sie dieses Programm ausführen und versuchen, es CTRLCoder kill
es:
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
Exception
rettet nicht vor , es rettet vor StandardError
. Sie sollten im Allgemeinen etwas Spezifischeres als die Standardeinstellung angeben StandardError
, aber die Rettung aus dem Bereich Exception
erweitert den Bereich, anstatt ihn einzuschränken, und kann katastrophale Folgen haben und die Fehlersuche äußerst schwierig machen.
Wenn Sie eine Situation haben, aus der StandardError
Sie retten möchten, und Sie mit Ausnahme der Ausnahme eine Variable benötigen, können Sie dieses 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 sinnvoll ist, Exception
ist die Protokollierung / Berichterstellung. In diesem Fall sollten Sie die Ausnahme sofort erneut auslösen:
begin
# iceberg?
rescue Exception => e
# do some logging
raise # not enough lifeboats ;)
end
Throwable
in Java zu fangen
ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error, Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]
und dannrescue *ADAPTER_ERRORS => e
Die eigentliche Regel lautet: Werfen Sie keine Ausnahmen weg. Die Objektivität des Autors Ihres Zitats ist fraglich, wie die Tatsache zeigt, dass es mit endet
oder ich werde dich erstechen
Beachten Sie natürlich, dass Signale (standardmäßig) Ausnahmen auslösen und normalerweise lang laufende Prozesse über ein Signal beendet werden. Wenn Sie also Exception abfangen und nicht bei Signalausnahmen beenden, ist Ihr Programm sehr schwer zu stoppen. 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:
thread.abort_on_exception = true
). Dann ist dies in Ihrem Verbindungsbehandlungs-Thread vollkommen 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 Obige funktioniert mit einer Variation von Rubys Standard-Ausnahmebehandlungsroutine, mit dem Vorteil, dass Ihr Programm dadurch nicht ebenfalls beendet wird. Rails tut dies in seinem Request-Handler.
Signalausnahmen werden im Haupt-Thread ausgelöst. Hintergrund-Threads werden sie nicht bekommen, daher macht es keinen Sinn, sie dort zu fangen.
Dies ist besonders nützlich in einer Produktionsumgebung, wo Sie nicht stoppen Ihr Programm wollen einfach , wenn etwas schief geht. Dann können Sie die Stack-Dumps in Ihre Protokolle aufnehmen und zu Ihrem Code hinzufügen, um bestimmte Ausnahmen weiter unten in der Aufrufkette und auf elegantere Weise zu behandeln.
Beachten Sie auch, dass es eine andere Ruby-Sprache gibt, die den gleichen Effekt hat:
a = do_something rescue "something else"
Wenn in dieser Zeile do_something
eine Ausnahme ausgelöst wird, wird sie von Ruby abgefangen, weggeworfen und a
zugewiesen "something else"
.
Tun Sie dies 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 werden, wird eine Ausnahme ausgelöst. Theoretisch sollten Sie den Debugger-Code nicht in Ihrem Programm herumliegen lassen (pff! Niemand macht das!), Aber Sie möchten ihn möglicherweise aus irgendeinem Grund für eine Weile dort lassen, aber Ihren Debugger nicht kontinuierlich ausführen.
Hinweis:
Wenn Sie ein anderes Programm ausgeführt haben, das Signalausnahmen abfängt und ignoriert (sagen Sie den obigen Code), dann:
pgrep ruby
oder ps | grep ruby
suchen Sie sie und führen Sie sie aus kill -9 <PID>
. Wenn Sie mit einem anderen Programm arbeiten, das aus irgendeinem Grund mit diesen Blöcken zum Ignorieren von Ausnahmen gespickt ist, ist es eine mögliche Ausrede, dies an die Spitze der Hauptzeile zu setzen:
%W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
Dies bewirkt, dass das Programm auf die normalen Beendigungssignale reagiert, indem es Ausnahmebehandler ohne Bereinigung sofort beendet und umgeht . Dies kann zu Datenverlust oder Ähnlichem führen. Achtung!
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 critical cleanup
wird jedes Mal aufgerufen, ob eine Ausnahme ausgelöst wird oder nicht.
kill -9
.
ensure
werden unabhängig davon ausgeführt, ob eine Ausnahme ausgelöst wurde oder nicht, während die rescue
Ausführung nur ausgeführt wird, wenn eine Ausnahme ausgelöst wurde.
Nicht rescue Exception => e
(und die Ausnahme nicht erneut auslösen) - oder Sie könnten von einer Brücke fahren.
Angenommen, Sie sitzen in einem Auto (mit Ruby). Sie haben kürzlich ein neues Lenkrad mit dem drahtlosen Upgrade-System (das verwendet eval
) 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 also links ab.
def turn_left
self.turn left:
end
Hoppla! Das ist wahrscheinlich Not Good ™, zum Glück erhöht Ruby a SyntaxError
.
Das Auto sollte sofort anhalten - richtig?
Nee.
begin
#...
eval self.steering_wheel
#...
rescue Exception => e
self.beep
self.log "Caught #{e}.", :warn
self.log "Logged Error - Continuing Process.", :info
end
Piep Piep
Warnung: SyntaxError-Ausnahme abgefangen.
Info: Protokollierter Fehler - Prozess wird fortgesetzt.
Man merkt , etwas falsch ist , und Sie Slam auf die Notbremse ( ^C
: Interrupt
)
Piep Piep
Warnung: Unterbrechungsausnahme abgefangen.
Info: Protokollierter Fehler - Prozess wird fortgesetzt.
Ja - das hat nicht viel geholfen. Du bist ziemlich nah an der Schiene, also stellst du das Auto in den Park ( kill
ing :) SignalException
.
Piep Piep
Warnung: SignalException Exception abgefangen.
Info: Protokollierter Fehler - Prozess wird fortgesetzt.
In der letzten Sekunde ziehen Sie die Schlüssel ( kill -9
) heraus und das Auto hält an, Sie knallen nach vorne in das Lenkrad (der Airbag kann sich nicht aufblasen, weil Sie das Programm nicht ordnungsgemäß gestoppt haben - Sie haben es beendet) und den Computer auf der Rückseite Ihres Autos knallt auf den Sitz davor. Eine halb volle Dose Cola läuft über die Papiere. Die Lebensmittel auf der Rückseite sind zerkleinert und die meisten sind mit Eigelb und Milch bedeckt. Das Auto muss dringend repariert und gereinigt werden. (Datenverlust)
Hoffentlich haben Sie eine Versicherung (Backups). Oh ja - weil sich der Airbag nicht aufgeblasen hat, sind Sie wahrscheinlich verletzt (gefeuert werden usw.).
Aber warte! Es gibtMehrGründe, warum Sie verwenden möchten rescue Exception => e
!
Nehmen wir an, Sie sind dieses Auto und möchten sicherstellen, dass sich der Airbag aufbläst, wenn das Auto seinen sicheren Bremsimpuls ü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 fangen , wenn Sie die Ausnahme erneut auslösen . Eine bessere Regel ist es also, niemals zu schlucken Exception
und den Fehler immer wieder zu erhöhen.
Das Hinzufügen von Rettung ist in einer Sprache wie Ruby leicht zu vergessen, und das Setzen einer Rettungserklärung direkt vor dem erneuten Ansprechen eines Problems fühlt sich ein wenig nicht trocken an. Und Sie wollen die Aussage nicht vergessen raise
. Und wenn Sie dies tun, viel Glück beim Versuch, diesen Fehler zu finden.
Zum Glück ist Ruby fantastisch. Sie können einfach das ensure
Schlüsselwort verwenden, um sicherzustellen, dass der Code ausgeführt wird. Das ensure
Schlüsselwort führt den Code aus, egal was passiert - wenn eine Ausnahme ausgelöst wird, wenn dies nicht der Fall ist, 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, den Sie verwenden sollten, rescue Exception => e
ist, wenn Sie Zugriff auf die Ausnahme benötigen oder wenn nur Code für eine Ausnahme ausgeführt werden soll. Und denken Sie daran, den Fehler erneut auszulösen. Jedes Mal.
Hinweis: Wie @Niall hervorhob, stellen Sie sicher, dass immer ausgeführt wird . 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, bei jedem Anhalten des Autos zu überprüfen, ob eine Ausnahme ausgelöst wird oder nicht. Obwohl das Aufblasen von Airbags in den meisten Programmierkontexten eine ungewöhnliche Aufgabe ist, ist dies bei den meisten Reinigungsaufgaben tatsächlich ziemlich häufig.
ensure
als Alternative zu rescue Exception
ist irreführend - das Beispiel impliziert, dass sie gleichwertig sind, aber wie angegeben ensure
wird es passieren, ob es eine Ausnahme gibt oder nicht. Jetzt werden Ihre Airbags aufgeblasen, weil Sie über 8 km / h gefahren sind, obwohl nichts schief gelaufen ist.
Weil dies alle Ausnahmen erfasst. Es ist unwahrscheinlich, dass sich Ihr Programm von einem dieser Programme erholen kann.
Sie sollten nur Ausnahmen behandeln, von denen Sie wissen, wie Sie sie wiederherstellen können. Wenn Sie eine bestimmte Art von Ausnahme nicht erwarten, behandeln Sie sie nicht, stürzen Sie laut ab (schreiben Sie Details in das Protokoll), diagnostizieren Sie dann Protokolle und korrigieren Sie den Code.
Ausnahmen zu schlucken ist schlecht, tu das nicht.
Dies ist ein spezieller Fall der Regel, dass Sie keine Ausnahme abfangen sollten, 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.
Ich habe gerade einen großartigen Blog-Beitrag darüber auf Honeybadger.io gelesen :
Warum sollten Sie Exception nicht retten?
Das Problem bei der Rettung von Exception besteht darin, dass tatsächlich jede von Resception geerbte Ausnahme gerettet wird. Welches ist ... alle von ihnen!
Dies ist ein Problem, da es einige Ausnahmen gibt, die von Ruby intern verwendet werden. Sie haben nichts mit Ihrer App zu tun, und wenn Sie sie verschlucken, passieren schlimme Dinge.
Hier sind einige der großen:
SignalException :: Interrupt - Wenn Sie dies beheben, können Sie Ihre App nicht beenden, indem Sie Control-c drücken.
ScriptError :: SyntaxError - Das Verschlucken von Syntaxfehlern bedeutet, dass Dinge wie Puts ("Etwas vergessen") stillschweigend fehlschlagen.
NoMemoryError - Möchten Sie wissen, was passiert, wenn Ihr Programm weiter ausgeführt wird, nachdem es den gesamten Arbeitsspeicher verbraucht hat? Ich auch nicht.
begin do_something() rescue Exception => e # Don't do this. This will swallow every single exception. Nothing gets past it. end
Ich vermute, dass Sie keine dieser Ausnahmen auf Systemebene wirklich schlucken möchten. Sie möchten nur alle Fehler auf Anwendungsebene abfangen. Die Ausnahmen verursachten IHREN Code.
Zum Glück gibt es einen einfachen Weg dorthin.