Vol. 7 — № 03
The Go Loop · KiosqueNewsstand
Blog  
Un atelier Go · Édition d'ApprentissageA Go Workshop · Learning Edition

recover sans masquer recover without masking

recover transforme un panic en valeur — à la frontière d'une requête ou d'une goroutine, pas partout. Le danger n'est pas de paniquer, c'est d'avaler un bug en silence. Re-paniquer, nommer le retour, ne jamais cacher l'invariant rompu. recover turns a panic into a value — at the boundary of a request or a goroutine, not everywhere. The danger isn't panicking, it's swallowing a bug silently. Re-panic, name the return, never hide the broken invariant.

AudienceAudience
Dev tenté de mettre recover partout, ou dont une goroutine fait tomber tout le serveur Dev tempted to put recover everywhere, or whose goroutine takes down the whole server
Format
Self-paced
ChapitresChapters
5
Date
Fév 2028 Feb 2028
≈ 15 min ●●●○ recoverFrontière

Chapitre 1 en accès libre — la suite (ch. 2 à 5) est réservée. Chapter 1 free to read — the rest (ch. 2–5) is members-only.

№ 01CadrageFraming3 min

recover n'est pas un try/catch qu'on saupoudre. C'est une digue, qu'on bâtit à une frontière précise.recover isn't a try/catch you sprinkle. It's a dike, built at a precise boundary.

Le volume 4 a montré la mécanique de recover : qu'il ne fonctionne que dans un defer, qu'il intercepte un panic en train de dérouler la pile, et qu'il convertit cette rupture en une valeur qu'on peut inspecter. Ce numéro ne revient pas sur cette mécanique — il répond à la question qu'elle laisse ouverte, et qui sépare le code robuste du code dangereux : où place-t-on un recover ? La tentation, surtout quand on vient d'un langage à exceptions, est de le traiter comme un try/catch et d'en mettre un peu partout, autour de chaque appel qui pourrait mal tourner. C'est exactement ce qu'il ne faut pas faire. recover a un seul bon emplacement : la frontière d'une unité de travail. Un serveur HTTP traite des milliers de requêtes ; si l'une d'elles panique à cause d'un bug, on ne veut pas que tout le serveur s'effondre et coupe les autres. On installe donc un recover à la frontière de la requête — typiquement dans un middleware — qui transforme le panic d'une requête en une réponse 500 pour ce client-là, journalise la trace pour qu'on puisse corriger, et laisse le serveur continuer à servir tous les autres. Le même raisonnement vaut pour le sommet d'une goroutine de travail, ou l'appel d'un greffon dont on ne maîtrise pas le code. La frontière, c'est l'endroit où l'on décide qu'une unité peut échouer sans contaminer le reste. Partout ailleurs, recover n'isole rien — il masque. Et masquer un panic, on va le voir, c'est souvent pire que le laisser tuer le programme.Volume 4 showed recover's mechanics: that it only works in a defer, that it intercepts a panic unwinding the stack, and that it converts that rupture into a value you can inspect. This issue doesn't revisit that mechanics — it answers the question it leaves open, and which separates robust code from dangerous code: where do you put a recover? The temptation, especially coming from an exception language, is to treat it like a try/catch and put one everywhere, around each call that might go wrong. That's exactly what not to do. recover has a single good location: the boundary of a unit of work. An HTTP server handles thousands of requests; if one of them panics because of a bug, you don't want the whole server to collapse and cut off the others. So you install a recover at the request's boundary — typically in a middleware — that turns one request's panic into a 500 response for that client, logs the trace so you can fix it, and lets the server keep serving all the others. The same reasoning holds for a worker goroutine's top, or the call of a plugin whose code you don't control. The boundary is the place where you decide a unit may fail without contaminating the rest. Everywhere else, recover isolates nothing — it masks. And masking a panic, as we'll see, is often worse than letting it kill the program.

