Vol. 7 · № 01 · Un atelier RubyA Ruby workshop

raise / rescue / ensure, ou l'erreur est un objet raise / rescue / ensure, or the error is an object

Une exception est un objet levé par raise, qui déroule la pile jusqu'à un rescue du bon type. rescue trie par classe (un rescue nu en attrape trop) ; ensure garantit le nettoyage sans rien avaler. Trois mots, trois rôles : signaler, rattraper, nettoyer — qu'on ne mélange jamais. An exception is an object raised by raise, unwinding the stack to a rescue of the right type. rescue sorts by class (a bare rescue catches too much); ensure guarantees cleanup without swallowing. Three words, three roles: signal, catch, clean up — never to be mixed.

AudienceAudience
Dev qui veut maîtriser le mécanisme d'erreur, pas l'invoquer en aveugle Dev who wants to master the error mechanism, not invoke it blindly
Format
Self-paced
ChapitresChapters
5
Date
Mai 2026 May 2026
≈ 18 min ●●○○ raiserescueensure
SommaireContents ·
№ 01CadrageFraming3 min

Lever une exception, c'est envoyer un objet vers le haut de la pile.Raising an exception is sending an object up the stack.

Le Vol 7 ouvre le flux d'erreur. Et fidèle au fil de la série, une exception n'est pas une construction à part : c'est un objet (Vol 1), instance d'une classe d'erreur, qu'on lève et qu'on rattrape par son type. raise crée et propulse cet objet vers le haut de la pile d'appels, en déroulant chaque méthode au passage, jusqu'à ce qu'un rescue du bon type l'attrape. ensure, lui, garantit qu'un bout de code s'exécute quoi qu'il arrive — succès ou erreur. Trois mots, trois rôles : signaler, rattraper, nettoyer. Tout le mécanisme tient là.Vol 7 opens the error flow. And true to the series' thread, an exception isn't a separate construct: it's an object (Vol 1), an instance of an error class, raised and caught by its type. raise creates and propels that object up the call stack, unwinding each method along the way, until a rescue of the right type catches it. ensure guarantees a piece of code runs whatever happens — success or error. Three words, three roles: signal, catch, clean up. The whole mechanism sits there.

raise, rescue, ensure : signaler, rattraper, nettoyerraise, rescue, ensure: signal, catch, clean up
# une exception est un OBJET, levé puis rattrapé par sa CLASSE
err = ArgumentError.new("âge négatif")
err.is_a?(StandardError)   # => true   c'est un objet, dans une hiérarchie
err.message                # => "âge négatif"

def set_age(n)
  raise ArgumentError, "âge négatif" if n < 0   # lever : on signale
  @age = n
end

begin
  set_age(-1)
rescue ArgumentError => e   # rattraper PAR TYPE
  puts "refusé : #{e.message}"
ensure
  puts "toujours exécuté"    # nettoyer : quoi qu'il arrive
end
L'exception remonte la pileThe exception climbs the stack
raise
set_ageset_age
↑
?
appelantcaller
↑
rescue
le bon typethe right type

L'objet-erreur déroule la pile : chaque méthode traversée est abandonnée jusqu'à ce qu'un rescue du bon type l'attrape. Si aucun ne l'attrape, il atteint le sommet et le programme s'arrête. ensure court à chaque niveau traversé.The error object unwinds the stack: each method crossed is abandoned until a rescue of the right type catches it. If none does, it reaches the top and the program halts. ensure runs at every level crossed.

Le réflexe du volumeThe volume's reflex

« Est-ce une condition vraiment exceptionnelle (raise), ou un flux normal déguisé en erreur ? » Avant de lever, demande-toi si le cas est anormal — une donnée invalide, un état impossible — ou simplement un résultat attendu (clé absente, fin de fichier) qu'une valeur de retour exprimerait mieux."Is this a truly exceptional condition (raise), or normal flow disguised as an error?" Before raising, ask whether the case is abnormal — invalid data, an impossible state — or just an expected outcome (missing key, end of file) a return value would express better.

Une exception est un objet.An exception is an object.

Comme tout en Ruby. raise lève une instance d'une classe d'erreur ; rescue la rattrape en testant sa classe (le duck typing des erreurs, № 2). e.message, e.backtrace sont des messages envoyés à cet objet. Comprendre ça désamorce toute la « magie » : il n'y a pas de système d'exception à part, juste des objets qu'on lève et qu'on trie par type le long de la pile.Like everything in Ruby. raise raises an instance of an error class; rescue catches it by testing its class (the duck typing of errors, № 2). e.message, e.backtrace are messages sent to that object. Grasping this defuses all the 'magic': there's no separate exception system, just objects you raise and sort by type along the stack.

