Ryan Davis’s (Ruby QuickRef dit (sans explication):
Ne sauvez pas Exception. DÉJÀ. ou je vais te poignarder.
Pourquoi pas? Quelle est la bonne chose à faire?
TL; DR : utilisez plutôt StandardError
pour la capture d’exception générale. Lorsque l'exception d'origine est à nouveau levée (par exemple, lors de la sauvegarde pour enregistrer uniquement l'exception), la récupération de Exception
est probablement correcte.
Exception
est la racine de hiérarchie des exceptions de Ruby , donc lorsque vous rescue Exception
vous sauvez de tout , y compris les sous-classes telles que SyntaxError
, LoadError
et Interrupt
.
Sauvetage Interrupt
empêche l'utilisateur d'utiliser CTRLC pour quitter le programme.
Sauver SignalException
empêche le programme de répondre correctement aux signaux. Il sera impossible à tuer sauf par kill -9
.
Sauver SyntaxError
signifie que eval
s qui échouent le feront en silence.
Tout cela peut être montré en exécutant ce programme et en essayant de CTRLC ou kill
le:
loop do
begin
sleep 1
eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
rescue Exception
puts "I refuse to fail or be stopped!"
end
end
Sauver de Exception
n'est même pas la valeur par 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 spécifique que la valeur par défaut StandardError
, mais le fait de sauver de Exception
élargit la portée plutôt que de la réduire, et peut avoir des résultats catastrophiques et rendre la chasse aux bogues extrêmement difficile.
Si vous voulez sauver de StandardError
et que vous avez besoin d'une variable exceptée, vous pouvez utiliser le formulaire suivant:
begin
# iceberg!
rescue => e
# lifeboats
end
qui équivaut à:
begin
# iceberg!
rescue StandardError => e
# lifeboats
end
L’un des rares cas courants où il est sain de sauver de Exception
est utilisé à des fins de journalisation/de génération de rapports, auquel cas vous devez immédiatement lever de nouveau l’exception:
begin
# iceberg?
rescue Exception => e
# do some logging
raise e # not enough lifeboats ;)
end
La règle réelle est la suivante: 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 vais te poignarder
Bien sûr, sachez que les signaux (par défaut) émettent des exceptions et que, normalement, les processus de longue durée sont terminés par un signal. Par conséquent, capturer Exception et non les 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 lance même pas ça pour voir si ça marche.
Cependant, supposons que vous ayez un serveur threadé et que vous voulez que toutes les exceptions ne le fassent pas:
thread.abort_on_exception = true
).Ceci 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 threads d'arrière-plan ne les auront pas, il est donc inutile d'essayer de les intercepter.
Ceci est particulièrement utile dans un environnement de production, où vous voulez pas que votre programme s’arrête tout simplement en cas de problème. Vous pouvez ensuite prendre les empilements de pile dans vos journaux et les ajouter à votre code pour traiter les exceptions spécifiques plus loin dans la chaîne d'appels et de manière plus harmonieuse.
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, celle-ci est interceptée par Ruby, jetée et a
est affectée à "something else"
.
En règle générale, ne faites pas cela, sauf dans des cas particuliers où vous savez vous n'avez pas besoin de vous inquiéter. Un exemple:
debugger rescue nil
La fonction 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 déclenche une exception. Maintenant théoriquement, vous ne devriez pas laisser le code de débogage traîner dans votre programme (pff! Personne ne le fait!) Mais vous voudrez peut-être le conserver pendant un certain temps pour une raison quelconque, mais ne pas exécuter continuellement votre débogueur.
Remarque:
Si vous avez exécuté le programme de quelqu'un d'autre qui détecte 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 en cause, puis exécutez kill -9 <PID>
.Si vous travaillez avec le programme de quelqu'un d'autre qui, pour une raison quelconque, est parsemé de ces blocs ignore-exception, le placer en haut de la ligne principale est une exception possible:
%W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
Cela force le programme à répondre aux signaux de terminaison normaux en terminant immédiatement, en contournant les gestionnaires d'exceptions, sans nettoyage. Donc, cela pourrait causer une perte de données ou similaire. Faites attention!
Si vous avez besoin de faire ceci:
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.
Disons que vous êtes dans une voiture (en cours d'exécution Ruby). Vous avez récemment installé un nouveau volant avec le système de mise à niveau par liaison radio (qui utilise eval
), mais vous ne saviez pas que l'un des programmeurs s'était trompé de syntaxe.
Vous êtes sur un pont et réalisez que vous vous dirigez un peu vers la rambarde, alors vous tournez à gauche.
def turn_left
self.turn left:
end
oops! C'est probablement Pas bon ™, heureusement, Ruby lève un SyntaxError
.
La voiture devrait s'arrêter immédiatement, n'est-ce pas?
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 capturée SyntaxError.
Info: Erreur enregistrée - Processus en cours.
Vous remarquez que quelque chose ne va pas et vous claquez sur les pauses d'urgence (^C
: Interrupt
)
bip bip
Avertissement: exception d'interruption capturée.
Info: Erreur enregistrée - Processus en cours.
Ouais - ça n'a pas beaucoup aidé. Vous êtes assez près du rail, vous devez donc garer la voiture (kill
ing: SignalException
).
bip bip
Avertissement: exception signalée exception capturé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 vous enfoncez dans le volant (l’airbag ne peut pas se déployer car vous n’avez pas arrêté le programme gracieusement - vous l’avez interrompu. ), et l’ordinateur à l’arrière de votre voiture s’incline sur le siège qui se trouve devant. Une canette de Coca à moitié pleine se répand sur les papiers. Les courses à l'arrière sont broyées 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 oui - parce que l'airbag ne s'est pas gonflé, vous êtes probablement blessé (viré, etc.).
Mais attendez! Il y a plus raisons pour lesquelles vous pourriez vouloir utiliser rescue Exception => e
!
Disons que vous êtes cette voiture et que vous voulez vous assurer que l'airbag se déploie si la voiture dépasse son moment d'arrêt de 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 pouvez intercepter Exception
niquement si vous relancez l'exception. Donc, une meilleure règle consiste à ne jamais avaler Exception
, et à toujours relancer l'erreur.
Toutefois, il est facile d'oublier l'ajout d'une opération de secours dans une langue telle que Ruby, et le fait de placer une déclaration d'assistance avant de soulever à nouveau une question donne l'impression que vous n'êtes pas assez sec. Et vous ne le faites pas voulez oublier la déclaration raise
. Et si vous le faites, bonne chance pour essayer de trouver cette erreur.
Heureusement, Ruby est génial, vous pouvez simplement utiliser le mot clé ensure
, ce qui garantit que le code est exécuté. Le mot-clé ensure
exécutera le code, peu importe ce qui se passe - si une exception est levée, si ce n'est pas le cas, la seule exception est si le monde se termine (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 pour laquelle vous devez utiliser rescue Exception => e
est si vous avez besoin d'accéder à l'exception, ou si vous souhaitez que le code ne s'exécute que sur une exception. Et rappelez-vous de relancer l'erreur. À chaque fois.
Remarque: Comme @Niall l'a fait remarquer, assurez-vous que toujours s'exécute. C’est bien, car parfois votre programme peut vous mentir et ne pas lancer d’exceptions, même en cas de problème. Avec des tâches critiques, telles que le gonflage des airbags, vous devez vous assurer que cela se produit quoi qu'il arrive. Pour cette raison, il est judicieux de vérifier chaque fois que la voiture s’arrête, qu’une exception soit levée ou non. Même si gonfler des airbags est une tâche peu commune dans la plupart des contextes de programmation, cette tâche est plutôt courante dans la plupart des tâches de nettoyage.
Ne pas rescue Exception => e
(et ne pas relancer l'exception) - ou vous pourriez quitter un pont.
Parce que cela capture toutes les exceptions. Il est peu probable que votre programme puisse récupérer aucun d'entre eux.
Vous ne devez gérer que les exceptions que 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 les exceptions est mauvais, ne faites pas ceci.
C'est un cas spécifique de la règle que vous ne devriez pas attraper aucun exception que vous ne savez pas comment gérer. Si vous ne savez pas comment vous en occuper, il est toujours préférable de laisser une autre partie du système s'en occuper.