web-dev-qa-db-fra.com

Commencer, sauver et assurer en Ruby?

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?

516
Lloyd Powell

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);
});
1133
Jörg W Mittag

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
33
alup

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
13
Farrel

Oui, ensure est appelé en toutes circonstances. Pour plus d'informations, voir " Exceptions, Catch, and Throw " du livre Programming Ruby et recherchez "Ensure".

6
Milan Novota

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é.

4
Aaron Qian

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.

3
Chris McCauley

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  
3
kuboon