№ 02La surpriseThe surprise4 min

rescue trie par type — et un rescue nu en attrape trop.rescue sorts by type — and a bare rescue catches too much.

rescue n'attrape pas « les erreurs » en bloc : il teste la classe de l'objet-exception, comme un case sur son type. rescue KeyError n'attrape que KeyError et ses sous-classes ; tout le reste continue de remonter. C'est le duck typing appliqué aux erreurs (Vol 3) : on trie par ce que l'erreur est. Le piège du débutant est le rescue nu — sans classe, il vaut rescue StandardError et ratisse donc presque tout. Un timeout réseau et un bug de programmation finissent dans le même filet, traités pareil. Nommer le type qu'on attrape, c'est décider quelle erreur on sait gérer, et laisser passer celles qu'on ne sait pas.rescue doesn't catch 'errors' wholesale: it tests the exception object's class, like a case on its type. rescue KeyError catches only KeyError and its subclasses; everything else keeps climbing. It's duck typing applied to errors (Vol 3): you sort by what the error is. The beginner's trap is the bare rescue — with no class, it means rescue StandardError and so rakes in almost everything. A network timeout and a programming bug land in the same net, handled the same. Naming the type you catch is deciding which error you know how to handle, and letting through the ones you don't.

Un seul filet pour deux erreurs sans rapportOne net for two unrelated errors
def fetch_user(id)
  row = db.query(id)          # peut lever Timeout::Error (réseau)
  User.new(row.fetch(:name)) # peut lever KeyError (donnée incomplète)
rescue => e                   # un rescue NU = rescue StandardError
  Rails.logger.warn(e)        # … attrape les DEUX, et tout le reste
  nil                         # un timeout réseau et un bug de données
end                           # finissent traités pareil : noyés en nil

