
Comment gérer les exceptions avec `begin/rescue`
Ne laissez plus une erreur inattendue faire crasher votre application Ruby. Apprenez à utiliser la structure `begin/rescue/end` pour intercepter et gérer élégamment les exceptions, comme les erreurs de fichier ou de réseau.
Que se passe-t-il lorsqu'une erreur imprévue survient ?
Par défaut, lorsqu'une opération impossible est tentée dans votre code, comme diviser un nombre par zéro ou essayer de lire un fichier qui n'existe pas, Ruby lève ce qu'on appelle une exception. Une exception est un objet qui signale qu'une condition d'erreur s'est produite. Si cette exception n'est pas gérée, le comportement par défaut est simple et brutal : votre programme s'arrête net et affiche un message d'erreur (la fameuse 'stack trace').
Pensez à une exception comme à une alarme incendie dans un bâtiment. Quand elle se déclenche, tout s'arrête et une procédure d'urgence doit être suivie. Si aucune procédure n'est prévue (le code n'est pas géré), c'est la panique et l'évacuation (le crash du programme). La gestion des exceptions consiste à définir cette procédure d'urgence pour que le programme puisse réagir de manière contrôlée, sans s'arrêter complètement.
Comment intercepter une exception avec la syntaxe `begin/rescue` ?
Pour "attraper" une exception et l'empêcher de faire crasher votre programme, Ruby fournit la structure begin...rescue...end. C'est le mécanisme de base pour la gestion des erreurs.
Voici sa structure la plus simple :
- Le bloc
begin: vous placez ici le code qui est susceptible de lever une exception. Ruby va tenter de l'exécuter. - Le bloc
rescue: si une exception est levée dans le blocbegin, Ruby arrête immédiatement l'exécution de ce bloc et saute directement au code contenu dans le blocrescue. - Le mot-clé
end: il marque la fin de la structure.
Prenons l'exemple d'une division par zéro qui, normalement, fait planter le script :
begin
puts "Je vais tenter une opération risquée..."
resultat = 10 / 0 # Cette ligne lève une ZeroDivisionError
puts "Cette ligne ne sera jamais atteinte."
rescue
puts "Oups ! Une erreur s'est produite, mais le programme continue."
end
puts "Le programme est arrivé à la fin sans crasher."
#=> Je vais tenter une opération risquée...
#=> Oups ! Une erreur s'est produite, mais le programme continue.
#=> Le programme est arrivé à la fin sans crasher.Comme vous le voyez, le programme n'a pas planté. Le code dans le bloc rescue a été exécuté et le script a pu continuer jusqu'à la fin.
Pourquoi est-il crucial de spécifier le type d'erreur à intercepter ?
Utiliser rescue sans préciser le type d'erreur, comme dans l'exemple précédent, est généralement une mauvaise pratique. C'est ce qu'on appelle un "rescue à nu" (bare rescue). Pourquoi ? Parce qu'il intercepte toutes les exceptions, y compris les fautes de frappe dans votre code (NoMethodError) ou des erreurs système plus graves. Cela peut masquer des bugs que vous devriez en fait corriger.
Conseil de pro : soyez toujours aussi spécifique que possible dans vos clauses rescue. La bonne pratique consiste à spécifier la classe de l'exception que vous souhaitez intercepter. Vous pouvez également capturer l'objet exception dans une variable pour inspecter son message.
begin
# ... code risqué ...
resultat = 10 / 0
rescue ZeroDivisionError => e
puts "Erreur interceptée ! Il est impossible de diviser par zéro."
puts "Message d'erreur système : #{e.message}"
end
#=> Erreur interceptée ! Il est impossible de diviser par zéro.
#=> Message d'erreur système : divided by 0Ici, nous n'attrapons que les erreurs de type ZeroDivisionError. Si une autre erreur, comme une TypeError, survenait, elle ne serait pas interceptée par ce bloc, ce qui est le comportement souhaité. La variable e contient l'instance de l'exception, nous permettant d'accéder à des informations utiles comme son message.
Mise en situation : gérer l'ouverture d'un fichier inexistant
Un cas d'usage très courant pour la gestion d'exceptions est la manipulation de fichiers. Que se passe-t-il si vous essayez de lire un fichier qui a été supprimé ou qui n'a jamais existé ? Ruby lève une exception Errno::ENOENT (Error Number: No Entity).
Dans une application réelle, vous ne voulez pas que votre programme s'arrête brutalement. Vous préférez afficher un message clair à l'utilisateur. Voici comment le gérer proprement :
filename = "rapport_inexistant.txt"
begin
file_content = File.read(filename)
puts "Contenu du fichier :"
puts file_content
rescue Errno::ENOENT
puts "Désolé, le fichier '#{filename}' n'a pas été trouvé."
puts "Veuillez vérifier le nom et l'emplacement du fichier."
endCette approche transforme une erreur système abrupte en une expérience utilisateur contrôlée et informative. C'est la marque d'un logiciel robuste et professionnel.
Aller plus loin : quand utiliser `else` et `ensure` ?
La structure begin/rescue peut être complétée par deux clauses optionnelles : else et ensure.
- Le bloc
else: son code n'est exécuté que si aucune exception n'a été levée dans le blocbegin. C'est utile pour séparer le code qui peut échouer du code qui ne doit s'exécuter qu'en cas de succès. - Le bloc
ensure: son code est exécuté toujours, qu'une exception ait été levée ou non, et que cette exception ait été interceptée ou non. Il est parfait pour les opérations de nettoyage, comme fermer un fichier ou une connexion réseau, pour garantir que les ressources sont toujours libérées.
Voici une structure complète pour illustrer le flux :
file = nil
begin
file = File.open("mon_fichier.txt", "w")
file.puts "Hello"
# Décommentez la ligne ci-dessous pour simuler une erreur
# raise "Une erreur s'est produite !"
rescue => e
puts "Une erreur a été gérée : #{e.message}"
else
puts "Le fichier a été écrit avec succès."
ensure
if file
file.close
puts "Le fichier a été fermé."
end
endMaîtriser begin/rescue/else/ensure vous donne un contrôle total sur la gestion des erreurs et des ressources, une compétence indispensable pour écrire des applications Ruby fiables.