Une digue à la frontière de la requêteA dike at the request boundary
func recoverMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if v := recover(); v != nil {
                log.Printf("panic récupéré : %v\n%s", v, debug.Stack())
                http.Error(w, "internal error", 500)   // … CETTE requête meurt
            }
        }()
        next.ServeHTTP(w, r)   // … le serveur, lui, continue de servir les autres
    })
}
La frontière isole l'échecThe boundary isolates the failure
une requête
un panic peut survenir
→
la frontière
recover : 500 + log
→
le process
reste vivant, sert les autres

Le recover bien placé ne supprime pas l'échec — il le contient. Une unité tombe, le reste tient. C'est une frontière d'isolation, pas un filet posé sous chaque ligne de code.A well-placed recover doesn't erase the failure — it contains it. One unit falls, the rest holds. It's an isolation boundary, not a net laid under every line of code.

Le réflexe du numéroThe issue's reflex

« Suis-je à une frontière d'unité de travail ? si oui, je récupère, je journalise, je convertis. Sinon, je laisse passer. » Un recover se compte sur les doigts d'une main dans une base de code : un par serveur, un par pool de goroutines, un par appel de greffon. S'il y en a dans chaque fonction, c'est le signe qu'on s'en sert comme d'un try/catch — et qu'on est en train de transformer des bugs francs en silences trompeurs."Am I at a unit-of-work boundary? if yes, I recover, log, convert. Otherwise, I let it through." A recover should be countable on one hand in a codebase: one per server, one per goroutine pool, one per plugin call. If there's one in every function, it's the sign you're using it as a try/catch — and turning honest bugs into deceptive silences.

Pourquoi un panic ne devrait pas tuer tout un serveurWhy one panic shouldn't kill a whole server

En Go, un panic non récupéré dans n'importe quelle goroutine arrête le processus entier — y compris les milliers de requêtes en vol qui n'avaient rien à voir. C'est cohérent avec la philosophie du langage : un panic signale un invariant rompu, un état où il n'est plus sûr de continuer, et la réponse par défaut est l'arrêt. Mais à l'échelle d'un serveur, cette réponse est trop brutale : le bug d'une requête ne devrait pas priver les autres de service. net/http le sait : il rattrape déjà le panic d'un handler, le journalise et coupe la connexion — mais le client ne reçoit aucune réponse exploitable, et une goroutine lancée par le handler n'est pas couverte du tout. Le middleware de recover rend l'échec propre : une vraie 500, une trace structurée. La frontière de recover est précisément le compromis : on accepte qu'une requête isolée échoue durement, on en capture la trace pour la corriger, et on préserve la disponibilité de l'ensemble. C'est de l'isolation de panne, au sens des systèmes — circonscrire le rayon de souffle d'un défaut. Le mot-clé, c'est circonscrire : pas étouffer.In Go, an unrecovered panic in any goroutine stops the whole process — including the thousands of in-flight requests that had nothing to do with it. It's consistent with the language's philosophy: a panic signals a broken invariant, a state where it's no longer safe to continue, and the default response is to stop. But at a server's scale, that response is too brutal: one request's bug shouldn't deprive the others of service. net/http knows this: it already recovers a handler's panic, logs it and drops the connection — but the client gets no usable response, and a goroutine launched by the handler isn't covered at all. A recover middleware makes the failure clean: a real 500, a structured trace. The recover boundary is precisely the compromise: you accept that an isolated request fails hard, you capture its trace to fix it, and you preserve the availability of the whole. It's fault isolation, in the systems sense — bounding a defect's blast radius. The keyword is bound: not smother.

🔒

La suite est réservée The rest is members-only

Le premier numéro est libre. Débloque tout The Go Loop — tous les volumes, à vie — pour 5 €, paiement unique. The first issue is free. Unlock all of The Go Loop — every volume, forever — for €5, one-time.

← Retour au kiosqueBack to newsstand