¿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?

Author: Andrew Marshall, 2012-04-06

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 evallos 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
 1261
Author: Andrew Marshall,
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:

  1. ser ignorado (por defecto)
  2. 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:

  1. 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, o ps | grep ruby, busca el PID de tu programa infractor, y luego ejecuta kill -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".
  2. 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!

  3. 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.

 76
Author: Michael Slade,
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.

 51
Author: Ben Aubin,
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.

 44
Author: Sergio Tulentsev,
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.

 9
Author: Russell Borogove,
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