624 votes

Commencer, sauver et assurer en Ruby ?

J'ai récemment commencé à programmer en Ruby, et je m'intéresse à la gestion des exceptions.

Je me demandais si ensure était l'équivalent en Ruby de finally en C# ? Je devrais :

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

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

ou dois-je le faire ?

#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

Fait ensure est appelé quoi qu'il arrive, même si une exception n'est pas levée ?

2 votes

Ni l'un ni l'autre n'est bon. En règle générale, lorsqu'il s'agit de ressources externes, il faut siempre vous voulez que l'ouverture de la ressource soit à l'intérieur du begin bloc.

1321voto

Jörg W Mittag Points 153275

Oui, ensure garantit que le code est toujours évalué. C'est pourquoi on l'appelle ensure . Il est donc équivalent à celui de Java et de C#. finally .

Le flux général de begin / rescue / else / ensure / end ressemble à ça :

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 o else . Vous pouvez également omettre les variables, auquel cas vous ne serez pas en mesure d'inspecter l'exception dans votre code de gestion des exceptions. (Vous pouvez toujours utiliser la variable d'exception globale pour accéder à la dernière exception levée, mais c'est un peu compliqué). Vous pouvez également omettre la classe d'exception, auquel cas toutes les exceptions qui héritent de la classe StandardError sera attrapé. (Veuillez noter que cela ne signifie pas que tous les exceptions sont capturées, car il existe des exceptions qui sont des instances de Exception pero no StandardError . La plupart des exceptions très graves qui compromettent l'intégrité du programme, telles que SystemStackError , NoMemoryError , SecurityError , NotImplementedError , LoadError , SyntaxError , ScriptError , Interrupt , SignalException o 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 class définitions et module définitions.

Toutefois, dans le cas précis que vous demandez, il existe en fait une expression bien meilleure. En général, lorsque vous travaillez avec une ressource que vous devez nettoyer à la fin, vous le faites en passant un bloc à une méthode qui fait tout le nettoyage pour vous. C'est similaire à un using en C#, sauf que Ruby est en fait suffisamment 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 tout 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 qu'est-ce que vous savez : c'est déjà disponible dans la bibliothèque centrale en tant que File.open . Mais il s'agit d'un modèle général que vous pouvez utiliser dans votre propre code également, pour mettre en œuvre tout type de nettoyage de ressources (à la using en C#) ou des transactions ou tout autre élément auquel vous pourriez penser.

Le seul cas où cela ne fonctionne pas est celui où l'acquisition et la libération de la ressource sont réparties sur différentes parties du programme. Mais si elle est localisée, comme dans votre exemple, alors vous pouvez facilement utiliser ces blocs de ressources.


BTW : en C# moderne, using est en fait superflue, 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);
});

104 votes

Notez que, bien que le ensure sont exécutées en dernier, elles ne constituent pas la valeur de retour.

1 votes

Je tiens à ajouter que vous pouvez avoir un fourre-tout pour afficher tous les types d'exception en faisant cela : begin ... rescue Exception => e ... puts e.message; puts e.backtrace.inspect ... end .

47 votes

J'aime voir des contributions riches comme celle-ci sur SO. Elle va au-delà de ce que le PO a demandé, de sorte qu'elle s'applique à beaucoup plus de développeurs, tout en restant dans le sujet. J'ai appris plusieurs choses grâce à cette réponse et aux modifications apportées. Merci de ne pas vous contenter d'écrire "Oui, ensure est appelé quoi qu'il arrive."

49voto

alup Points 671

Pour votre information, même si une exception est relancée dans les rescue la section ensure sera exécuté avant que l'exécution du code ne passe au gestionnaire d'exception suivant. Par exemple :

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

15voto

Farrel Points 1955

Si vous voulez vous assurer qu'un fichier est fermé, vous devez utiliser la forme bloc de l'option File.open :

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

3 votes

Je suppose que si vous ne voulez pas gérer l'erreur, mais simplement la soulever et fermer le gestionnaire de fichier, vous n'avez pas besoin du sauvetage de début ici ?

7voto

Milan Novota Points 10892

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

5voto

Chris McCauley Points 9764

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 en fermant un handle de fichier en cas d'erreur, ou en libérant un mutex.

1 votes

Sauf que dans son cas, il n'y a aucune garantie que le dossier soit fermé, car File.open ne se trouve PAS à l'intérieur du bloc begin-ensure. Seul file.close mais ce n'est pas suffisant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X