Ruby Language
Catching Exceptions avec Begin / Rescue
Recherche…
Un bloc de gestion des erreurs de base
Faisons une fonction pour diviser deux nombres, cela fait très confiance à ses entrées:
def divide(x, y)
return x/y
end
Cela fonctionnera très bien pour beaucoup d'entrées:
> puts divide(10, 2)
5
Mais pas tout
> puts divide(10, 0)
ZeroDivisionError: divided by 0
> puts divide(10, 'a')
TypeError: String can't be coerced into Fixnum
Nous pouvons réécrire la fonction en encapsulant l'opération de division risquée dans un bloc begin... end
pour vérifier les erreurs et utiliser une clause rescue
pour générer un message et renvoyer nil
s'il y a un problème.
def divide(x, y)
begin
return x/y
rescue
puts "There was an error"
return nil
end
end
> puts divide(10, 0)
There was an error
> puts divide(10, 'a')
There was an error
Enregistrement de l'erreur
Vous pouvez enregistrer l'erreur si vous souhaitez l'utiliser dans la clause de rescue
def divide(x, y)
begin
x/y
rescue => e
puts "There was a %s (%s)" % [e.class, e.message]
puts e.backtrace
end
end
> divide(10, 0)
There was a ZeroDivisionError (divided by 0)
from (irb):10:in `/'
from (irb):10
from /Users/username/.rbenv/versions/2.3.1/bin/irb:11:in `<main>'
> divide(10, 'a')
There was a TypeError (String can't be coerced into Fixnum)
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/workspace.rb:87:in `eval'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/workspace.rb:87:in `evaluate'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/context.rb:380:in `evaluate'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb.rb:489:in `block (2 levels) in eval_input'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb.rb:623:in `signal_status'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb.rb:486:in `block in eval_input'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/ruby-lex.rb:246:in `block (2 levels) in each_top_level_statement'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/ruby-lex.rb:232:in `loop'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/ruby-lex.rb:232:in `block in each_top_level_statement'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/ruby-lex.rb:231:in `catch'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/ruby-lex.rb:231:in `each_top_level_statement'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb.rb:485:in `eval_input'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb.rb:395:in `block in start'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb.rb:394:in `catch'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb.rb:394:in `start'
/Users/username/.rbenv/versions/2.3.1/bin/irb:11:in `<main>'
Vérification des erreurs différentes
Si vous voulez faire des choses différentes en fonction du type d'erreur, utilisez plusieurs clauses de rescue
, chacune avec un type d'erreur différent en tant qu'argument.
def divide(x, y)
begin
return x/y
rescue ZeroDivisionError
puts "Don't divide by zero!"
return nil
rescue TypeError
puts "Division only works on numbers!"
return nil
end
end
> divide(10, 0)
Don't divide by zero!
> divide(10, 'a')
Division only works on numbers!
Si vous souhaitez enregistrer l'erreur à utiliser dans le bloc de rescue
:
rescue ZeroDivisionError => e
Utilisez une clause de rescue
sans argument pour intercepter les erreurs d'un type non spécifié dans une autre clause de rescue
.
def divide(x, y)
begin
return x/y
rescue ZeroDivisionError
puts "Don't divide by zero!"
return nil
rescue TypeError
puts "Division only works on numbers!"
return nil
rescue => e
puts "Don't do that (%s)" % [e.class]
return nil
end
end
> divide(nil, 2)
Don't do that (NoMethodError)
Dans ce cas, essayer de diviser nil
par 2 n'est pas un ZeroDivisionError
ou un TypeError
, donc il est géré par la clause de rescue
par défaut, qui imprime un message pour nous informer qu'il s'agit d'une NoMethodError
.
Réessayer
Dans une clause de rescue
, vous pouvez retry
pour exécuter la clause begin
, probablement après avoir modifié les circonstances à l'origine de l'erreur.
def divide(x, y)
begin
puts "About to divide..."
return x/y
rescue ZeroDivisionError
puts "Don't divide by zero!"
y = 1
retry
rescue TypeError
puts "Division only works on numbers!"
return nil
rescue => e
puts "Don't do that (%s)" % [e.class]
return nil
end
end
Si nous transmettons des paramètres dont nous savons qu’ils provoquent une TypeError
, la clause begin
est exécutée (marquée ici en imprimant "About to divide") et l’erreur est interceptée comme précédemment, et nil
est renvoyé:
> divide(10, 'a')
About to divide...
Division only works on numbers!
=> nil
Mais si nous transmettons des paramètres qui provoquent une ZeroDivisionError
, la clause begin
est exécutée, l'erreur est interceptée, le diviseur passe de 0 à 1, puis retry
l'exécution du bloc de begin
(à partir du haut), maintenant avec un différent y
. La deuxième fois, il n'y a pas d'erreur et la fonction renvoie une valeur.
> divide(10, 0)
About to divide... # First time, 10 ÷ 0
Don't divide by zero!
About to divide... # Second time 10 ÷ 1
=> 10
Vérification de l'absence de toute erreur
Vous pouvez utiliser une clause else
pour le code qui sera exécuté si aucune erreur n'est générée.
def divide(x, y)
begin
z = x/y
rescue ZeroDivisionError
puts "Don't divide by zero!"
rescue TypeError
puts "Division only works on numbers!"
return nil
rescue => e
puts "Don't do that (%s)" % [e.class]
return nil
else
puts "This code will run if there is no error."
return z
end
end
La clause else
ne s'exécute pas si une erreur transfère le contrôle à l'une des clauses de rescue
:
> divide(10,0)
Don't divide by zero!
=> nil
Mais si aucune erreur n'est déclenchée, la clause else
s'exécute:
> divide(10,2)
This code will run if there is no error.
=> 5
Notez que la clause else
ne sera pas exécutée si vous revenez de la clause begin
def divide(x, y)
begin
z = x/y
return z # Will keep the else clause from running!
rescue ZeroDivisionError
puts "Don't divide by zero!"
else
puts "This code will run if there is no error."
return z
end
end
> divide(10,2)
=> 5
Code qui doit toujours s'exécuter
Utilisez une clause ensure
si vous souhaitez toujours exécuter du code.
def divide(x, y)
begin
z = x/y
return z
rescue ZeroDivisionError
puts "Don't divide by zero!"
rescue TypeError
puts "Division only works on numbers!"
return nil
rescue => e
puts "Don't do that (%s)" % [e.class]
return nil
ensure
puts "This code ALWAYS runs."
end
end
La clause ensure
sera exécutée en cas d'erreur:
> divide(10, 0)
Don't divide by zero! # rescue clause
This code ALWAYS runs. # ensure clause
=> nil
Et quand il n'y a pas d'erreur:
> divide(10, 2)
This code ALWAYS runs. # ensure clause
=> 5
La clause assure est utile lorsque vous voulez vous assurer, par exemple, que les fichiers sont fermés.
Notez que, contrairement à la clause else
, la clause ensure
est exécutée avant que la clause begin
ou rescue
renvoie une valeur. Si la clause ensure
a un return
qui remplace la valeur de return
de toute autre clause!