968 votes

Pourquoi est-ce un mauvais style de "sauver l'exception => e" en Ruby ?

Ryan Davis Ruby QuickRef dit (sans explication) :

Ne pas sauver l'exception. JAMAIS. ou je te poignarde.

Pourquoi pas ? Quelle est la bonne chose à faire ?

35 votes

Alors vous pourriez probablement écrire votre propre :)

74 votes

Je suis très mal à l'aise avec l'appel à la violence ici. C'est juste un programme.

1 votes

Jetez un coup d'œil à cet article en Ruby Exception avec une belle Hiérarchie des exceptions en Ruby .

1453voto

Andrew Marshall Points 43955

TL;DR : Utiliser StandardError à la place pour la capture des exceptions générales. Lorsque l'exception originale est soulevée à nouveau (par exemple, lors d'un sauvetage pour enregistrer l'exception seulement), le sauvetage de Exception est probablement correct.


Exception est la racine de La hiérarchie des exceptions de Ruby donc quand vous rescue Exception que vous sauvez de tout y compris les sous-classes telles que SyntaxError , LoadError y Interrupt .

Sauvetage Interrupt empêche l'utilisateur d'utiliser CTRLC pour quitter le programme.

Sauvetage SignalException empêche le programme de répondre correctement aux signaux. Il sera impossible de le tuer, sauf par kill -9 .

Sauvetage SyntaxError signifie que eval qui échouent le feront en silence.

Tout cela peut être démontré en exécutant ce programme, et en essayant de CTRLC o kill il :

loop do
  begin
    sleep 1
    eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
  rescue Exception
    puts "I refuse to fail or be stopped!"
  end
end

Sauvetage de Exception n'est même pas le défaut. Faire

begin
  # iceberg!
rescue
  # lifeboats
end

ne sauve pas de Exception il sauve de StandardError . Vous devez généralement spécifier quelque chose de plus précis que la valeur par défaut de l'option StandardError mais en sauvant de Exception élargit le site le champ d'application plutôt que de le réduire, et peut avoir des résultats catastrophiques et rendre la recherche de bogues extrêmement difficile.


Si vous avez une situation où vous voulez sauver de la StandardError et que vous avez besoin d'une variable avec l'exception, vous pouvez utiliser ce formulaire :

begin
  # iceberg!
rescue => e
  # lifeboats
end

ce qui est équivalent à :

begin
  # iceberg!
rescue StandardError => e
  # lifeboats
end

L'un des rares cas communs où il est sain de sauver des Exception c'est à des fins de journalisation/rapport, auquel cas vous devez immédiatement relancer l'exception :

begin
  # iceberg?
rescue Exception => e
  # do some logging
  raise # not enough lifeboats ;)
end

136 votes

Donc c'est comme attraper Throwable en java

11 votes

Si vous relancez l'exception, alors c'est bien puisque vous ne l'avalez pas, mais que vous essayez juste de savoir que cela s'est produit et de le laisser remonter. C'est généralement le cas pour la journalisation.

54 votes

Ce conseil est bon pour un environnement Ruby propre. Mais malheureusement, un certain nombre de gemmes ont créé des exceptions qui descendent directement de Exception. Notre environnement en compte 30 : par exemple OpenID::Server::EncodingError, OAuth::InvalidRequest, HTMLTokenizerSample. Il s'agit d'exceptions que l'on voudrait vraiment attraper dans des blocs de sauvetage standard. Malheureusement, rien dans Ruby n'empêche ou même ne décourage les gems d'hériter directement d'Exception -- même le nommage est peu intuitif.

86voto

Michael Slade Points 9128

El réel La règle est : Ne pas jeter les exceptions. L'objectivité de l'auteur de votre citation est douteuse, comme en témoigne le fait qu'elle se termine par

ou je te poignarde

Bien sûr, il faut savoir que les signaux (par défaut) lèvent des exceptions, et que les processus qui s'exécutent depuis longtemps sont normalement interrompus par le biais d'un signal. Ne faites donc pas cela :

#! /usr/bin/ruby

while true do
  begin
    line = STDIN.gets
    # heavy processing
  rescue Exception => e
    puts "caught exception #{e}! ohnoes!"
  end
end

Non, vraiment, ne le fais pas. Ne le fais même pas pour voir si ça marche.

Cependant, disons que vous avez un serveur threadé et que vous voulez que toutes les exceptions ne soient pas :

  1. être ignoré (par défaut)
  2. arrêter le serveur (ce qui se produit si vous dites thread.abort_on_exception = true ).

