¿Empezar, rescatar y asegurar en Ruby?

Resuelto Lloyd Powell asked hace 14 años • 7 respuestas

Recientemente comencé a programar en Ruby y estoy analizando el manejo de excepciones.

Me preguntaba si ensureera el equivalente de Ruby finallyen C#. ¿Debería tener:

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

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

o debería hacer esto?

#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

¿ ensureSe llama sin importar qué, incluso si no se genera una excepción?

Lloyd Powell avatar Feb 03 '10 18:02 Lloyd Powell
Aceptado

Sí, ensuregarantiza que el código siempre se evalúe. Por eso se llama ensure. Por lo tanto, es equivalente a Java y C# finally.

El flujo general de // / / beginse rescueve elseasí :ensureend

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

Puedes omitir rescue, ensureo else. También puede omitir las variables, en cuyo caso no podrá inspeccionar la excepción en su código de manejo de excepciones. (Bueno, siempre puedes usar la variable de excepción global para acceder a la última excepción que se generó, pero eso es un poco complicado). Y puedes omitir la clase de excepción, en cuyo caso se StandardErrordetectarán todas las excepciones que hereden. (Tenga en cuenta que esto no significa que se detecten todas las excepciones, porque hay excepciones que son instancias de Exceptionpero no StandardError. En su mayoría, excepciones muy graves que comprometen la integridad del programa, como SystemStackError, NoMemoryError, SecurityError, NotImplementedError, LoadError, SyntaxError, ScriptError, Interrupto SignalException. SystemExit)

Algunos bloques forman bloques de excepción implícitos. Por ejemplo, las definiciones de métodos son implícitamente también bloques de excepción, por lo que en lugar de escribir

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

tu escribes solo

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

o

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

Lo mismo se aplica a classlas definiciones y moduledefiniciones.

Sin embargo, en el caso específico sobre el que preguntas, en realidad existe un modismo mucho mejor. En general, cuando trabajas con algún recurso que necesitas limpiar al final, lo haces pasando un bloque a un método que hace toda la limpieza por ti. Es similar a un usingbloque en C#, excepto que Ruby es lo suficientemente poderoso como para que no tengas que esperar a que los sumos sacerdotes de Microsoft bajen de la montaña y gentilmente cambien su compilador por ti. En Ruby, puedes implementarlo tú mismo:

# 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

Y qué sabes: esto ya está disponible en la biblioteca principal como File.open. Pero es un patrón general que también puedes usar en tu propio código, para implementar cualquier tipo de limpieza de recursos (al estilo de usingC#) o transacciones o cualquier otra cosa que se te ocurra.

El único caso en el que esto no funciona es si la adquisición y liberación del recurso se distribuyen en diferentes partes del programa. Pero si está localizado, como en su ejemplo, puede utilizar fácilmente estos bloques de recursos.


Por cierto: en C# moderno, usingen realidad es superfluo, porque usted mismo puede implementar bloques de recursos estilo 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);
});
Jörg W Mittag avatar Feb 03 '2010 13:02 Jörg W Mittag

Para su información, incluso si se vuelve a generar una excepción en la rescuesección, el ensurebloque se ejecutará antes de que la ejecución del código continúe con el siguiente controlador de excepciones. Por ejemplo:

begin
  raise "Error!!"
rescue
  puts "test1"
  raise # Reraise exception
ensure
  puts "Ensure block"
end
alup avatar Oct 29 '2012 12:10 alup