standarderror - ¿Por qué es un estilo malo rescatar Excepción=> e` en Ruby?
ruby exception handle (5)
Ruby QuickRef de Ryan Davis dice (sin explicación):
No rescates a la excepción. SIEMPRE. o te apuñalaré.
Por qué no? ¿Qué es lo correcto a hacer?
Digamos que estás en un coche (corriendo Ruby). Hace poco instaló un nuevo volante con el sistema de actualización por aire (que utiliza eval
), pero no sabía que uno de los programadores se había equivocado con la sintaxis.
Estás en un puente y te das cuenta de que vas un poco hacia la barandilla, por lo que giras a la izquierda.
def turn_left
self.turn left:
end
oops Probablemente no sea bueno ™, por suerte, Ruby SyntaxError
un SyntaxError
.
El coche debería detenerse inmediatamente, ¿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: se ha capturado la excepción SyntaxError.
Información: Error registrado - Proceso continuo.
Se da cuenta de que algo está mal y se golpea en los cortes 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 de la baranda, así que pones el auto en el parque ( kill
a: SignalException
).
bip bip
Advertencia: Excepción de SignalException capturada.
Información: Error registrado - Proceso continuo.
En el último segundo, saca las llaves ( kill -9
), y el auto se detiene, se estrella contra el volante (la bolsa de aire no se puede inflar porque no detuvo el programa con gracia, lo terminó). y la computadora en la parte trasera de tu auto se estrella contra el asiento que está frente a él. Una lata de coca cola medio llena se derrama sobre los papeles. Los alimentos en la parte de atrás están triturados, y la mayoría están cubiertos con yema de huevo y leche. El auto necesita serias reparaciones y limpieza. (Pérdida de datos)
Esperemos que tengas seguro (Backups). Oh sí, porque la bolsa de aire no se infla, es probable que estés herido (te despidan, etc.).
¡Pero espera! Hay Más razones por las que podría querer usar la rescue Exception => e
!
Digamos que usted es ese automóvil y desea asegurarse de que la bolsa de aire se infla si el automóvil está excediendo su impulso de parada 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 capturar la Exception
solo si vuelve a subir la excepción . Por lo tanto, una regla mejor es nunca tragar la Exception
y siempre volver a elevar 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 parece un poco no DRY. Y usted no quiere olvidar la declaración de raise
. Y si lo haces, buena suerte intentando encontrar ese error.
Afortunadamente, Ruby es increíble, puedes usar la palabra clave ensure
, que asegura que el código se ejecute. La palabra clave ensure
ejecutará el código sin importar qué: si se lanza una excepción, si no, la única excepción es si el mundo termina (u otros eventos improbables).
begin
# do driving stuff
ensure
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
end
¡Auge! Y ese código debería funcionar de todos modos. La única razón por la que debe usar rescue Exception => e
es si necesita acceder a la excepción, o si solo desea que el código se ejecute en una excepción. Y recuerda volver a subir el error. Cada vez.
Nota: como señaló @Niall, asegúrese de que siempre se ejecuta. Esto es bueno porque a veces su programa puede mentirle y no lanzar excepciones, incluso cuando ocurren problemas. Con las 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 lanza una excepción o no. Aunque inflar las bolsas de aire 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.
TL; DR
No rescue Exception => e
(y no vuelva a subir la excepción), o podría salir de un puente.
Ese es un caso específico de la regla de que no debería detectar ninguna excepción que no sepa manejar. Si no sabe cómo manejarlo, siempre es mejor dejar que otra parte del sistema lo atrape y lo maneje.
La regla real es: no tirar 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) emiten excepciones, y normalmente los procesos de larga duración se terminan a través de una señal, por lo que capturar Excepciones y no terminar en excepciones de señales hará que su programa sea muy difícil de detener. Así que 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 lo ejecutes para ver si funciona.
Sin embargo, supongamos que tiene un servidor con hilos y desea que todas las excepciones no:
- ser ignorado (por defecto)
- detiene el servidor (lo que sucede si dices
thread.abort_on_exception = true
).
Entonces esto es perfectamente aceptable en tu 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 no también 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 captan, por lo que no tiene sentido intentar 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 pila en sus registros y agregarlos a su código para lidiar con una excepción específica más abajo en la cadena de llamadas y de una manera más elegante.
Tenga en cuenta también que hay otro idioma Ruby que tiene el mismo efecto:
a = do_something rescue "something else"
En esta línea, si do_something
genera una excepción, Ruby la captura, la tira y se le asigna "something else"
.
En general, no hagas eso, excepto en casos especiales en los que sabes que no debes preocuparte. Un ejemplo:
debugger rescue nil
La función de debugger
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 desees 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 atrapa las excepciones de señal y las ignora, (diga el código anterior):
- en Linux, en una shell, escriba
pgrep ruby
, ops | grep ruby
ps | grep ruby
, busque el PID de su programa ofensivo y luego ejecutekill -9 <PID>
. - en Windows, use el Administrador de tareas ( CTRL - MAYÚS - ESC ), vaya a la pestaña "procesos", encuentre su proceso, haga clic derecho y seleccione "Finalizar proceso".
- en Linux, en una shell, escriba
Si está trabajando con el programa de otra persona que, por el motivo que sea, está salpicado con 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 de inmediato, sin pasar por los controladores de excepción, sin limpieza . Por lo que podría causar la pérdida de datos o similar. ¡Ten cuidado!
Si necesitas hacer esto:
begin do_something rescue Exception => e critical_cleanup raise end
puedes hacer esto
begin do_something ensure critical_cleanup end
En el segundo caso,
critical cleanup
se llamará cada vez, se lance o no una excepción.
Porque esto captura todas las excepciones. Es poco probable que su programa pueda recuperarse de cualquiera de ellos.
Solo debe manejar las excepciones de las que sabe cómo recuperarse. Si no anticipa un cierto tipo de excepción, no la maneje, cuelgue ruidosamente (escriba los detalles en el registro), luego diagnostique los registros y corrija el código.
Tragar excepciones es malo, no hagas esto.
TL; DR : en su lugar, utilice StandardError
para la captura de excepciones generales. Cuando se vuelve a elevar la excepción original (por ejemplo, cuando se rescata solo para registrar la excepción), es probable que se recupere la Exception
.
Exception
es la raíz de la jerarquía de excepciones de Ruby , de modo que cuando SyntaxError
rescue Exception
, rescatas todo , incluidas subclases como SyntaxError
, LoadError
e Interrupt
.
El rescate de la Interrupt
evita que el usuario use CTRL C para salir del programa.
Al SignalException
el programa no responde correctamente a las señales. Será infalible excepto por kill -9
.
Rescatando SyntaxError
significa que las eval
que fallan lo harán silenciosamente.
Todo esto se puede mostrar ejecutando este programa e intentando presionar CTRL C o kill
:
loop do
begin
sleep 1
eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
rescue Exception
puts "I refuse to fail or be stopped!"
end
end
El rescate de Exception
ni siquiera es el predeterminado. Obra
begin
# iceberg!
rescue
# lifeboats
end
no rescata de la Exception
, rescata de StandardError
. Por lo general, debe especificar algo más específico que el StandardError
predeterminado, pero rescatar de Exception
amplía el alcance en lugar de limitarlo, 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 de 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 sano rescatar de Exception
es para fines de registro / informe, en cuyo caso debe volver a subir la excepción inmediatamente:
begin
# iceberg?
rescue Exception => e
# do some logging
raise e # not enough lifeboats ;)
end