¿Por qué es un mal estilo `rescue Exception => e` en Ruby?
Ryan Davis Ruby QuickRef dice (sin explicación):
No rescate Excepción. NUNCA. o te apuñalaré.
¿Por qué no? ¿Qué es lo correcto?
5 answers
TL; DR : Use StandardError
en su lugar para la captura de excepciones generales. Cuando se vuelve a levantar la excepción original (por ejemplo, cuando se rescata para registrar solo la excepción), rescatar Exception
probablemente esté bien.
Exception
es la raíz de la jerarquía de excepciones de Ruby , por lo que cuando rescue Exception
rescatas de todo , incluyendo subclases como SyntaxError
, LoadError
, y Interrupt
.
Rescatar Interrupt
evita que el usuario use CTRLC para salir del programa.
Rescatar SignalException
impide que el programa responda correctamente a las señales. Será imposible de matar excepto por kill -9
.
Rescatar SyntaxError
significa que eval
los que fallen lo harán silenciosamente.
Todo esto se puede mostrar ejecutando este programa, y tratando de CTRLC o kill
it:
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 rescate de Exception
, rescata de StandardError
. Usted debe generalmente especificar algo más específico que el predeterminado StandardError
, pero rescatar de 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 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 / informes, en cuyo caso debe volver a plantear inmediatamente la excepción:
begin
# iceberg?
rescue Exception => e
# do some logging
raise e # not enough lifeboats ;)
end
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-26 21:00:47
La regla real es: No deseche las excepciones. La objetividad del autor de su cita es cuestionable, como lo demuestra el hecho de que termina con
O te apuñalaré{[19]]}
Por supuesto, tenga en cuenta que las señales (por defecto) lanzan 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ñal hará que su programa sea muy difícil de detener. Así que no lo 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 hagas para ver si funciona.
Sin embargo, supongamos que tiene un servidor enhebrado y desea que todas las excepciones no:
- ser ignorado (por defecto)
- detenga el servidor (lo que sucede si dice
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 a una variación del controlador de excepciones predeterminado de Ruby, con la ventaja de que tampoco mata tu programa. Rails hace esto en su gestor de peticiones.
Las excepciones de señal se elevan en el hilo principal. Los hilos de fondo no los obtendrán, por lo que no tiene sentido tratar de atraparlos allí.
Esto es particularmente útil en un entorno de producción, donde usted no quiere 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 excepciones específicas más abajo la cadena de llamadas y de una manera más elegante.
Tenga en cuenta también que hay otro lenguaje Ruby que tiene el mismo efecto: {[19]]}
a = do_something rescue "something else"
En esta línea, si do_something
plantea una excepción, es atrapada por Ruby, desechada, y a
es asignada "something else"
.
Generalmente, no hagas eso, excepto en casos especiales donde sabes que no necesitas preocuparte. Un ejemplo:
debugger rescue nil
La función debugger
es una forma bastante agradable de establecer un punto de interrupción en su código, pero si ejecutándose fuera de un depurador, y Rails, genera una excepción. Ahora teóricamente no debería dejar código de depuración por ahí en su programa (pff! nadie hace eso!) pero es posible que desee mantenerlo allí por un tiempo por alguna razón, pero no ejecutar continuamente su depurador.
Nota:
-
Si ha ejecutado el programa de otra persona que detecta excepciones de señal y las ignora, (diga el código anterior) entonces:
- en Linux, en un shell, escriba
pgrep ruby
, ops | grep ruby
, busca el PID de tu programa infractor, y luego ejecutakill -9 <PID>
. - en Windows, utilice el Administrador de tareas (CTRL-SHIFT-ESC ), vaya a la pestaña" procesos", encuentre su proceso, haga clic derecho y seleccione"Finalizar proceso".
- en Linux, en un shell, escriba
-
Si está trabajando con el programa de otra persona que, por cualquier razón, está salpicado de estos bloques de excepción de ignorados, entonces poner esto en la parte superior de la línea principal es una posible cop-out:
%W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
Esto hace que el programa responda a las señales de terminación normales finalizando inmediatamente, sin pasar por los controladores de excepciones, 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: {[19]]}
begin do_something ensure critical_cleanup end
En el segundo caso,
critical cleanup
se llamará cada vez, independientemente de que se lance o no una excepción.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-30 21:27:27
Digamos que estás en un coche (ejecutando 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 se equivocó en 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
Oops! Eso es probablemente No Es Bueno™, por suerte, Ruby plantea un SyntaxError
.
El coche debe 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
Beep beep
Advertencia: Se detecta la excepción SyntaxError.
Info: Error registrado - Proceso Continuo.
Te das cuenta de que algo está mal, y golpeas los descansos de emergencia(^C
: Interrupt
)
Beep beep
Advertencia: Excepción de Interrupción captada.
Info: Error registrado - Proceso Continuo.
Sí, eso no ayudó mucho. Estás bastante cerca del carril, así que aparcas el coche (kill
ing: SignalException
).
Beep beep
Advertencia: Excepción Señalexception capturada.
Info: Error registrado - Proceso Continuo.
En el último segundo, saca las llaves (kill -9
), y el coche se detiene, slam hacia el volante (la bolsa de aire no se inflan porque no detener correctamente el programa - terminó ella), y el equipo en el la parte trasera de su coche se golpea contra el asiento en frente de él. Una lata medio llena de coca se derrama sobre los periódicos. Los alimentos en la parte posterior se trituran, y la mayoría están cubiertos de yema de huevo y leche. El coche necesita una reparación y limpieza serias. (Pérdida de datos)
Esperemos que tenga un seguro (Copias de seguridad). Oh sí-debido a que la bolsa de aire no se infló, probablemente estás herido (ser despedido, etc.).
Pero espera! Hay más razones por las que es posible que desee utilizar rescue Exception => e
!
Vamos a digamos que eres ese auto, y quieres asegurarte de que el airbag se infle si el auto está excediendo su momento 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: Puedes atrapar Exception
solo si vuelve a plantear la excepción . Por lo tanto, una mejor regla es nunca tragar Exception
, y siempre volver a plantear el error.
Pero agregar rescue es fácil de olvidar en un lenguaje como Ruby, y poner una declaración de rescue justo antes de volver a plantear un problema se siente un poco no SECO. Y tú no quiero olvidar la declaración raise
. Y si lo haces, buena suerte tratando de encontrar ese error.
Afortunadamente, Ruby es impresionante, solo puede usar la palabra clave ensure
, que se asegura de 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 lo es, 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
Boom! 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 acceso a la excepción, o si solo desea que el código se ejecute en una excepción. Y recuerde volver a elevar el error. Siempre.
Nota: Como @Niall señaló, 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 qué. Debido a esto, comprobar cada vez que el coche se detiene, si se lanza una excepción o no, es un buena idea. A pesar de que inflar bolsas de aire es una tarea poco común en la mayoría de los contextos de programación, esto es en realidad bastante común con la mayoría de las tareas de limpieza.
TL; DR
No rescue Exception => e
(y no volver a levantar la excepción)-o podría conducir fuera de un puente.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-03 19:40:37
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 la maneje, bloquee en voz alta (escriba los detalles en el registro), luego diagnostique los registros y corrija el código.
Tragar excepciones es malo, no hagas esto.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-06 19:21:19
Ese es un caso específico de la regla de que no debes atrapar ninguna excepción que no sepas manejar. Si no sabes cómo manejarlo, siempre es mejor dejar que otra parte del sistema lo atrape y lo maneje.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-06 23:54:05