J'ai récemment commencé à programmer en Ruby et je m'intéresse au traitement des exceptions.
Je me demandais si ensure
était le Ruby équivalent de finally
en C #? Devrais-je avoir:
file = File.open("myFile.txt", "w")
begin
file << "#{content} \n"
rescue
#handle the error here
ensure
file.close unless file.nil?
end
ou devrais-je faire cela?
#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
Est-ce que ensure
est appelé quoi qu'il arrive, même si une exception n'est pas levée?
Oui, ensure
garantit que le code est toujours évalué. C'est pourquoi on l'appelle ensure
. Donc, il est équivalent à finally
de Java et C #.
Le flux général de begin
/rescue
/else
/ensure
/end
ressemble à ceci:
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
Vous pouvez laisser de côté rescue
, ensure
ou else
. Vous pouvez également omettre les variables, auquel cas vous ne pourrez pas inspecter l'exception dans votre code de traitement des exceptions. (Eh bien, vous pouvez toujours utiliser la variable d'exception globale pour accéder à la dernière exception qui a été levée, mais c'est un peu compliqué.) Et vous pouvez laisser de côté la classe d'exception, auquel cas toutes les exceptions héritées de StandardError
sera attrapé. (Veuillez noter que cela ne signifie pas que les exceptions toutes sont capturées, car il existe des exceptions qui sont des instances de Exception
mais pas StandardError
. Des exceptions généralement très graves qui compromettent l'intégrité du programme tel que SystemStackError
, NoMemoryError
, SecurityError
, NotImplementedError
, LoadError
, SyntaxError
, ScriptError
, Interrupt
, SignalException
ou SystemExit
.)
Certains blocs forment des blocs d'exception implicites. Par exemple, les définitions de méthodes sont implicitement aussi des blocs d’exception, donc au lieu d’écrire
def foo
begin
# ...
rescue
# ...
end
end
vous écrivez juste
def foo
# ...
rescue
# ...
end
ou
def foo
# ...
ensure
# ...
end
Il en va de même pour les définitions class
et les définitions module
.
Cependant, dans le cas précis sur lequel vous vous posez la question, il existe en réalité un bien meilleur idiome. En général, lorsque vous travaillez avec une ressource que vous devez nettoyer à la fin, vous le faites en transmettant un bloc à une méthode qui effectue tout le nettoyage à votre place. Cela ressemble à un bloc using
en C #, sauf que Ruby est en fait assez puissant pour que vous n'ayez pas à attendre que les grands prêtres de Microsoft descendent de la montagne et changent gracieusement leur compilateur pour vous. En Ruby, vous pouvez simplement l'implémenter vous-même:
# 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
Et que savez-vous: ceci est déjà disponible dans la bibliothèque principale en tant que File.open
. Mais c'est un schéma général que vous pouvez également utiliser dans votre propre code, pour implémenter tout type de nettoyage des ressources (à la using
en C #), des transactions ou autre chose que vous pouvez penser.
Le seul cas où cela ne fonctionne pas, si l'acquisition et la libération de la ressource sont réparties sur différentes parties du programme. Mais s'il est localisé, comme dans votre exemple, vous pouvez facilement utiliser ces blocs de ressources.
BTW: en C # moderne, using
est en fait superflu, car vous pouvez implémenter vous-même des blocs de ressources de style Ruby:
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);
});
Pour votre information, même si une exception est à nouveau déclenchée dans la section rescue
, le bloc ensure
sera exécuté avant que l’exécution du code ne se poursuive avec le prochain gestionnaire d’exceptions. Par exemple:
begin
raise "Error!!"
rescue
puts "test1"
raise # Reraise exception
ensure
puts "Ensure block"
end
Si vous voulez vous assurer qu'un fichier est fermé, vous devez utiliser la forme de bloc de File.open
:
File.open("myFile.txt", "w") do |file|
begin
file << "#{content} \n"
rescue
#handle the error here
end
end
Oui, ensure
est appelé en toutes circonstances. Pour plus d'informations, voir " Exceptions, Catch, and Throw " du livre Programming Ruby et recherchez "Ensure".
Oui, ensure
ASSURE qu'il est exécuté à chaque fois. Vous n'avez donc pas besoin du file.close
dans le bloc begin
.
À propos, un bon moyen de tester est de faire:
begin
# Raise an error here
raise "Error!!"
rescue
#handle the error here
ensure
p "=========inside ensure block"
end
Vous pouvez tester pour voir si "========= à l'intérieur du bloc" sera imprimé en cas d'exception. Ensuite, vous pouvez commenter l'instruction qui génère l'erreur et voir si l'instruction ensure
est exécutée en vérifiant si quelque chose est imprimé.
Oui, ensure
comme finally
garantit que le bloc sera exécuté. Ceci est très utile pour s’assurer que les ressources critiques sont protégées, par exemple. fermer un handle de fichier en cas d'erreur, ou relâcher un mutex.
C'est pourquoi nous avons besoin de ensure
:
def hoge
begin
raise
rescue
raise # raise again
ensure
puts 'ensure' # will be executed
end
puts 'end of func' # never be executed
end