Un dimanche soir, ma femme remplit un formulaire en ligne pour la retraite de son père. Elle valide. Écran gris : « Erreur technique, veuillez réessayer plus tard. » Elle recommence, change de navigateur, vérifie sa connexion. Toujours le même mur. Le support est fermé, on est dimanche.
Réflexe de vieux dev, j'ouvre la console. Et là, dans la réponse du serveur, en clair : « Le format de l'email n'est pas conforme. » Code 417. Le coupable, trouvé en dix secondes : le « + » dans son adresse, prenom+retraite@gmail.com, une adresse parfaitement valide que ce formulaire refusait sans le dire.
Le serveur savait. Il l'avait même écrit. Cette phrase n'est simplement jamais arrivée jusqu'à l'écran.
Ce n'est pas un problème de formulation, ni de gentillesse envers l'utilisateur. C'est une décorrélation : d'un côté un serveur qui connaît la raison exacte du refus, de l'autre un écran qui n'en montre rien. Entre les deux, une frontière. Et la vérité ne l'a pas franchie. La question intéressante n'est pas « pourquoi ce message est nul », c'est « pourquoi l'information s'arrête à la frontière alors qu'elle existe des deux côtés du réseau ».
Pourquoi la vérité ne passe pas la frontière
Regarde ce qui l'a franchie, justement : le code 417. Il est arrivé jusqu'à la console, intact. Pas par chance : parce que les codes de statut HTTP sont un vocabulaire que le serveur et le navigateur partagent d'avance. 404, 403, 500, les deux camps savent déjà ce que ça veut dire. Un code de statut, c'est un contrat. Il traverse la frontière parce qu'il n'a besoin d'aucune traduction.
La raison précise, elle, voyageait dans le corps de la réponse. Du texte libre. Aucune structure convenue, aucun contrat. Le front reçoit une chaîne de caractères dont il ne sait rien : destinée à l'utilisateur ? technique ? déjà traduite ? fiable ? (Au passage, c'est bien le serveur qui a tranché : le navigateur a laissé passer le « + », le serveur l'a refusé, parce que c'est lui qui valide pour de vrai.) Face à cette incertitude sur le corps, le front fait le choix prudent et paresseux : catch (e) { afficher("Erreur technique") }. Un attrape-tout. Il jette la raison parce qu'il ne sait pas quoi en faire, et se rabat sur une phrase qui marche pour tout et ne dit rien.
Voilà la vraie mécanique du mensonge. Ce n'est pas que le serveur se tait : c'est que la moitié de ce qu'il dit n'a pas de contrat pour traverser. Le statut passe, la raison reste à quai. À l'usine, une machine bien faite n'a jamais un seul voyant rouge « panne » : elle a un code pour le bourrage papier et un autre pour le moteur grillé, parce qu'on ne répare pas les deux de la même façon.
Donne à la raison le même contrat qu'au code
Le remède est déjà sous tes yeux : si le code de statut franchit la frontière, c'est parce qu'il est standardisé. Il suffit d'étendre ce principe. Donne à la raison un contrat, exactement comme le statut en a un.
Concrètement, le serveur arrête de renvoyer du texte libre et renvoie un code d'erreur stable, lisible par une machine, documenté :
{
"code": "email.plus_non_supporte",
"field": "email",
"message": "Le format de l'email n'est pas conforme."
}
Ce n'est pas un bricolage maison : c'est le standard Problem Details (RFC 9457, type application/problem+json), fait précisément pour qu'une API dise à un client ce qui a échoué, de façon exploitable. Côté front, tu tiens une table qui associe chaque code connu à un vrai message actionnable, dans la langue de l'utilisateur :
const messages = {
"email.plus_non_supporte":
"Ton adresse contient un « + », que ce formulaire n'accepte pas. Essaie une adresse sans.",
};
afficher(messages[erreur.code] ?? "Erreur technique, réessaie plus tard.");
Tout se joue dans ce ??. Le message générique n'est plus la réponse par défaut : il devient le filet pour les codes que tu n'as pas encore traduits. Afficher la vérité redevient la règle, la cacher redevient une exception que tu choisis. Ta frontière a maintenant un poste de contrôle : ce qui porte un contrat passe, en clair, jusqu'à l'écran.
Et c'est exactement la partie que l'assistant n'écrira pas à ta place. Demande-lui une gestion d'erreur, il te sortira le catch générique, parce qu'il ne connaît pas ton domaine : quels codes d'erreur ton produit expose, ce que tes utilisateurs ont le droit de savoir, ça ne s'infère pas d'un modèle, ça se décide. Le contrat, c'est du savoir métier. Il reste à toi.
Par quoi commencer demain matin
Prends tes cinq erreurs 4xx les plus fréquentes. Donne à chacune un code stable côté serveur, mappe-les côté front. Une heure de travail, et la moitié de tes « erreur technique » deviennent des phrases qui disent quoi faire. Tu gardes le générique pour les 5xx, là où c'est honnêtement tout ce que tu peux dire : le problème est chez toi, l'utilisateur ne peut qu'attendre.
Ta base de code connaît la vérité bien plus souvent que ton écran ne l'avoue. Elle n'a pas besoin que tu l'inventes. Juste que tu lui donnes un moyen de traverser.
Pour aller sous le capot : le code de statut HTTP, la différence entre 4xx et 5xx, la validation côté client ou serveur, ce qu'est un message d'erreur qui sert à quelque chose, et le cas limite qui a tout déclenché.