Ruby QuickRef de Ryan Davis dit (sans explication):
Ne sauvez pas l'exception. DÉJÀ. ou je te poignarderai.
Pourquoi pas? Quelle est la bonne chose à faire?
Ruby QuickRef de Ryan Davis dit (sans explication):
Ne sauvez pas l'exception. DÉJÀ. ou je te poignarderai.
Pourquoi pas? Quelle est la bonne chose à faire?
Réponses:
TL; DR : utilisez à la StandardError
place pour la capture d'exceptions générales. Lorsque l'exception d'origine est re-levée (par exemple lors d'un sauvetage pour enregistrer l'exception uniquement), le sauvetage Exception
est probablement correct.
Exception
est la racine de la hiérarchie des exceptions de Ruby , donc quand vous rescue Exception
vous sauver de tout , y compris les sous - classes telles que SyntaxError
, LoadError
et Interrupt
.
Le sauvetage Interrupt
empêche l'utilisateur d'utiliser CTRLCpour quitter le programme.
Le sauvetage SignalException
empêche le programme de répondre correctement aux signaux. Ce sera impossible à tuer sauf par kill -9
.
Le sauvetage SyntaxError
signifie que eval
les échecs se feront en silence.
Tous ces éléments peuvent être affichés en exécutant ce programme, et en essayant de CTRLCou kill
il:
loop do
begin
sleep 1
eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
rescue Exception
puts "I refuse to fail or be stopped!"
end
end
Le sauvetage Exception
n'est même pas la valeur par défaut. Faire
begin
# iceberg!
rescue
# lifeboats
end
ne sauve pas Exception
, il sauve StandardError
. Vous devez généralement spécifier quelque chose de plus spécifique que la valeur par défaut StandardError
, mais le sauvetage Exception
élargit la portée plutôt que de la réduire, et peut avoir des résultats catastrophiques et rendre la recherche de bogues extrêmement difficile.
Si vous avez une situation dans laquelle vous souhaitez effectuer un sauvetage StandardError
et que vous avez besoin d'une variable à l'exception, vous pouvez utiliser ce formulaire:
begin
# iceberg!
rescue => e
# lifeboats
end
ce qui équivaut à:
begin
# iceberg!
rescue StandardError => e
# lifeboats
end
L'un des rares cas courants où il est raisonnable de sauver Exception
est à des fins de journalisation / génération de rapports, auquel cas vous devez immédiatement lever à nouveau l'exception:
begin
# iceberg?
rescue Exception => e
# do some logging
raise # not enough lifeboats ;)
end
Throwable
en java
ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error, Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]
et puisrescue *ADAPTER_ERRORS => e
La vraie règle est: ne jetez pas les exceptions. L'objectivité de l'auteur de votre citation est discutable, comme en témoigne le fait qu'elle se termine par
ou je te poignarderai
Bien sûr, sachez que les signaux (par défaut) lèvent des exceptions, et que les processus de longue durée se terminent par un signal, donc intercepter une exception et ne pas terminer sur des exceptions de signal rendra votre programme très difficile à arrêter. Alors ne fais pas ça:
#! /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 lancez même pas cela pour voir si cela fonctionne.
Cependant, supposons que vous ayez un serveur threadé et que vous ne souhaitiez pas que toutes les exceptions:
thread.abort_on_exception = true
). Ensuite, cela est parfaitement acceptable dans votre thread de gestion de connexion:
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 correspond à une variante du gestionnaire d'exceptions par défaut de Ruby, avec l'avantage qu'il ne tue pas également votre programme. Rails le fait dans son gestionnaire de requêtes.
Les exceptions de signal sont levées dans le thread principal. Les fils d'arrière-plan ne les obtiendront pas, il est donc inutile d'essayer de les attraper là-bas.
Ceci est particulièrement utile dans un environnement de production, où vous ne voulez pas que votre programme s'arrête simplement en cas de problème. Ensuite, vous pouvez prendre les vidages de pile dans vos journaux et ajouter à votre code pour traiter des exceptions spécifiques plus bas dans la chaîne d'appels 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, il est attrapé par Ruby, jeté et a
attribué "something else"
.
Généralement, ne faites pas cela, sauf dans des cas particuliers où vous savez que vous n'avez pas à vous inquiéter. Un exemple:
debugger rescue nil
le debugger
fonction est un moyen plutôt agréable de définir un point d'arrêt dans votre code, mais si elle s'exécute en dehors d'un débogueur et de Rails, elle déclenche une exception. Maintenant, théoriquement, vous ne devriez pas laisser traîner du code de débogage dans votre programme (pff!
Remarque:
Si vous avez exécuté le programme de quelqu'un d'autre qui intercepte les exceptions de signal et les ignore (dites le code ci-dessus), alors:
pgrep ruby
ou ps | grep ruby
recherchez le PID de votre programme incriminé, puis exécutez kill -9 <PID>
. Si vous travaillez avec le programme de quelqu'un d'autre qui, pour quelque raison que ce soit, est parsemé de ces blocs ignorer les exceptions, le mettre en haut de la ligne principale est une sortie possible:
%W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
Cela oblige le programme à répondre aux signaux de terminaison normaux en se terminant immédiatement, en contournant les gestionnaires d'exceptions, sans nettoyage . Cela pourrait donc entraîner une perte de données ou similaire. Faites attention!
Si vous devez le faire:
begin
do_something
rescue Exception => e
critical_cleanup
raise
end
vous pouvez réellement faire ceci:
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.
kill -9
.
ensure
qu'il y ait une exception levée ou non, tandis que le test rescue
ne fonctionnera que si une exception a été déclenchée.
Ne le faites pas rescue Exception => e
(et ne relancez pas l'exception) - ou vous pourriez quitter un pont.
Disons que vous êtes dans une voiture (avec Ruby). Vous avez récemment installé un nouveau volant avec le système de mise à niveau over-the-air (qui utilise eval
), mais vous ne saviez pas que l'un des programmeurs s'était trompé sur la syntaxe.
Vous êtes sur un pont et vous vous rendez compte que vous vous dirigez un peu vers la balustrade, alors tournez à gauche.
def turn_left
self.turn left:
end
Oops! Ce n'est probablement pas bon ™, heureusement, Ruby soulève un SyntaxError
.
La voiture devrait s'arrêter immédiatement - non?
Nan.
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
Avertissement: exception SyntaxError interceptée.
Info: Erreur enregistrée - Processus en cours.
Vous remarquez quelque chose ne va pas, et vous slam sur les pauses d'urgence ( ^C
: Interrupt
)
bip Bip
Avertissement: exception d'interruption interceptée.
Info: Erreur enregistrée - Processus en cours.
Ouais - ça n'a pas beaucoup aidé. Vous êtes assez près du rail, alors vous mettez la voiture en stationnement ( kill
ing:) SignalException
.
bip Bip
Avertissement: exception SignalException interceptée.
Info: Erreur enregistrée - Processus en cours.
À la dernière seconde, vous retirez les clés ( kill -9
), et la voiture s'arrête, vous claquez vers l'avant dans le volant (l'airbag ne peut pas se gonfler parce que vous n'avez pas gracieusement arrêté le programme - vous l'avez terminé), et l'ordinateur à l'arrière de votre voiture claque dans le siège en face d'elle. Une canette à moitié pleine de Coke déborde sur les papiers. L'épicerie à l'arrière est écrasée et la plupart sont recouvertes de jaune d'oeuf et de lait. La voiture doit être sérieusement réparée et nettoyée. (Perte de données)
J'espère que vous avez une assurance (sauvegardes). Oh ouais - parce que l'airbag ne s'est pas gonflé, vous êtes probablement blessé (être renvoyé, etc.).
Mais attendez! Il y aplusraisons pour lesquelles vous voudrez peut-être utiliser rescue Exception => e
!
Disons que vous êtes cette voiture et que vous voulez vous assurer que l'airbag se gonfle si la voiture dépasse sa vitesse d'arrêt en toute sécurité.
begin
# do driving stuff
rescue Exception => e
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
raise
end
Voici l'exception à la règle: vous ne pouvez intercepter Exception
que si vous relancez l'exception . Donc, une meilleure règle est de ne jamais avaler Exception
et de toujours relancer l'erreur.
Mais l'ajout de sauvetage est à la fois facile à oublier dans un langage comme Ruby, et mettre une déclaration de sauvetage juste avant de soulever un problème semble un peu sec. Et vous ne voulez pas oublier la raise
déclaration. Et si vous le faites, bonne chance pour essayer de trouver cette erreur.
Heureusement, Ruby est génial, vous pouvez simplement utiliser le ensure
mot - clé, qui s'assure que le code s'exécute. Le ensure
mot-clé exécutera le code quoi qu'il arrive - si une exception est levée, si ce n'est pas le cas, la seule exception étant la fin du monde (ou d'autres événements improbables).
begin
# do driving stuff
ensure
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
end
Boom! Et ce code devrait fonctionner de toute façon. La seule raison que vous devez utiliser rescue Exception => e
est si vous avez besoin d'accéder à l'exception ou si vous souhaitez uniquement que le code s'exécute sur une exception. Et n'oubliez pas de relancer l'erreur. À chaque fois.
Remarque: Comme l'a souligné @Niall, assurez-vous qu'il fonctionne toujours . C'est bien parce que parfois votre programme peut vous mentir et ne pas lever d'exceptions, même lorsque des problèmes surviennent. Avec les tâches critiques, comme le gonflage des airbags, vous devez vous assurer que cela se produit quoi qu'il arrive. Pour cette raison, vérifier chaque fois que la voiture s'arrête, si une exception est levée ou non, est une bonne idée. Même si le gonflage des airbags est une tâche peu courante dans la plupart des contextes de programmation, cela est en fait assez courant avec la plupart des tâches de nettoyage.
ensure
comme alternative à rescue Exception
est trompeuse - l'exemple implique qu'ils sont équivalents, mais comme indiqué ensure
se produira qu'il y ait une exception ou non, alors maintenant vos airbags se déploieront parce que vous avez dépassé 5 mph, même si rien ne s'est mal passé.
Parce que cela capture toutes les exceptions. Il est peu probable que votre programme puisse récupérer de l' un d'eux.
Vous ne devez gérer que les exceptions dont vous savez comment récupérer. Si vous ne prévoyez pas un certain type d'exception, ne le gérez pas, plantez fort (écrivez les détails dans le journal), puis diagnostiquez les journaux et corrigez le code.
Avaler des exceptions est mauvais, ne faites pas ça.
C'est un cas spécifique de la règle que vous ne devez pas détecter d' exception que vous ne savez pas comment gérer. Si vous ne savez pas comment le gérer, il est toujours préférable de laisser une autre partie du système l'attraper et le gérer.
Je viens de lire un excellent article de blog à ce sujet sur honeybadger.io :
Pourquoi vous ne devriez pas sauver l'exception
Le problème du sauvetage d'Exception est qu'il sauve en fait chaque exception qui hérite d'Exception. Ce qui est ... tous!
C'est un problème car il existe des exceptions qui sont utilisées en interne par Ruby. Ils n'ont rien à voir avec votre application, et les avaler entraînera de mauvaises choses.
En voici quelques-uns:
SignalException :: Interrupt - Si vous sauvez ceci, vous ne pouvez pas quitter votre application en appuyant sur control-c.
ScriptError :: SyntaxError - Avaler des erreurs de syntaxe signifie que des choses comme put ("J'ai oublié quelque chose) échouent silencieusement.
NoMemoryError - Vous voulez savoir ce qui se passe lorsque votre programme continue de fonctionner après avoir utilisé toute la RAM? Moi non plus.
begin do_something() rescue Exception => e # Don't do this. This will swallow every single exception. Nothing gets past it. end
Je suppose que vous ne voulez pas vraiment avaler ces exceptions au niveau du système. Vous souhaitez uniquement détecter toutes vos erreurs au niveau de l'application. Les exceptions ont provoqué VOTRE code.
Heureusement, il existe un moyen simple d'y parvenir.