Dans ce cas, cela est parfaitement acceptable dans votre fil de gestion des connexions :

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

Ce qui précède est une variante du gestionnaire d'exceptions par défaut de Ruby, avec l'avantage qu'il ne tue pas votre programme. Rails fait cela dans son gestionnaire de requête.

Les exceptions de signal sont soulevées dans le thread principal. Les threads d'arrière-plan ne les reçoivent pas, il est donc inutile d'essayer de les attraper à cet endroit.

C'est particulièrement utile dans un environnement de production, où l'on fait pas vous voulez que votre programme s'arrête simplement dès que quelque chose ne va pas. Vous pouvez alors prendre les vidages de pile dans vos journaux et ajouter à votre code pour traiter les exceptions spécifiques plus bas dans la chaîne d'appel et d'une manière plus gracieuse.

Notez également qu'il existe un autre idiome Ruby qui a à peu près le même effet :

a = do_something rescue "something else"

Dans cette ligne, si do_something lève une exception, elle est attrapée par Ruby, jetée, et a est attribué "something else" .

En général, ne faites pas cela, sauf dans des cas particuliers où vous connaître vous n'avez pas besoin de vous inquiéter. Un exemple :

debugger rescue nil

El debugger est un moyen plutôt agréable de définir un point d'arrêt dans votre code, mais si elle est exécutée en dehors d'un débogueur, et de Rails, elle lève une exception. En théorie, vous ne devriez pas laisser traîner du code de débogage dans votre programme (pff ! personne ne fait ça !) mais vous pourriez vouloir le garder là pendant un certain temps pour une raison ou une autre, sans pour autant lancer continuellement votre débogueur.

Note :

  1. Si vous avez exécuté le programme de quelqu'un d'autre qui attrape les exceptions de signal et les ignore (disons le code ci-dessus), alors.. :

    • sous Linux, dans un shell, tapez pgrep ruby o ps | grep ruby recherchez le PID de votre programme incriminé, puis exécutez kill -9 <PID> .
    • sous Windows, utilisez le gestionnaire des tâches ( CTRL - SHIFT - ESC ), allez dans l'onglet "processus", trouvez votre processus, faites un clic droit et sélectionnez "Terminer le processus".
  2. Si vous travaillez avec le programme de quelqu'un d'autre qui, pour une raison ou une autre, est truffé de ces blocs d'exceptions ignorées, le fait de placer ce bloc en haut de la ligne principale est une échappatoire possible :

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

    Cela amène le programme à répondre aux signaux de fin normaux en se terminant immédiatement, en contournant les gestionnaires d'exceptions, sans nettoyage . Cela peut donc entraîner une perte de données ou autre. Faites attention !

  3. Si vous devez le faire :

    begin
      do_something
    rescue Exception => e
      critical_cleanup
      raise
    end

    vous pouvez vraiment le faire :

    begin
      do_something
    ensure
      critical_cleanup
    end

    Dans le second cas, critical cleanup sera appelé à chaque fois, qu'une exception soit levée ou non.

21 votes

Désolé, c'est faux. Un serveur doit nunca sauver l'exception et ne rien faire d'autre que de l'enregistrer. Cela le rendra inefficace, sauf par les moyens suivants kill -9 .

8 votes

Vos exemples dans la note 3 ne sont pas équivoques. ensure s'exécutera, qu'une exception soit levée ou non, tandis que l'option rescue ne s'exécutera que si une exception a été levée.

1 votes

Ils ne sont pas /exactement/ équivalents mais je n'arrive pas à trouver comment exprimer succinctement l'équivalence d'une manière qui ne soit pas laide.

49voto

Sergio Tulentsev Points 82783

Parce que cela capture toutes les exceptions. Il est peu probable que votre programme puisse se remettre de tout d'entre eux.

Vous ne devez gérer que les exceptions dont vous savez comment vous remettre. Si vous n'anticipez pas un certain type d'exception, ne la gérez pas, plantez bruyamment (écrivez les détails dans le journal), puis diagnostiquez les journaux et corrigez le code.

Avaler des exceptions est mauvais, ne faites pas ça.

13voto

Russell Borogove Points 8423

C'est un cas spécifique de la règle selon laquelle vous ne devriez pas attraper tout l'exception que vous ne savez pas comment gérer. Si vous ne savez pas comment la gérer, il est toujours préférable de laisser une autre partie du système l'attraper et la gérer.

0voto

shevchik Points 6781

Parce que vous allez attraper toutes les exceptions soulevées dans votre application (même celles soulevées à un niveau inférieur). Vous ne devez attraper que les exceptions que vous avez soulevées.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X