Frage Warum ist es ein schlechter Stil, Exception => e` in Ruby zu retten?


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

Keine Ausnahme retten. JE. oder ich werde dich erstechen.

Warum nicht? Was ist das Richtige?


800
2018-04-06 19:17


Ursprung


Antworten:


TL; DR: Benutzen StandardError stattdessen für den allgemeinen Ausnahmefang. Wenn die ursprüngliche Ausnahme erneut ausgelöst wird (z. B. wenn gerettet wird, um nur die Ausnahme zu protokollieren), wird gerettet Exception ist wahrscheinlich in Ordnung.


Exception ist die Wurzel von Rubys Exception-HierarchieAlso wenn du rescue Exception Sie retten von alleseinschließlich Unterklassen wie SyntaxError, LoadError, und Interrupt.

Rettung Interrupt verhindert die Verwendung des Benutzers STRGC um das Programm zu beenden.

Rettung SignalException verhindert, dass das Programm korrekt auf Signale reagiert. Es wird unkalkulierbar sein außer durch kill -9.

Rettung SyntaxError bedeutet, dass evals, die scheitern, werden das still tun.

All diese können angezeigt werden, indem Sie dieses Programm ausführen und versuchen STRGC oder kill es:

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

Rettung von Exception ist nicht einmal der Standard. Tun

begin
  # iceberg!
rescue
  # lifeboats
end

rettet nicht von Exception, es rettet aus StandardError. Sie sollten im Allgemeinen etwas Spezifischeres als den Standard angeben StandardError, aber retten von Exception  erweitert sich der Umfang, anstatt ihn einzuschränken, kann katastrophale Folgen haben und die Fehlersuche extrem erschweren.


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

begin
  # iceberg!
rescue => e
  # lifeboats
end

was entspricht:

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

Einer der wenigen häufigen Fälle, in denen es sinnvoll ist, zu retten Exception dient zur 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

1240
2018-04-06 19:38



Das echt Regel ist: Wirf 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

Seien Sie sich natürlich bewusst, dass Signale (standardmäßig) Ausnahmen auslösen, und normalerweise lang andauernde Prozesse durch ein Signal beendet werden, so dass das Abfangen von Exception und nicht das Beenden von Signalausnahmen Ihr Programm sehr schwer zum Stoppen bringt. Also tu 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ühre das nicht einmal durch, um zu sehen, ob es funktioniert.

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

  1. ignoriert werden (Standard)
  2. Stoppen Sie den Server (was passiert, wenn Sie sagen thread.abort_on_exception = true).

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 führt zu einer Variation von Rubys Standard-Exception-Handler, mit dem Vorteil, dass es auch nicht Ihr Programm zerstört. Rails erledigt dies in seinem Request-Handler.

Signalausnahmen werden im Hauptthread ausgelöst. Hintergrundthreads werden sie nicht bekommen, also hat es keinen Sinn, sie dort zu fangen.

Dies ist besonders nützlich in einer Produktionsumgebung, wo Sie dies tun nicht möchte, dass Ihr Programm einfach stoppt, wenn etwas schief geht. Dann können Sie die Stack-Dumps in Ihren Protokollen übernehmen und Ihrem Code hinzufügen, um mit bestimmten Ausnahmen weiter unten in der Aufrufkette und auf eine elegantere Weise umzugehen.

Beachten Sie auch, dass es ein anderes Ruby-Idiom gibt, das den gleichen Effekt hat:

a = do_something rescue "something else"

In dieser Zeile, wenn do_something löst eine Ausnahme aus, wird von Ruby gefangen, weggeworfen, und a ist zugewiesen "something else".

Im Allgemeinen tun Sie das nicht, außer in besonderen Fällen, wo Sie kennt Sie müssen sich keine Sorgen machen. Ein Beispiel:

debugger rescue nil

Das debugger Funktion ist eine ziemlich nette Möglichkeit, einen Haltepunkt in Ihrem Code festzulegen, aber wenn außerhalb eines Debuggers und Rails ausgeführt wird, löst es eine Ausnahme aus. Nun sollten Sie theoretisch nicht den Debug-Code in Ihrem Programm liegen lassen (pff! Niemand macht das!), Aber Sie sollten ihn aus irgendeinem Grund für eine Weile behalten, aber nicht ständig Ihren Debugger laufen lassen.

Hinweis:

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

    • In Linux, in einer Shell, tippe pgrep ruby, oder ps | grep ruby, suchen Sie nach der PID Ihres problematischen Programms, und führen Sie sie aus kill -9 <PID>.
    • Verwenden Sie in Windows den Task-Manager (STRG-VERSCHIEBUNG-ESC), gehen Sie zur Registerkarte "Prozesse", finden Sie Ihren Prozess, klicken Sie mit der rechten Maustaste darauf und wählen Sie "Prozess beenden".
  2. Wenn Sie mit dem Programm eines anderen arbeiten, das aus irgendeinem Grund mit diesen Ignorier-Ausnahme-Blöcken gespickt ist, dann ist dies ein möglicher Ausweg:

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

    Dies bewirkt, dass das Programm auf die normalen Beendigungssignale reagiert, indem es unterbindet und Exception-Handler umgeht. ohne Aufräumen. Dies könnte 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 critical cleanup wird jedes Mal aufgerufen, ob eine Ausnahme ausgelöst wird oder nicht.


73
2018-04-07 05:30



Nehmen wir an, Sie sind in einem Auto (Ruby läuft). Sie haben kürzlich ein neues Lenkrad mit dem Over-the-Air-Upgrade-System installiert (welches eval), aber Sie wussten nicht, dass einer der Programmierer die Syntax durcheinander brachte.

Sie sind auf einer Brücke und erkennen, dass Sie ein wenig Richtung Geländer gehen, also biegen Sie links ab.

def turn_left
  self.turn left:
end

Hoppla! Das ist wahrscheinlich Nicht gutZum Glück wirft Ruby ein 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 - Fortlaufender Prozess.

Sie bemerken, dass etwas nicht stimmt, und Sie knallen die Notpausen (^C: Interrupt)

Piep Piep

Warnung: Interrupt-Ausnahme abgefangen.

Info: Protokollierter Fehler - Fortlaufender Prozess.

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

Piep Piep

Warnung: SignalException-Ausnahme abgefangen.

Info: Protokollierter Fehler - Fortlaufender Prozess.

In der letzten Sekunde ziehen Sie die Schlüssel heraus (kill -9), und das Auto stoppt, Sie knallen vorwärts ins Lenkrad (der Airbag kann nicht aufblasen, weil Sie das Programm nicht anmutig stoppten - Sie beendeten es), und der Computer in der Rückseite Ihres Autos knallt in den Sitz hinein davor. Eine halbvolle 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 braucht eine ernsthafte Reparatur und Reinigung. (Datenverlust)

Hoffentlich haben Sie eine Versicherung (Backups). Oh ja - weil der Airbag nicht aufgeblasen hat, sind Sie wahrscheinlich verletzt (gefeuert, etc).


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

Nehmen wir an, Sie sind das Auto, und Sie möchten sicherstellen, dass der Airbag aufgeblasen wird, wenn das Auto mehr als 5 Stundenmeilen vor dem Anhalten geht.

 begin 
    # do driving stuff
 rescue Exception => e
    self.airbags.inflate if self.speed >= 5.mph 
    raise
 end

Hier ist die Ausnahme von der Regel: Sie können fangen Exception  nur wenn Sie die Ausnahme erneut auslösen. Also, eine bessere Regel ist, niemals zu schlucken Exceptionund den Fehler immer erneut auslösen.

Aber das Hinzufügen von Rescue ist in einer Sprache wie Ruby leicht zu vergessen, und eine Rescue-Anweisung direkt vor dem erneuten Anhängen eines Problems zu platzieren fühlt sich ein bisschen nicht-DRY an. Und Sie unterlassen Sie will das vergessen raise Erklärung. Und wenn Sie das tun, versuchen Sie, diesen Fehler zu finden.

Glücklicherweise ist Ruby super, du kannst einfach die ensure Schlüsselwort, das sicherstellt, dass der Code ausgeführt wird. Das ensure keyword führt den Code aus, egal was passiert - wenn eine Ausnahme ausgelöst wird, falls nicht, die einzige Ausnahme ist, wenn die Welt endet (oder andere unwahrscheinliche Ereignisse).

 begin 
    # do driving stuff
 ensure
    self.airbags.inflate if self.speed >= 5.mph 
 end

Boom! Und dieser Code sollte sowieso laufen. Der einzige Grund, den Sie verwenden sollten rescue Exception => e Dies ist der Fall, wenn Sie auf die Ausnahme zugreifen müssen oder wenn der Code nur für eine Ausnahme ausgeführt werden soll. Und denken Sie daran, den Fehler erneut zu beheben. Jedes Mal. Oder du wirst 3 Leute haben, die dich erstechen (einschließlich deines Chefs).


TL; DR

Nicht rescue Exception => e (und nicht die Ausnahme erneut auslösen) - oder du Macht fahre eine Brücke ab.


46
2018-01-31 23:55



Weil dies alle Ausnahmen erfasst. Es ist unwahrscheinlich, dass Ihr Programm wiederhergestellt werden kann irgendein von ihnen.

Sie sollten nur mit Ausnahmen umgehen, 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 lautstark ab (schreiben Sie Details in das Protokoll), diagnostizieren Sie dann Protokolle und korrigieren Sie den Code.

Verschlucken von Ausnahmen ist schlecht, tue das nicht.


43
2018-04-06 19:21



Das ist ein spezieller Fall der Regel, die Sie nicht fangen sollten irgendein Ausnahme, Sie wissen nicht, wie Sie damit umgehen sollen. Wenn Sie nicht wissen, wie Sie damit umgehen sollen, ist es immer besser, wenn Sie sich von einem anderen Teil des Systems einfangen lassen.


8
2018-04-06 23:54



Dadurch werden auch Fehler von Ihnen ausgeblendet, zum Beispiel wenn Sie einen Methodennamen falsch eingegeben haben:

def my_fun
  "my_fun"
end

begin
 # you mistypped my_fun to my_func
 my_func # my_func()
rescue Exception
  # rescued NameError (or NoMethodError if you called method with parenthesis)
end

1
2018-03-25 09:11