Ruby: gérer les erreurs à l'aide des exceptions
Par Bounga le dimanche, 3 décembre 2006, 19:47 - Documentations - Lien permanent
Lorsqu'on développe à l'aide d'un langage objet, comme Ruby, il est courant d'avoir recourt aux exceptions pour traiter les erreurs. Malheureusement de nombreux développeurs ne sont pas à l'aise avec ce type de traitement et continuent à utiliser les tests conditionnels (if / case / etc) pour traiter les erreurs inattendues alors que ces tests sont prévus pour la logique du code plutôt que pour la gestion des erreurs.
Nous allons donc voir pourquoi et comment les exceptions peuvent simplifier votre code.
Les exceptions ? Qu'est-ce que c'est ?
Une exception est un type spécial d'objet, une instance de la classe Exception ou un descendant de cette classe. Lancer une exception (raise) sert à interrompre le chemin normal d'exécution du programme pour traiter le problème ou quitter le programme.
Le choix par l'interpréteur Ruby de quitter ou non le programme dépendra du fait que vous avez écrit ou non un bloc rescue pour le morceau de code qui a levé l'exception. Si vous n'avez pas fourni ce bloc, le programme quittera sinon l'interpréteur entrera dans votre bloc rescue pour prendre les mesures nécessaires.
Un exemple simple pourrait être : 1/0 qui lévera une exception du type ZeroDivisionError puisque vous avez tenté de diviser un nombre par zéro ce qui mathématiquement impossible. ZeroDivisionError est le nom d'une classe particulière d'exception qui hérite de la classe Exception.
Voici une liste des exceptions qu'on rencontre le plus souvent. Elles descendent toutes de la classe Exception.
| Nom de l'exception | Raison | Exemple |
|---|---|---|
RuntimeError |
Levé par défaut par raise |
raise |
NoMethodError |
Un message inconnu a été envoyé à un objet | Object.new.methode_inconnue |
NameError |
Un mot n'a pu être résolu en tant que variable ou nom de méthode | variable_inconnue |
IOError |
Causé par la lecture d'un flux fermé, l'écriture sur un flux en lecture seule, etc | STDIN.puts("Impossible d'écrire sur STDIN") |
TypeError |
Une méthode a reçue un argument qu'elle ne peut pas gérer | 3 + "un chaîne ..." |
ArgumentError |
Levé par l'utilisation d'un nombre incorrect d'arguments | def m(x); end; m(1, 2, 3) |
rescue est là pour vous aider !
Comme je vous l'ai expliqué avant, lever une exception ne revient pas forcément à quitter le programme. Vous pouvez traiter les exceptions en solutionnant les problèmes ce qui vous permet de continuer l'exécution du programme. Pour cela vous devez utiliser le mot-clef rescue.
Deux méthodes vous permettent de gérer vos exceptions :
- Entourer le code à protéger des mots-clefs
begin/end - Protéger une méthode compléte en ajoutant un bloc
rescueà la fin de la définition de cette méthode
print "Veuillez entrer un chiffre : "
n = gets.to_i
begin
resultat = 100 / n
rescue
puts "Avez-vous tenté une division par zéro ?"
exit
end
puts "100/#{n} vaut #{resultat}."
Si vous tentez d'exécuter ce morceau de code en entrant, le programme va lever une exception de type ZeroDivisionError, notre code étant dans un bloc begin/rescue, la bloc rescue sera appelé et notre message d'erreur affiché. Si vous entrez une valeur correcte le code sera exécuté normalement et le bloc rescue ne sera pas interprété.
Vous pouvez également utiliser une clause rescue qui ne gérera qu'un type précis d'exception. Dans notre cas, nous aurions pu utiliser rescue ZeroDivisionError. Dans ce cas les autres exceptions ne seront pas attrapées par notre bloc rescue.
Et si je veux lever une exception dans mon code ?
Utiliser le système d'exception vous permettra d'avoir un code plus flexible et compact. Il est donc tout naturel que vous puissiez lever des exceptions dans votre propre code et même créer de nouvelles exceptions.
Pour lever une exception, il vous suffit d'utiliser le mot-clef raise. Vous pouvez éventuellement lui passer le nom de l'exception que vous voulez lever et une chaîne qui sera utilisée comme message d'erreur.
Voici un exemple d'utilisation :
def methode_explosive(x) raise ArgumentError, "Le chiffre est supérieur à 10 !" unless x < 10 end methode_explosive(20)
On obtient donc : ArgumentError: Le chiffre est supérieur à 10 !
Vous pouvez bien évidemment gérer cette erreur :
begin methode_explosive(20) rescue ArgumentError puts "Ce chiffre est trop grand !" end
On obtient maintenant : Ce chiffre est trop grand !
Ici on ne traite que ArgumentError pour être sûr de ne faire notre traitement d'erreur que dans le cas précis d'une erreur d'argument. Les autres types d'erreurs ne seront pas traités et ne seront donc pas masqués par notre code de traitement des erreurs. Vous aurez compris qu'il faut éviter les rescue généralistes sous peine de vous retrouver avec des bugs introuvables puisque masqués.
Notez qu'il vous est possible de relancer une exception que vous avez traité. De cette façon, vous pourrez transmettre l'exception aux autres blocs rescue pour plus de traitement.
Dans notre exemple, on pourrait faire :
begin methode_explosive(20) rescue ArgumentError puts "Ce chiffre est trop grand !" raise end
On obtient maintenant :
Ce chiffre est trop grand !
ArgumentError: Le chiffre est supérieur à 10 !
D'accord, mais comment je crée mes propres exceptions ?
Il peut vous arriver d'avoir à créer vos propres exceptions pour plus de clarté (c'est ce qui est fait très massivement dans Rails par exemple). Il vous faudra donc créer une nouvelle classe d'exception héritant d'Exception ou de l'une de ses sous-classes.
En pratique, voilà comment on s'y prend :
class MonException < Exception end raise MonException, "Eh ! C'est ma nouvelle exception !"
L'un des avantages de créer vos propres classes d'exceptions, en plus de la clarté, est de pouvoir appeler vos exceptions par leur nom :
begin
puts "On va lever notre nouvelle exception"
raise MonException
rescue MonException => e
puts "Une exception a été levée : #{e}"
end
inattendues
Ce qui nous donnera :
On va lever notre nouvelle exception
Une exception a été levée : MonException
Conclusion
Vous aurez donc compris qu'utiliser les exceptions vous permettra d'avoir un code plus flexible, qui sera capable de gérer des erreurs parfois difficilement récupérables autrement. Créer vos propres exceptions vous permettra également de mieux documenter votre code et d'afficher des messages plus précis aux utilisateurs. Alors n'hésitez plus et mettez vous aux exceptions 
Commentaires
o/
Vive les exceptions xD
Très bon article !
Super article donc par exemple au lieu de faire ca
@saved = @jeton.save
if @saved
respond_to do |type|
type.html { type.html { flash[:notice] = "Votre jeton a été soumis, vous allez recevoir un email de confirmation"; redirect_to jetons_url } }
type.js
end
else
respond_to do |type|
type.html {render :action => new }
type.js
end
end
Je pourrais remplace le code par ca
@jeton.save!
respond_to do |type|
type.html { type.html { flash[:notice] = "Votre jeton a été soumis, vous allez recevoir un email de confirmation"; redirect_to jetons_url } }
type.js
end
rescue
respond_to do |type|
type.html {render :action => new }
type.js
end
?
Bolo: oui, certainement en spécifiant le type d'exception que tu veux attraper pour ne pas couvrir une autre erreur mais c'est l'idée
Sache tout de même que dans ton exemple précis, je préfère utiliser
if @jeton.saveparce que ici c'est un comportement très facilement prévisible. Les exceptions sont plutôt destinées aux erreurs inattendues comme par exemple l'ouverture d'un fichier qui foire.Toujours est-il que tu as compris le principe