Ruby QuickRef de Ryan Davis dice (sin explicación):
No rescates la excepción. SIEMPRE. o te apuñalaré.
Por qué no? ¿Qué es lo que hay que hacer?
Ruby QuickRef de Ryan Davis dice (sin explicación):
No rescates la excepción. SIEMPRE. o te apuñalaré.
Por qué no? ¿Qué es lo que hay que hacer?
Respuestas:
TL; DR : utilice en su StandardError
lugar para la captura de excepciones generales. Cuando se vuelve a generar la excepción original (por ejemplo, cuando se rescata para registrar solo la excepción), el rescate Exception
probablemente esté bien.
Exception
es la raíz de la jerarquía de excepciones de Ruby , por lo que al rescue Exception
rescatar de todo , incluyendo las subclases tales como SyntaxError
, LoadError
, y Interrupt
.
El rescate Interrupt
evita que el usuario use CTRLCpara salir del programa.
El rescate SignalException
evita que el programa responda correctamente a las señales. Será invencible excepto por kill -9
.
Rescatar SyntaxError
significa que eval
los que fallan lo harán en silencio.
Todo esto se puede mostrar ejecutando este programa e intentando CTRLCo kill
:
loop do
begin
sleep 1
eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
rescue Exception
puts "I refuse to fail or be stopped!"
end
end
Rescatar de Exception
ni siquiera es el valor predeterminado. Haciendo
begin
# iceberg!
rescue
# lifeboats
end
no rescata Exception
, rescata de StandardError
. En general, debe especificar algo más específico que el predeterminado StandardError
, pero rescatarlo Exception
amplía el alcance en lugar de reducirlo, y puede tener resultados catastróficos y hacer que la búsqueda de errores sea extremadamente difícil.
Si tiene una situación en la que desea rescatar StandardError
y necesita una variable con la excepción, puede usar este formulario:
begin
# iceberg!
rescue => e
# lifeboats
end
que es equivalente a:
begin
# iceberg!
rescue StandardError => e
# lifeboats
end
Uno de los pocos casos comunes en los que es sensato rescatar Exception
es para fines de registro / informes, en cuyo caso debe volver a plantear la excepción de inmediato:
begin
# iceberg?
rescue Exception => e
# do some logging
raise # not enough lifeboats ;)
end
Throwable
en Java
ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error, Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]
y luegorescue *ADAPTER_ERRORS => e
La verdadera regla es: no descarte las excepciones. La objetividad del autor de su cita es cuestionable, como lo demuestra el hecho de que termina con
o te apuñalaré
Por supuesto, tenga en cuenta que las señales (por defecto) arrojan excepciones, y normalmente los procesos de ejecución prolongada se terminan a través de una señal, por lo que capturar la excepción y no terminar en las excepciones de la señal hará que su programa sea muy difícil de detener. Entonces no hagas esto:
#! /usr/bin/ruby
while true do
begin
line = STDIN.gets
# heavy processing
rescue Exception => e
puts "caught exception #{e}! ohnoes!"
end
end
No, en serio, no lo hagas. Ni siquiera ejecutes eso para ver si funciona.
Sin embargo, supongamos que tiene un servidor con subprocesos y desea que todas las excepciones no:
thread.abort_on_exception = true
). Entonces esto es perfectamente aceptable en su hilo de manejo de conexión:
begin
# do stuff
rescue Exception => e
myLogger.error("uncaught #{e} exception while handling connection: #{e.message}")
myLogger.error("Stack trace: #{backtrace.map {|l| " #{l}\n"}.join}")
end
Lo anterior funciona con una variación del controlador de excepciones predeterminado de Ruby, con la ventaja de que tampoco mata su programa. Rails hace esto en su manejador de solicitudes.
Las excepciones de señal se generan en el hilo principal. Los hilos de fondo no los conseguirán, por lo que no tiene sentido tratar de atraparlos allí.
Esto es particularmente útil en un entorno de producción, donde no desea que su programa simplemente se detenga cuando algo sale mal. Luego puede tomar los volcados de la pila en sus registros y agregarlos a su código para lidiar con excepciones específicas más adelante en la cadena de llamadas y de una manera más elegante.
Tenga en cuenta también que hay otra expresión de Ruby que tiene el mismo efecto:
a = do_something rescue "something else"
En esta línea, si do_something
se produce una excepción, Ruby lo atrapa, lo tira y a
lo asigna "something else"
.
En general, no haga eso, excepto en casos especiales en los que sabe que no necesita preocuparse. Un ejemplo:
debugger rescue nil
La debugger
función es una forma bastante agradable de establecer un punto de interrupción en su código, pero si se ejecuta fuera de un depurador y Rails, genera una excepción. Ahora, en teoría, no deberías dejar el código de depuración en tu programa (¡pff! ¡Nadie hace eso!), Pero es posible que quieras mantenerlo allí por un tiempo por alguna razón, pero no ejecutes continuamente tu depurador.
Nota:
Si ha ejecutado el programa de otra persona que detecta excepciones de señales y las ignora (diga el código anterior), entonces:
pgrep ruby
o ps | grep ruby
busque el PID de su programa infractor y luego ejecútelo kill -9 <PID>
. Si está trabajando con el programa de otra persona que, por cualquier motivo, está salpicado de estos bloques de ignorar excepciones, poner esto en la parte superior de la línea principal es una posible solución:
%W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
Esto hace que el programa responda a las señales de terminación normales al terminar inmediatamente, sin pasar por los controladores de excepciones, sin limpieza . Por lo tanto, podría causar pérdida de datos o similar. ¡Ten cuidado!
Si necesitas hacer esto:
begin
do_something
rescue Exception => e
critical_cleanup
raise
end
en realidad puedes hacer esto:
begin
do_something
ensure
critical_cleanup
end
En el segundo caso, critical cleanup
se llamará cada vez, se produzca o no una excepción.
kill -9
.
ensure
se ejecutará independientemente de si se produjo una excepción o no, mientras rescue
que solo se ejecutará si se generó una excepción.
No lo hagas rescue Exception => e
(y no vuelvas a plantear la excepción), o podrías salirte de un puente.
Digamos que estás en un auto (corriendo Ruby). Recientemente instaló un nuevo volante con el sistema de actualización por aire (que utiliza eval
), pero no sabía que uno de los programadores había estropeado la sintaxis.
Estás en un puente y te das cuenta de que vas un poco hacia la barandilla, así que giras a la izquierda.
def turn_left
self.turn left:
end
¡Uy! Probablemente no sea Good ™, afortunadamente, Ruby plantea a SyntaxError
.
El auto debe detenerse de inmediato, ¿verdad?
No
begin
#...
eval self.steering_wheel
#...
rescue Exception => e
self.beep
self.log "Caught #{e}.", :warn
self.log "Logged Error - Continuing Process.", :info
end
bip bip
Advertencia: Excepción de error de sintaxis capturada.
Información: Error registrado - Proceso continuo.
Se nota que algo está mal, y que pisar el frenos de emergencia ( ^C
: Interrupt
)
bip bip
Advertencia: Excepción de interrupción atrapada.
Información: Error registrado - Proceso continuo.
Sí, eso no ayudó mucho. Estás bastante cerca del riel, por lo que pones el auto en estacionamiento ( kill
ing:) SignalException
.
bip bip
Advertencia: Excepción de excepción de señal atrapada.
Información: Error registrado - Proceso continuo.
En el último segundo, sacas las llaves (kill -9
), y el automóvil se detiene, se estrella contra el volante (el airbag no puede inflarse porque no detuvo el programa con gracia, lo cerró) y la computadora en la parte de atrás de su automóvil se estrella contra el asiento frente a él. Una lata medio llena de Coca-Cola se derrama sobre los papeles. Los comestibles en la parte posterior están triturados, y la mayoría están cubiertos de yema de huevo y leche. El auto necesita reparaciones y limpieza serias. (Pérdida de datos)
Esperemos que tenga seguro (copias de seguridad). Ah, sí, debido a que el airbag no se infló, probablemente estés herido (despedido, etc.).
¡Pero espera! Haymásrazones por las que es posible que desee usar rescue Exception => e
!
Digamos que usted es ese automóvil y desea asegurarse de que el airbag se infla si el automóvil excede su impulso de frenado seguro.
begin
# do driving stuff
rescue Exception => e
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
raise
end
Aquí está la excepción a la regla: puede atrapar Exception
solo si vuelve a aumentar la excepción . Entonces, una mejor regla es nunca tragar Exception
, y siempre volver a plantear el error.
Pero agregar rescate es fácil de olvidar en un lenguaje como Ruby, y poner una declaración de rescate justo antes de volver a plantear un problema se siente un poco no SECO. Y no quieres olvidar la raise
declaración. Y si lo haces, buena suerte tratando de encontrar ese error.
Afortunadamente, Ruby es increíble, solo puedes usar la ensure
palabra clave, lo que asegura que el código se ejecute. La ensure
palabra clave ejecutará el código pase lo que pase: si se lanza una excepción, si no es así, la única excepción es si el mundo termina (u otros eventos poco probables).
begin
# do driving stuff
ensure
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
end
¡Auge! Y ese código debería ejecutarse de todos modos. La única razón por la que debe usar rescue Exception => e
es si necesita acceso a la excepción, o si solo desea que el código se ejecute en una excepción. Y recuerde volver a plantear el error. Cada vez.
Nota: Como señaló @Niall, asegúrese de que siempre se ejecute. Esto es bueno porque a veces su programa puede mentirle y no lanzar excepciones, incluso cuando ocurren problemas. Con tareas críticas, como inflar bolsas de aire, debe asegurarse de que suceda sin importar lo que pase. Debido a esto, es una buena idea verificar cada vez que el automóvil se detiene, si se produce una excepción o no. Aunque inflar airbags es una tarea poco común en la mayoría de los contextos de programación, esto es bastante común en la mayoría de las tareas de limpieza.
ensure
como alternativa rescue Exception
es engañosa: el ejemplo implica que son equivalentes, pero como se dijo ensure
sucederá si hay una Excepción o no, por lo que ahora sus bolsas de aire se inflarán porque superó las 5 mph, aunque nada salió mal.
Porque esto captura todas las excepciones. Es poco probable que su programa pueda recuperarse de cualquiera de ellos.
Debe manejar solo las excepciones de las que sabe cómo recuperarse. Si no anticipa un cierto tipo de excepción, no lo maneje, bloquee ruidosamente (escriba los detalles en el registro), luego diagnostique los registros y corrija el código.
Tragar excepciones es malo, no hagas esto.
Ese es un caso específico de la regla de que no debe detectar ninguna excepción que no sepa cómo manejar. Si no sabe cómo manejarlo, siempre es mejor dejar que otra parte del sistema lo atrape y lo maneje.
Acabo de leer una gran publicación de blog al respecto en honeybadger.io :
¿Por qué no deberías rescatar a Exception?
El problema con el rescate de Exception es que en realidad rescata cada excepción que hereda de Exception. Lo cual es ... ¡todos ellos!
Eso es un problema porque hay algunas excepciones que Ruby usa internamente. No tienen nada que ver con tu aplicación, y tragarlas hará que sucedan cosas malas.
Estos son algunos de los grandes:
SignalException :: Interrupt - Si rescatas esto, no puedes salir de tu aplicación presionando control-c.
ScriptError :: SyntaxError - La ingestión de errores de sintaxis significa que cosas como put ("Olvidé algo" fallarán en silencio.
NoMemoryError - ¿Quieres saber qué sucede cuando tu programa sigue ejecutándose después de que usa toda la RAM? Yo tampoco.
begin do_something() rescue Exception => e # Don't do this. This will swallow every single exception. Nothing gets past it. end
Supongo que realmente no quieres tragarte ninguna de estas excepciones a nivel de sistema. Solo desea detectar todos los errores de nivel de aplicación. Las excepciones causaron SU código.
Afortunadamente, hay una manera fácil de hacerlo.