¿Por qué es malo estilo ‘rescatar Exception => e` en Ruby?

Ruby QuickRef de Ryan Davis dice (sin explicación):

No rescates la excepción. NUNCA. o te apuñalaré.

Por qué no? ¿Qué es lo correcto?

TL; DR : utilice StandardError lugar para captura de excepción general. Cuando se vuelve a generar la excepción original (por ejemplo, al rescatar para registrar solo la excepción), es probable que exista la Exception rescate.


Exception es la raíz de la jerarquía de excepciones de Ruby , por lo que cuando rescue Exception rescatas de todo , incluidas subclases como SyntaxError , LoadError e Interrupt .

La Interrupt rescate evita que el usuario use CTRL C para salir del progtwig.

El SignalException evita que el progtwig responda correctamente a las señales. Será inacabable excepto por kill -9 .

Rescatar SyntaxError significa que las eval que fallan lo harán de forma silenciosa.

Todos estos se pueden mostrar ejecutando este progtwig e intentando 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 no es ni siquiera el predeterminado. Obra

 begin # iceberg! rescue # lifeboats end 

no rescata de Exception , rescata de StandardError . En general, debe especificar algo más específico que StandardError predeterminado, pero rescatar de Exception amplía el scope en lugar de reducirlo, y puede tener resultados catastróficos y dificultar la búsqueda de errores.


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 sensato rescatar de 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 e # not enough lifeboats ;) end 

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é

Por supuesto, tenga en cuenta que las señales (por defecto) arrojan excepciones, y normalmente los procesos de larga duración terminan a través de una señal, por lo que capturar Exception y no terminar en excepciones de señal hará que su progtwig 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, realmente, no lo hagas. Ni siquiera corras para ver si funciona.

Sin embargo, supongamos que tiene un servidor enhebrado y desea que todas las excepciones no:

  1. ser ignorado (el predeterminado)
  2. detener el servidor (lo que sucede si dices 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 manejador de excepciones predeterminado de Ruby, con la ventaja de que no mata tu progtwig. Rails hace esto en su controlador de solicitud.

Las excepciones de señal se generan 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 no desea que su progtwig simplemente se detenga cada vez que algo sale mal. Luego puede tomar los volcados de stack en sus registros y agregarlos a su código para hacer frente a una excepción específica más adelante 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 atrapa, la descarta y a se le asigna "something else" .

Por lo 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 el código, pero si se ejecuta fuera de un depurador y Rails, genera una excepción. Ahora, teóricamente, no deberías dejar el código de depuración en tu progtwig (¡por favor! ¡Nadie lo hace!) Pero es posible que quieras mantenerlo allí por un tiempo por algún motivo, pero no ejecutar continuamente tu depurador.

Nota:

  1. Si ha ejecutado el progtwig de otra persona que capta excepciones de señal y las ignora, (diga el código anterior), entonces:

    • en Linux, en un shell, escriba pgrep ruby , o ps | grep ruby ps | grep ruby , busque el PID de su progtwig ofensivo y luego ejecute kill -9 .
    • en Windows, use el Administrador de tareas ( CTRLMAYÚSESC ), vaya a la pestaña “procesos”, encuentre su proceso, haga clic derecho y seleccione “Terminar proceso”.
  2. Si está trabajando con el progtwig de otra persona que, por alguna razón, está salpicado con estos bloques de ignorar excepción, entonces poner esto en la parte superior de la línea principal es un posible escape:

     %W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" } 

    Esto hace que el progtwig responda a las señales de terminación normales al finalizar de forma inmediata, pasando por alto a los manejadores de excepciones, sin ninguna limpieza . Por lo tanto, podría causar la pérdida de datos o similar. ¡Ten cuidado!

  3. 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, se solicitará critical cleanup cada vez, independientemente de que se haya lanzado una excepción.

Digamos que estás en un auto (corriendo Ruby). Recientemente instaló un nuevo volante con el sistema de actualización por air (que usa eval ), pero no sabía que uno de los progtwigdores había estropeado la syntax.

Estás en un puente y te das cuenta de que vas un poco hacia la barandilla, por lo que debes girar a la izquierda.

 def turn_left self.turn left: end 

Uy! Probablemente eso no sea bueno ™. Afortunadamente, Ruby plantea un SyntaxError .

El auto debe detenerse inmediatamente, ¿no?

Nop.

 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: Capturada la excepción SyntaxError.

Información: Error registrado – Proceso continuo.

Usted nota que algo está mal, y golpea los descansos de emergencia ( ^C : Interrupt )

beep beep

Advertencia: excepción de interrupción atrapada.

Información: Error registrado – Proceso continuo.

Sí, eso no ayudó mucho. Estás muy cerca de la barandilla, así que pones el auto en el parque ( kill : SignalException ).

beep beep

Advertencia: se ha detectado la excepción SignalException.

Información: Error registrado – Proceso continuo.

En el último segundo, sacas las llaves ( kill -9 ), y el auto se detiene, te abres hacia adelante en el volante (el airbag no se puede inflar porque no detuviste el progtwig con gracia, lo terminaste), y la computadora en la parte trasera de tu auto choca contra el asiento que está frente a ella. Una lata medio llena de Coca-Cola se dertwig sobre los papeles. Los comestibles en la parte posterior están aplastados, y la mayoría están cubiertos de yema de huevo y leche. El automóvil necesita reparación y limpieza serias. (Pérdida de datos)

Espero que tengas seguro (copias de seguridad). Ah, sí, porque el airbag no se infló, probablemente te lastimes (te despidan, etc.).


¡Pero espera! Hay Más razones por las que puede querer usar rescue Exception => e !

Digamos que usted es ese automóvil, y quiere asegurarse de que el airbag se infla si el automóvil está excediendo su impulso de parada segura.

  begin # do driving stuff rescue Exception => e self.airbags.inflate if self.exceeding_safe_stopping_momentum? raise end 

Esta es la excepción a la regla: puede capturar Exception solo si vuelve a generar la excepción . Por lo tanto, una regla mejor es no engullir Exception y siempre volver a generar el error.

Pero agregar rescate es fácil de olvidar en un lenguaje como Ruby, y poner una statement de rescate justo antes de volver a plantear un problema parece un poco no SECO. Y no quiere olvidar la statement de raise . Y si lo haces, buena suerte tratando de encontrar ese error.

Afortunadamente, Ruby es increíble, solo puede usar la palabra clave Ensure, que asegura que el código se ejecute. La palabra clave de ensure ejecutará el código sin importar qué, si se lanza una excepción, si no lo es, siendo la única excepción si el mundo finaliza (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 @Niall señaló, asegúrese de que siempre se ejecute. Esto es bueno porque a veces su progtwig puede mentirle y no arrojar excepciones, incluso cuando ocurren problemas. Con tareas críticas, como inflar las bolsas de air, debes asegurarte de que suceda sin importar nada. Debido a esto, es una buena idea consultar cada vez que el auto se detiene, ya sea que se produzca una excepción o no. Aunque inflar airbags es una tarea poco común en la mayoría de los contextos de progtwigción, esto es bastante común con la mayoría de las tareas de limpieza.


TL; DR

No rescue Exception => e (y no vuelvas a subir la excepción), o puedes salir de un puente.

Porque esto captura todas las excepciones. Es poco probable que su progtwig pueda recuperarse de alguno de ellos.

Solo debe manejar excepciones de las que sepa cómo recuperarse. Si no anticipa un cierto tipo de excepción, no la maneje, cuelgue fuerte (escriba 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 detecte y maneje.

Esto también le ocultará errores, por ejemplo si escribió mal el nombre de un método:

 def my_fun "my_fun" end begin # you mistypped my_fun to my_func my_func # my_func() rescue Exception # rescued NameError (or NoMethodError if you called method with parenthesis) end