625 Stimmen

Begin, Rescue und Ensure in Ruby?

Ich habe vor kurzem angefangen, in Ruby zu programmieren und beschäftige mich gerade mit der Behandlung von Ausnahmen.

Ich habe mich gefragt, ob ensure war das Ruby-Äquivalent zu finally in C#? Sollte ich haben:

file = File.open("myFile.txt", "w")

begin
  file << "#{content} \n"
rescue
  #handle the error here
ensure
  file.close unless file.nil?
end

oder sollte ich das tun?

#store the file
file = File.open("myFile.txt", "w")

begin
  file << "#{content} \n"
  file.close
rescue
  #handle the error here
ensure
  file.close unless file.nil?
end

Hat ensure in jedem Fall aufgerufen werden, auch wenn keine Ausnahme ausgelöst wird?

2 Stimmen

Beides ist nicht gut. Wenn Sie mit externen Ressourcen arbeiten, sollten Sie in der Regel immer wollen, dass die Ressource "opening" innerhalb der begin Block.

1323voto

Jörg W Mittag Punkte 349574

Ja, ensure sorgt dafür, dass der Code immer ausgewertet wird. Deshalb heißt es ja auch ensure . Es ist also gleichwertig mit dem von Java und C# finally .

Der allgemeine Fluss der begin / rescue / else / ensure / end sieht so aus:

begin
  # something which might raise an exception
rescue SomeExceptionClass => some_variable
  # code that deals with some exception
rescue SomeOtherException => some_other_variable
  # code that deals with some other exception
else
  # code that runs only if *no* exception was raised
ensure
  # ensure that this code always runs, no matter what
  # does not change the final value of the block
end

Sie können weglassen rescue , ensure ou else . Sie können die Variablen auch weglassen. In diesem Fall können Sie die Ausnahme in Ihrem Code zur Ausnahmebehandlung nicht überprüfen. (Nun, Sie können immer die globale Ausnahmevariable verwenden, um auf die zuletzt ausgelöste Ausnahme zuzugreifen, aber das ist ein bisschen umständlich.) Und Sie können die Ausnahmeklasse weglassen, in diesem Fall werden alle Ausnahmen, die von StandardError gefangen werden. (Bitte beachten Sie, dass dies nicht bedeutet, dass alle Ausnahmen abgefangen werden, weil es Ausnahmen gibt, die Instanzen von Exception aber nicht StandardError . Meistens sehr schwerwiegende Ausnahmen, die die Integrität des Programms gefährden, wie z. B. SystemStackError , NoMemoryError , SecurityError , NotImplementedError , LoadError , SyntaxError , ScriptError , Interrupt , SignalException ou SystemExit .)

Einige Blöcke bilden implizite Ausnahmeblöcke. Zum Beispiel sind Methodendefinitionen implizit auch Ausnahmeblöcke, so dass man statt

def foo
  begin
    # ...
  rescue
    # ...
  end
end

Sie schreiben nur

def foo
  # ...
rescue
  # ...
end

ou

def foo
  # ...
ensure
  # ...
end

Das Gleiche gilt für class Definitionen und module Definitionen.

In dem speziellen Fall, nach dem Sie fragen, gibt es jedoch ein viel besseres Idiom. Wenn Sie mit einer Ressource arbeiten, die Sie am Ende aufräumen müssen, tun Sie das im Allgemeinen, indem Sie einen Block an eine Methode übergeben, die alle Aufräumarbeiten für Sie erledigt. Das ist vergleichbar mit einer using Block in C#, mit dem Unterschied, dass Ruby tatsächlich so leistungsfähig ist, dass man nicht darauf warten muss, dass die Hohepriester von Microsoft vom Berg herunterkommen und gnädigerweise ihren Compiler für einen ändern. In Ruby können Sie es einfach selbst implementieren:

# This is what you want to do:
File.open('myFile.txt', 'w') do |file|
  file.puts content
end

# And this is how you might implement it:
def File.open(filename, mode='r', perm=nil, opt=nil)
  yield filehandle = new(filename, mode, perm, opt)
ensure
  filehandle&.close
end

Und wer hätte das gedacht: Das ist bereits verfügbar in der Kernbibliothek als File.open . Aber es ist ein allgemeines Muster, das Sie auch in Ihrem eigenen Code verwenden können, um jede Art von Ressourcenbereinigung zu implementieren (à la using in C#) oder Transaktionen oder was Ihnen sonst noch einfällt.

Der einzige Fall, in dem dies nicht funktioniert, ist, wenn die Beschaffung und Freigabe der Ressource über verschiedene Teile des Programms verteilt sind. Wenn sie jedoch lokalisiert ist, wie in Ihrem Beispiel, dann können Sie diese Ressourcenblöcke problemlos verwenden.


Übrigens: In modernem C#, using ist eigentlich überflüssig, da Sie Ressourcenblöcke im Stil von Ruby selbst implementieren können:

class File
{
    static T open<T>(string filename, string mode, Func<File, T> block)
    {
        var handle = new File(filename, mode);
        try
        {
            return block(handle);
        }
        finally
        {
            handle.Dispose();
        }
    }
}

// Usage:

File.open("myFile.txt", "w", (file) =>
{
    file.WriteLine(contents);
});

105 Stimmen

Beachten Sie, dass, obwohl die ensure Anweisungen zuletzt ausgeführt werden, sind sie nicht der Rückgabewert.

1 Stimmen

Ich möchte noch hinzufügen, dass Sie auf diese Weise alle Ausnahmetypen anzeigen lassen können: begin ... rescue Exception => e ... puts e.message; puts e.backtrace.inspect ... end .

47 Stimmen

Ich finde es toll, wenn ich solche reichhaltigen Beiträge auf SO sehe. Es geht über das hinaus, was der OP fragte, so dass es für viele weitere Entwickler gilt, ist aber immer noch auf Thema. Ich habe ein paar Dinge aus dieser Antwort und den Änderungen gelernt. Danke, dass Sie nicht nur "Ja" geschrieben haben, ensure wird angerufen, egal was passiert."

50voto

alup Punkte 2811

Zu Ihrer Information: Auch wenn eine Ausnahme in der rescue Abschnitt, der ensure Block ausgeführt, bevor die Codeausführung mit dem nächsten Exception-Handler fortgesetzt wird. Zum Beispiel:

begin
  raise "Error!!"
rescue
  puts "test1"
  raise # Reraise exception
ensure
  puts "Ensure block"
end

15voto

Farrel Punkte 2363

Wenn Sie sicherstellen wollen, dass eine Datei geschlossen ist, sollten Sie die Blockform von File.open :

File.open("myFile.txt", "w") do |file|
  begin
    file << "#{content} \n"
  rescue
  #handle the error here
  end
end

3 Stimmen

Ich schätze, wenn Sie den Fehler nicht behandeln wollen, sondern ihn nur auslösen und das Dateihandle schließen wollen, brauchen Sie das begin rescue hier nicht?

8voto

kuboon Punkte 8796

Deshalb brauchen wir ensure :

def hoge
  begin
    raise
  rescue  
    raise # raise again
  ensure  
    puts 'ensure' # will be executed
  end  
  puts 'end of func' # never be executed
end

7voto

Milan Novota Punkte 15296

Ja, ensure unter allen Umständen aufgerufen wird. Für weitere Informationen siehe " Ausnahmen, Fangen und Werfen " des Buches Programming Ruby und suchen Sie nach "ensure".

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X