Le rescue nu vaut rescue StandardError : il attrape le Timeout (réseau, qu'on pourrait retenter) et le KeyError (donnée invalide, une vraie faute) et n'importe quel NoMethodError dû à un bug. Tout devient nil, indistinctement — on a perdu l'information que portait le type de l'erreur.The bare rescue means rescue StandardError: it catches the Timeout (network, retryable) and the KeyError (invalid data, a real fault) and any NoMethodError from a bug. Everything becomes nil, indistinctly — you've lost the information the error's type carried.

Un rescue nu n'est pas « prudent » : c'est un filet qui attrape les bugs avec les erreurs, et les traite pareil.A bare rescue isn't "careful": it's a net that catches bugs along with errors, and treats them alike.
№ 03La mécaniqueThe mechanics5 min

ensure court toujours, retry relance, raise nu re-lève.ensure always runs, retry retries, bare raise re-raises.

L'anatomie complète tient en cinq mots-clés, chacun avec un rôle net. begin délimite la zone à risque ; rescue rattrape par type ; else court si — et seulement si — rien n'a été levé ; ensure court dans tous les cas, c'est la garantie de nettoyage ; retry relance le begin depuis le début, pour les erreurs transitoires. Et raise sans argument, dans un rescue, re-lève l'exception courante telle quelle : on journalise, puis on laisse remonter, sans rien avaler. Une méthode ou un bloc font office de begin implicite — pas besoin de l'écrire. Cinq mots, et le flux d'erreur n'a plus de zone d'ombre.The full anatomy is five keywords, each with a clear role. begin marks the risky zone; rescue catches by type; else runs if — and only if — nothing was raised; ensure runs in every case, the cleanup guarantee; retry restarts the begin from the top, for transient errors. And raise with no argument, inside a rescue, re-raises the current exception as is: you log, then let it climb, swallowing nothing. A method or block acts as an implicit begin — no need to write it. Five words, and the error flow has no blind spots.

L'anatomie : begin / rescue / else / ensure / retryThe anatomy: begin / rescue / else / ensure / retry
begin
  attempt                  # le code à risque
rescue Timeout::Error
  retry if (tries ||= 0; tries += 1) < 3   # RETRY : relance le begin
rescue KeyError => e
  log(e)
  raise                    # RAISE nu : re-lève LA MÊME erreur, sans l'avaler
else
  commit                   # ELSE : court seulement si AUCUNE exception
ensure
  connection.close         # ENSURE : court TOUJOURS, succès comme échec
end
Une méthode est son propre beginA method is its own begin
# pas besoin de begin : une méthode (ou un bloc) est son propre begin
def charge(card)
  gateway.bill(card)
rescue Gateway::Declined => e
  notify(e)
  raise                    # on a journalisé, mais l'erreur doit remonter
ensure
  audit_log.write(card.id) # toujours, qu'on ait payé ou échoué
end
Journaliser puis re-lever.Log then re-raise.

Le couple le plus utile du numéro : rescue pour réagir (journaliser, notifier, incrémenter une métrique), puis raise nu pour re-lever la même exception — backtrace intact — et la laisser remonter vers qui saura vraiment la traiter. C'est l'opposé de l'avaler. Et ensure reste à part : il ne gère rien, il garantit un nettoyage. Distinguer « réagir » (rescue), « relever » (raise) et « nettoyer » (ensure) suffit à écrire 95 % du flux d'erreur correctement.The issue's most useful pairing: rescue to react (log, notify, bump a metric), then bare raise to re-raise the same exception — backtrace intact — and let it climb to whoever truly handles it. It's the opposite of swallowing. And ensure stays apart: it handles nothing, it guarantees cleanup. Telling apart "react" (rescue), "re-raise" (raise) and "clean up" (ensure) is enough to write 95% of the error flow correctly.

Deux pièges de retry à nommer en passant : sans compteur, il boucle à l'infini si l'erreur persiste ; et il relance tout le begin, donc gare aux effets déjà appliqués avant le point de rupture. retry est fait pour le transitoire — un timeout, une latence réseau — borné par un nombre d'essais, jamais pour réessayer une opération vouée à échouer.Two retry traps worth naming in passing: with no counter, it loops forever if the error persists; and it restarts the whole begin, so beware effects already applied before the breaking point. retry is for the transient — a timeout, network latency — bounded by an attempt count, never to re-run an operation doomed to fail.

№ 04Le piègeThe trap4 min

Nettoyer dans rescue : on saute le cas normal, on avale l'erreur.Cleaning up in rescue: you skip the normal case, you swallow the error.

Le contre-exemple du numéro naît d'une confusion des rôles. On veut fermer un fichier, alors on met file.close dans le rescue — ou on le duplique entre le chemin heureux et le rescue. Mais rescue ne court que sur erreur : si parse réussit puis quelque chose lève après, ou si on oublie une branche, la fermeture manque. Et nettoyer dans rescue oblige souvent à rattraper l'exception, donc à l'avaler. Le nettoyage n'est pas une réaction à l'erreur, c'est une garantie inconditionnelle : sa place est ensure, qui court dans tous les cas sans rien rattraper. Mieux encore quand elle existe : la forme à bloc, qui ferme la ressource pour toi.The issue's counter-example springs from confusing roles. You want to close a file, so you put file.close in the rescue — or duplicate it between the happy path and the rescue. But rescue runs only on error: if parse succeeds then something raises later, or you miss a branch, the close is skipped. And cleaning up in rescue often forces you to catch the exception, hence to swallow it. Cleanup isn't a reaction to the error, it's an unconditional guarantee: its place is ensure, which runs in every case without catching anything. Better still where it exists: the block form, which closes the resource for you.

rescue ne court que sur erreur — et avale l'exceptionrescue runs only on error — and swallows the exception
# Le piège : mettre le nettoyage dans rescue
def process(path)
  file = File.open(path)
  data = parse(file)       # si parse lève…
  file.close               # … cette ligne est SAUTÉE → descripteur fuité
  data
rescue => e
  file.close               # on ferme ici, mais on vient d'AVALER l'erreur
  nil                      # parse a échoué — l'appelant ne le saura jamais
end
# deux bugs en un : la fermeture manque sur le chemin heureux interrompu,
# et l'exception est noyée en nil
ensure, ou la forme à blocensure, or the block form
# ensure : le nettoyage court TOUJOURS, et n'avale rien
def process(path)
  file = File.open(path)
  parse(file)              # si ça lève, l'erreur REMONTE intacte…
ensure
  file&.close              # … mais ceci s'exécute quand même (succès ou échec)
end

# encore mieux — la forme à bloc ferme pour toi, sans ensure à écrire :
def process(path)
  File.open(path) { |file| parse(file) }   # close garanti à la sortie du bloc
end
Le contre-exemple obligatoire.The mandatory counter-example.

Range chaque intention dans le bon mot : rescue pour réagir à une erreur précise, ensure pour garantir un nettoyage quoi qu'il arrive. Le nettoyage ne doit jamais dépendre de la présence d'une erreur — un fichier, une connexion, un verrou se libèrent sur succès comme sur échec. Mettre close dans rescue, c'est le lier à l'erreur (donc l'oublier sur le chemin heureux) et être tenté d'avaler l'exception pour « finir proprement ». ensure sépare les deux ; la forme à bloc (File.open do … end) supprime même la question.File each intention under the right word: rescue to react to a specific error, ensure to guarantee cleanup whatever happens. Cleanup must never depend on an error being present — a file, a connection, a lock release on success as well as failure. Putting close in rescue ties it to the error (so you skip it on the happy path) and tempts you to swallow the exception to "finish cleanly". ensure separates the two; the block form (File.open do … end) removes the question entirely.

Le nettoyage n'est pas une réaction à l'erreur — c'est une promesse tenue dans tous les cas. Sa place est ensure, jamais rescue.Cleanup isn't a reaction to the error — it's a promise kept in every case. Its place is ensure, never rescue.
№ 05Bilan & éditoWrap-up & editorial2 min

Signaler, rattraper par type, nettoyer — trois mots, trois rôles.Signal, catch by type, clean up — three words, three roles.

Une exception est un objet levé par raise, qui déroule la pile jusqu'à un rescue du bon type. rescue trie par classe — un rescue nu attrape StandardError, donc trop. ensure garantit le nettoyage dans tous les cas et n'avale rien ; raise nu re-lève l'erreur après l'avoir journalisée ; retry relance le begin, borné, pour le transitoire. La règle qui tient tout : range chaque intention dans le bon mot — réagir (rescue), relever (raise), nettoyer (ensure) — et ne mélange jamais les trois.An exception is an object raised by raise, unwinding the stack to a rescue of the right type. rescue sorts by class — a bare rescue catches StandardError, hence too much. ensure guarantees cleanup in every case and swallows nothing; bare raise re-raises the error after logging it; retry restarts the begin, bounded, for the transient. The rule that holds it all: file each intention under the right word — react (rescue), re-raise (raise), clean up (ensure) — and never mix the three.

Le motThe wordSon rôleIts roleLe réflexeThe reflex
raiseraiseLever un objet-erreur, dérouler la pile.Raise an error object, unwind the stack.Pour l'exceptionnel ; nu dans rescue = re-lever.For the exceptional; bare in rescue = re-raise.
rescuerescueRattraper par type (=== sur la classe).Catch by type (=== on the class).Nomme la classe ; jamais un rescue nu fourre-tout.Name the class; never a catch-all bare rescue.
ensureensureNettoyer, toujours, sans rien rattraper.Clean up, always, catching nothing.Le nettoyage va ici, jamais dans rescue.Cleanup goes here, never in rescue.
retryretryRelancer le begin, pour le transitoire.Restart the begin, for the transient.Toujours borné par un compteur d'essais.Always bounded by an attempt counter.
Une exception est un objet qu'on trie par type le long de la pile — pas une magie à part, juste le « tout est message » appliqué à l'erreur.An exception is an object you sort by type along the stack — not a separate magic, just "everything is a message" applied to error.
Mot de l'éditeurFrom the editor

Ouverture du Vol 7 : l'erreur est un objet, pas une exception au modèle.Opening Vol 7: the error is an object, not an exception to the model.

Le flux d'erreur semble être un monde à part — un système parallèle de raise et de rescue. Ce numéro montre qu'il n'en est rien : une exception est un objet (Vol 1), levé puis trié par sa classe (le duck typing du Vol 3, appliqué aux erreurs). raise, rescue, ensure ne sont pas trois incantations mais trois rôles nets — signaler, rattraper, nettoyer — qu'on confond à ses dépens. La discipline du volume commence ici : ne pas traiter le flux d'erreur comme un fourre-tout, mais comme un envoi de message qu'on dirige par type, vers qui sait vraiment répondre.The error flow looks like a world apart — a parallel system of raise and rescue. This issue shows it isn't: an exception is an object (Vol 1), raised then sorted by its class (Vol 3's duck typing, applied to errors). raise, rescue, ensure aren't three incantations but three clear roles — signal, catch, clean up — that you confuse at your peril. The volume's discipline starts here: don't treat the error flow as a catch-all, but as a message you route by type, toward whoever truly answers.

Prochain numéro : concevoir sa hiérarchie d'erreurs. On a rattrapé par type ; le № 2 dessine l'arbre des types — pourquoi on hérite de StandardError et jamais d'Exception (sous peine d'avaler le Ctrl-C de l'utilisateur), et comment des classes d'erreur maison disent ce qui a échoué bien mieux qu'un message de chaîne. La puissance du rescue par type ne vaut que par la qualité de l'arbre qu'on lui donne à trier.Next issue: designing your error hierarchy. We caught by type; № 2 draws the type tree — why you inherit from StandardError and never Exception (lest you swallow the user's Ctrl-C), and how custom error classes say what failed far better than a string message. The power of rescue-by-type is only as good as the tree you give it to sort.