¿Empezar, rescatar y asegurar en Ruby?
Recientemente comencé a programar en Ruby y estoy analizando el manejo de excepciones.
Me preguntaba si ensure
era el equivalente de Ruby finally
en 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
¿ ensure
Se llama sin importar qué, incluso si no se genera una excepción?
Sí, ensure
garantiza 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 // / / begin
se rescue
ve else
así :ensure
end
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
, ensure
o 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 StandardError
detectará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 Exception
pero no StandardError
. En su mayoría, excepciones muy graves que comprometen la integridad del programa, como SystemStackError
, NoMemoryError
, SecurityError
, NotImplementedError
, LoadError
, SyntaxError
, ScriptError
, Interrupt
o 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 class
las definiciones y module
definiciones.
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 using
bloque 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 using
C#) 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, using
en 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);
});
Para su información, incluso si se vuelve a generar una excepción en la rescue
sección, el ensure
bloque 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