435

Recientemente comencé a programar en Ruby, y estoy viendo el manejo de excepciones.¿Comenzar, rescatar y asegurar en Ruby?

Me preguntaba si ensure era el equivalente de Ruby de 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 

¿El ensure ser llamado, no importa lo que, incluso si una excepción no se produce?

Respuesta

971

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

El flujo general de begin/rescue/else/ensure/end se parece a esto:

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 

Usted puede dejar de lado 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 raro.) Y puedes omitir la clase de excepción, en cuyo caso se detectarán todas las excepciones que hereden de StandardError. (Tenga en cuenta que esto no significa que todas las excepciones están atrapadas, porque hay excepciones que son casos de Exception pero no StandardError.excepciones en su mayoría muy graves que ponen en peligro la integridad del programa como SystemStackError, NoMemoryError, SecurityError, NotImplementedError, LoadError, SyntaxError, ScriptError, Interrupt, o SignalExceptionSystemExit.)

Algunos bloques forman bloques excepción implícita. Por ejemplo, las definiciones de método son implícitamente también bloques de excepción, así que en vez de escribir

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

se escriben simplemente

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

o

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

Lo mismo se aplica a class definiciones y module definiciones.

Sin embargo, en el caso específico que usted está preguntando, en realidad hay 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 bloque using en C#, excepto que Ruby es realmente lo suficientemente poderoso como para no tener que esperar a que los sumos sacerdotes de Microsoft bajen de la montaña y cambien graciosamente el compilador. En Rubí, sólo puede implementar por sí 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 unless filehandle.nil? 
end 

Y lo sabes: este es ya disponible en la biblioteca central como File.open. Pero es un patrón general que también puede usar en su propio código, para implementar cualquier tipo de limpieza de recursos (a la using en C#) o transacciones o cualquier otra cosa que pueda pensar.

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


Por cierto: en la moderna C#, using es en realidad superflua, porque se puede aplicar bloques de recursos de estilo de Ruby a sí mismo:

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); 
}); 
+101

+1 por "escribió un capítulo en un libro de Ruby bastante bueno". –

+61

Tenga en cuenta que, aunque las sentencias 'ensure' se ejecutan al final, no son el valor de retorno. – Chris

+0

Desea agregar que puede tener un catch-all para mostrar cada tipo de excepción haciendo esto: 'begin ... rescue Exception => e ... puts e.message; pone e.backtrace.inspect ... end'. – Automatico

6

Sí, se llama ensure en cualquier circunstancia. Para obtener más información, consulte "Exceptions, Catch, and Throw" del libro Programming Ruby y busque "asegurar".

11

Si desea asegurarse de un archivo está cerrado se debe utilizar la forma de bloques de File.open:

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

supongo que si no desea manejar el error, pero sólo levantarlo, y cerrar el identificador de archivo, no lo hace Necesito comenzar el rescate aquí? – rogerdpack

3

Sí, ensure como finallygarantiza que el bloque se ejecutará. Esto es muy útil para garantizar que los recursos críticos estén protegidos, p. cerrando un identificador de archivo en caso de error o liberando un mutex.

3

Sí, ensure ASEGURA que se ejecuta cada vez, por lo que no necesita el file.close en el bloque begin.

Por cierto, una buena manera de probar es hacer:

begin 
    # Raise an error here 
    raise "Error!!" 
rescue 
    #handle the error here 
ensure 
    p "=========inside ensure block" 
end 

puede probar para ver si "========= dentro garantizar bloque" se imprimirán cuando hay es una excepción Luego puede comentar la declaración que genera el error y ver si se ejecuta la instrucción ensure al ver si se imprime algo.

29

su información, incluso si una excepción se volvió a subir en la sección rescue , el bloque ensure 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 
3

Es por esto que necesitamos ensure:

def hoge 
    begin 
    raise 
    rescue 
    raise # raise again 
    ensure 
    puts 'ensure' # will be executed 
    end 
    puts 'end of func' # never be executed 
end 
Cuestiones relacionadas