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

Rescue-Klauseln

Eine Rescue-Klausel

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

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

begin
  puts 'Begin.'
rescue
  puts 'Rescued an exception!'
else
  puts 'No exception raised.'
end

Ausgabe

Begin.
No exception raised.

Ensure-Klausel

Die Ensure-Klausel

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

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

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

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.



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

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