Ausnahmen
Ruby-Code kann Ausnahmen auslösen.
Meistens soll eine ausgelöste Ausnahme das laufende Programm darauf aufmerksam machen, dass eine ungewöhnliche (d. h. *außergewöhnliche*) Situation aufgetreten ist und möglicherweise behandelt werden muss.
Code im Ruby-Kern, in der Ruby-Standardbibliothek und in Ruby-Gems generiert unter bestimmten Umständen Ausnahmen
File.open('nope.txt') # Raises Errno::ENOENT: "No such file or directory"
Ausgelöste Ausnahmen
Eine ausgelöste Ausnahme überträgt die Programmausführung auf die eine oder andere Weise.
Nicht abgefangene Ausnahmen
Wenn eine Ausnahme nicht *abgefangen* wird (siehe Abgefangene Ausnahmen unten), wird die Ausführung an Code im Ruby-Interpreter übertragen, der eine Meldung ausgibt und das Programm (oder den Thread) beendet.
$ ruby -e "raise" -e:1:in '<main>': unhandled exception
Abgefangene Ausnahmen
Ein *Ausnahmebehandler* kann bestimmen, was passieren soll, wenn eine Ausnahme ausgelöst wird; der Behandler kann eine Ausnahme *abfangen* und verhindern, dass das Programm beendet wird.
Ein einfaches Beispiel
begin raise 'Boom!' # Raises an exception, transfers control. puts 'Will not get here.' rescue puts 'Rescued an exception.' # Control transferred to here; program does not exit. end puts 'Got here.'
Ausgabe
Rescued an exception. Got here.
Ein Ausnahmebehandler hat mehrere Elemente
| Element | Verwendung |
|---|---|
| Beginn-Klausel. | Beginnt den Behandler und enthält den Code, dessen ausgelöste Ausnahme, falls vorhanden, abgefangen werden kann. |
| Eine oder mehrere Rescue-Klauseln. | Jede enthält "abfangenden" Code, der für bestimmte Ausnahmen ausgeführt werden soll. |
| Else-Klausel (optional). | Enthält Code, der ausgeführt werden soll, wenn keine Ausnahme ausgelöst wird. |
| Ensure-Klausel (optional). | Enthält Code, der ausgeführt werden soll, unabhängig davon, ob eine Ausnahme ausgelöst oder abgefangen wurde. |
end-Anweisung. |
Beendet den Behandler.‘ |
Beginn-Klausel
Die Beginn-Klausel beginnt den Ausnahmebehandler
-
Kann mit einer
begin-Anweisung beginnen; siehe auch Beginnlose Ausnahmebehandler. -
Enthält Code, dessen ausgelöste Ausnahme (falls vorhanden) vom Behandler abgedeckt ist.
-
Endet mit der ersten folgenden
rescue-Anweisung.
Rescue-Klauseln
Eine Rescue-Klausel
-
Beginnt mit einer
rescue-Anweisung. -
Enthält Code, der für bestimmte ausgelöste Ausnahmen ausgeführt werden soll.
-
Endet mit der ersten folgenden
rescue-,else-,ensure- oderend-Anweisung.
Abgefangene Ausnahmen
Eine rescue-Anweisung kann eine oder mehrere Klassen enthalten, die abgefangen werden sollen; wenn keine angegeben ist, wird StandardError angenommen.
Die Rescue-Klausel fängt sowohl die angegebene Klasse (oder StandardError, wenn keine angegeben ist) als auch eine ihrer Unterklassen ab; siehe Hierarchie der eingebauten Ausnahmeklassen.
begin 1 / 0 # Raises ZeroDivisionError, a subclass of StandardError. rescue puts "Rescued #{$!.class}" end
Ausgabe
Rescued ZeroDivisionError
Wenn die rescue-Anweisung eine Ausnahmeklasse angibt, wird nur diese Klasse (oder eine ihrer Unterklassen) abgefangen; dieses Beispiel wird mit einem ZeroDivisionError beendet, das nicht abgefangen wurde, da es nicht ArgumentError oder eine seiner Unterklassen ist.
begin 1 / 0 rescue ArgumentError puts "Rescued #{$!.class}" end
Eine rescue-Anweisung kann mehrere Klassen angeben, was bedeutet, dass ihr Code eine Ausnahme einer beliebigen der angegebenen Klassen (oder ihrer Unterklassen) abfängt.
begin 1 / 0 rescue FloatDomainError, ZeroDivisionError puts "Rescued #{$!.class}" end
Mehrere Rescue-Klauseln
Ein Ausnahmebehandler kann mehrere Rescue-Klauseln enthalten; in diesem Fall fängt die erste Klausel, die die Ausnahme abfängt, diese ab, und die davor und danach liegenden werden ignoriert.
begin Dir.open('nosuch') rescue Errno::ENOTDIR puts "Rescued #{$!.class}" rescue Errno::ENOENT puts "Rescued #{$!.class}" end
Ausgabe
Rescued Errno::ENOENT
Erfassung der abgefangenen Ausnahme
Eine rescue-Anweisung kann eine Variable angeben, deren Wert die abgefangene Ausnahme wird (eine Instanz von Exception oder eine ihrer Unterklassen).
begin 1 / 0 rescue => x puts x.class puts x.message end
Ausgabe
ZeroDivisionError divided by 0
Globale Variablen
Zwei schreibgeschützte globale Variablen haben immer den Wert nil, außer in einer Rescue-Klausel; sie sind
-
$!: enthält die abgefangene Ausnahme. -
$@: enthält ihren Backtrace.
Beispiel
begin 1 / 0 rescue p $! p $@ end
Ausgabe
#<ZeroDivisionError: divided by 0> ["t.rb:2:in 'Integer#/'", "t.rb:2:in '<main>'"]
Ursache
In einer Rescue-Klausel gibt die Methode Exception#cause den vorherigen Wert von $! zurück, der nil sein kann; anderswo gibt die Methode nil zurück.
Beispiel
begin raise('Boom 0') rescue => x0 puts "Exception: #{x0.inspect}; $!: #{$!.inspect}; cause: #{x0.cause.inspect}." begin raise('Boom 1') rescue => x1 puts "Exception: #{x1.inspect}; $!: #{$!.inspect}; cause: #{x1.cause.inspect}." begin raise('Boom 2') rescue => x2 puts "Exception: #{x2.inspect}; $!: #{$!.inspect}; cause: #{x2.cause.inspect}." end end end
Ausgabe
Exception: #<RuntimeError: Boom 0>; $!: #<RuntimeError: Boom 0>; cause: nil. Exception: #<RuntimeError: Boom 1>; $!: #<RuntimeError: Boom 1>; cause: #<RuntimeError: Boom 0>. Exception: #<RuntimeError: Boom 2>; $!: #<RuntimeError: Boom 2>; cause: #<RuntimeError: Boom 1>.
Else-Klausel
Die else-Klausel
-
Beginnt mit einer
else-Anweisung. -
Enthält Code, der ausgeführt werden soll, wenn keine Ausnahme in der Beginn-Klausel ausgelöst wird.
-
Endet mit der ersten folgenden
ensure- oderend-Anweisung.
begin puts 'Begin.' rescue puts 'Rescued an exception!' else puts 'No exception raised.' end
Ausgabe
Begin. No exception raised.
Ensure-Klausel
Die Ensure-Klausel
-
Beginnt mit einer
ensure-Anweisung. -
Enthält Code, der ausgeführt werden soll, unabhängig davon, ob eine Ausnahme ausgelöst wird und unabhängig davon, ob eine ausgelöste Ausnahme behandelt wird.
-
Endet mit der ersten folgenden
end-Anweisung.
def foo(boom: false) puts 'Begin.' raise 'Boom!' if boom rescue puts 'Rescued an exception!' else puts 'No exception raised.' ensure puts 'Always do this.' end foo(boom: true) foo(boom: false)
Ausgabe
Begin. Rescued an exception! Always do this. Begin. No exception raised. Always do this.
End-Anweisung
Die end-Anweisung beendet den Behandler.
Code, der danach erreicht wird, ist nur dann erreichbar, wenn eine ausgelöste Ausnahme abgefangen wird.
Beginnlose Ausnahmebehandler
Wie oben gezeigt, kann ein Ausnahmebehandler mit begin und end implementiert werden.
Ein Ausnahmebehandler kann auch implementiert werden als
-
Methodenkörper
def foo(boom: false) # Serves as beginning of exception handler. puts 'Begin.' raise 'Boom!' if boom rescue puts 'Rescued an exception!' else puts 'No exception raised.' end # Serves as end of exception handler.
-
Ein Block
Dir.chdir('.') do |dir| # Serves as beginning of exception handler. raise 'Boom!' rescue puts 'Rescued an exception!' end # Serves as end of exception handler.
Ausnahme neu auslösen
Es kann nützlich sein, eine Ausnahme abzufangen, aber ihre endgültige Auswirkung zuzulassen; zum Beispiel kann ein Programm eine Ausnahme abfangen, Daten darüber protokollieren und die Ausnahme dann "wiederherstellen".
Dies kann über die Methode raise geschehen, aber auf besondere Weise; eine abfangende Klausel
-
Erfasst eine Ausnahme.
-
Führt alles Notwendige bezüglich der Ausnahme aus (z. B. Protokollierung).
-
Ruft die Methode
raiseohne Argument auf, was die abgefangene Ausnahme auslöst.
begin 1 / 0 rescue ZeroDivisionError # Do needful things (like logging). raise # Raised exception will be ZeroDivisionError, not RuntimeError. end
Ausgabe
ruby t.rb
t.rb:2:in 'Integer#/': divided by 0 (ZeroDivisionError)
from t.rb:2:in '<main>'
Erneut versuchen
Es kann nützlich sein, eine Begin-Klausel erneut zu versuchen. Wenn sie beispielsweise auf eine möglicherweise volatile Ressource (wie eine Webseite) zugreifen muss, kann es nützlich sein, den Zugriff mehr als einmal zu versuchen (in der Hoffnung, dass sie verfügbar wird).
retries = 0 begin puts "Try ##{retries}." raise 'Boom' rescue puts "Rescued retry ##{retries}." if (retries += 1) < 3 puts 'Retrying' retry else puts 'Giving up.' raise end end
Try #0.
Rescued retry #0.
Retrying
Try #1.
Rescued retry #1.
Retrying
Try #2.
Rescued retry #2.
Giving up.
# RuntimeError ('Boom') raised.
Beachten Sie, dass der Versuch die gesamte Begin-Klausel neu ausführt, nicht nur den Teil nach dem Fehlerpunkt.
Ausnahme auslösen
Die Methode Kernel#raise löst eine Ausnahme aus.
Benutzerdefinierte Ausnahmen
Um zusätzliche oder alternative Informationen bereitzustellen, können Sie benutzerdefinierte Ausnahmeklassen erstellen. Jede sollte eine Unterklasse einer der integrierten Ausnahmeklassen sein (üblicherweise StandardError oder RuntimeError); siehe Hierarchie der eingebauten Ausnahmeklassen.
class MyException < StandardError; end
Nachrichten
Jedes Exception-Objekt hat eine Nachricht, die eine Zeichenkette ist, die beim Erstellen des Objekts gesetzt wird; siehe Exception.new.
Die Nachricht kann nicht geändert werden, aber Sie können ein ähnliches Objekt mit einer anderen Nachricht erstellen; siehe Exception#exception.
Diese Methode gibt die Nachricht wie definiert zurück
Zwei weitere Methoden geben erweiterte Versionen der Nachricht zurück
-
Exception#detailed_message: fügt den Namen der Ausnahmeklasse hinzu, mit optionaler Hervorhebung. -
Exception#full_message: fügt den Namen der Ausnahmeklasse und den Backtrace hinzu, mit optionaler Hervorhebung.
Jede der beiden oben genannten Methoden akzeptiert das Schlüsselwortargument highlight; wenn der Wert des Schlüsselworts highlight true ist, enthält die zurückgegebene Zeichenkette ANSI-Codes (siehe unten) für Fettdruck und Unterstreichung, um das Erscheinungsbild der Nachricht zu verbessern.
Jede Ausnahmeklasse (Ruby oder benutzerdefiniert) kann entweder eine dieser Methoden überschreiben und das Schlüsselwortargument highlight: true so interpretieren, dass die zurückgegebene Nachricht ANSI-Codes enthält, die Farbe, Fettdruck und Unterstreichung angeben.
Da die erweiterte Nachricht auf ein Nicht-Terminal-Gerät (z. B. in eine HTML-Seite) geschrieben werden kann, ist es am besten, die ANSI-Codes auf diese weit verbreiteten Codes zu beschränken.
-
Schriftfarbe beginnen
Farbe ANSI-Code Rot \e[31mGrün \e[32mGelb \e[33mBlau \e[34mMagenta \e[35mCyan \e[36m
-
Schriftattribut beginnen
Attribute ANSI-Code Fett \e[1mUnterstrichen \e[4m
-
Alles oben Beendende
Farbe ANSI-Code Zurücksetzen \e[0m
Es ist auch am besten, eine Nachricht zu erstellen, die bequem lesbar ist, auch wenn die ANSI-Codes "wie sie sind" enthalten sind (anstatt als Schriftrichtlinien interpretiert zu werden).
Backtraces
Ein *Backtrace* ist eine Aufzeichnung der Methoden, die sich derzeit im Aufrufstapel befinden; jede solche Methode wurde aufgerufen, hat aber noch nicht zurückgegeben.
Diese Methoden geben Backtrace-Informationen zurück
-
Exception#backtrace: gibt den Backtrace als Array von Zeichenketten odernilzurück. -
Exception#backtrace_locations: gibt den Backtrace als Array vonThread::Backtrace::Location-Objekten odernilzurück. JedesThread::Backtrace::Location-Objekt liefert detaillierte Informationen zu einer aufgerufenen Methode.
Standardmäßig setzt Ruby den Backtrace der Ausnahme auf den Ort, an dem sie ausgelöst wurde.
Der Entwickler kann dies ändern, indem er entweder das Argument backtrace an Kernel#raise übergibt oder Exception#set_backtrace verwendet.
Beachten Sie, dass
-
standardmäßig stellen sowohl
backtraceals auchbacktrace_locationsdenselben Backtrace dar; -
wenn der Entwickler den Backtrace mit einer der obigen Methoden auf ein Array von
Thread::Backtrace::Locationsetzt, stellen sie immer noch denselben Backtrace dar; -
wenn der Entwickler den Backtrace auf eine Zeichenkette oder ein Array von Zeichenketten setzt
-
mit
Kernel#raise:backtrace_locationswirdnil; -
mit
Exception#set_backtrace:backtrace_locationsbehält den ursprünglichen Wert; -
wenn der Entwickler den Backtrace mit
Exception#set_backtraceaufnilsetzt, behältbacktrace_locationsden ursprünglichen Wert; wenn die Ausnahme dann erneut ausgelöst wird, werden sowohlbacktraceals auchbacktrace_locationszum Ort des erneuten Auslösens.