Codes d'erreur cohérents : une petite taxonomie et des journaux plus sûrs
Apprenez à concevoir des codes d'erreur cohérents, mapper les exceptions vers des messages sûrs pour l’utilisateur et logger assez pour déboguer sans exposer de secrets.

Pourquoi les erreurs jetées créent-elles du chaos pour les utilisateurs et les équipes
Quand chaque partie d'une app jette sa propre erreur, les gens obtiennent des messages différents pour le même problème. Un échec de connexion peut afficher "Quelque chose s'est mal passé" sur mobile, "401" sur le web, et une trace de pile rouge dans un écran admin. L'utilisateur n'apprend rien d'utile, et votre boîte de support se remplit de captures d'écran qui ne se ressemblent pas.
Cette incohérence explique pourquoi les utilisateurs signalent des problèmes par "ça a planté" ou "ça n'a pas marché". Ils ne savent pas s'ils ont tapé le mauvais mot de passe, perdu la connexion, ou subi une panne temporaire. Sans un signal stable à partager, ils devinent, réessaient, ou partent.
Les ingénieurs le ressentent aussi. Si les erreurs sont jetées et affichées telles quelles, les journaux deviennent bruyants : textes différents pour la même cause, contexte manquant et aucune manière propre de regrouper les incidents. Un bug peut ressembler à 20 problèmes différents, d'où plus de temps pour repérer les motifs, reproduire l'incident et confirmer un correctif.
Le texte brut des exceptions crée aussi un risque de sécurité. Les exceptions incluent souvent des détails que vous ne devriez jamais montrer aux utilisateurs (ou stocker en clair) : clés secrètes, chaînes de connexion, chemins internes, extraits SQL ou données utilisateur. Une erreur non gérée peut divulguer plus que prévu.
Un système simple de codes d'erreur cohérents règle cela. Les utilisateurs voient un message clair et sûr plus un code court à partager. Votre équipe voit des journaux structurés liés au même code, donc le débogage s'accélère sans exposer de données sensibles.
Ce que vous allez construire : codes, messages et journaux utiles
Vous créez un contrat simple pour les échecs :
- Un code stable qui identifie ce qui s'est passé
- Un message sûr pour l'utilisateur qui explique quoi faire ensuite
- Des journaux qui aident à corriger la cause racine
Un bon code d'erreur n'est pas une phrase. C'est un identifiant qui reste le même même si vous réécrivez le message, changez de framework, ou refactorez la fonctionnalité. Cette stabilité rend les codes utiles pour le support, les tableaux de bord et les rapports de bugs.
Gardez les couches séparées. Les utilisateurs obtiennent un langage simple et des étapes suivantes. Les ingénieurs obtiennent un contexte détaillé dans les journaux. Cette séparation réduit aussi le risque de fuite parce que le message utilisateur n'inclut jamais de secrets, traces de pile, erreurs SQL ou identifiants internes.
Exemple : si la connexion échoue parce que la base de données est hors service, l'utilisateur devrait voir « Nous ne pouvons pas vous connecter pour le moment. Réessayez dans quelques minutes. ». Le code pourrait être AUTH.SERVICE_UNAVAILABLE. Les journaux capturent le timeout de la base, la dépendance en échec et un identifiant de requête.
Pour un petit outil interne, des erreurs jetées brutes peuvent suffire. Une fois que vous avez de vrais utilisateurs, des tickets de support ou des contraintes de conformité, cette structure rapporte vite.
Commencez par une petite taxonomie d'erreurs que vous pouvez réellement maintenir
Une bonne taxonomie est volontairement ennuyeuse. Si vous ne pouvez pas vous en souvenir sans doc, elle est trop grande. L'objectif est d'avoir des codes d'erreur cohérents qui indiquent quel type de problème s'est produit sans exposer l'organisation interne de votre code.
Groupez par impact utilisateur, pas par couches internes. « DB » est utile parce que cela signifie généralement « réessayez plus tard ». « RepositoryError » n'est que la structure de vos dossiers qui fuit dans votre API.
Voici un jeu de départ simple (6 groupes) qui couvre la plupart des apps :
| Groupe | Ce qui appartient ici (une phrase) | Recommandé de réessayer ? |
|---|---|---|
| AUTH | Problèmes de connexion/session comme identifiants invalides, tokens expirés ou auth manquante. | Parfois (rafraîchissement de token), souvent Non |
| VALIDATION | La requête est incorrecte : champs manquants, formats erronés, valeurs hors plage. | Non |
| PAYMENT | Problèmes de facturation, état d'abonnement, ou refus du prestataire de paiement. | Parfois (timeouts), souvent Non |
| DB | Base de données indisponible, timeouts, verrous, ou migrations ratées. | Souvent Oui |
| INTEGRATION | Un service tiers a échoué (email, SMS, cartes, cibles webhook). | Souvent Oui |
| INTERNAL | Tout ce qui est inattendu et doit être investigué (bugs, nulls, états impossibles). | Parfois, par défaut Non |
Deux règles maintiennent ça viable :
- Chaque groupe doit avoir une phrase claire « appartient ici si... », pour éviter des débats d'une heure sur des cas limites.
- Décidez du comportement de réessai au niveau du groupe comme valeur par défaut, puis surchargez seulement quand nécessaire.
Si un utilisateur clique sur « Enregistrer » et que l'app ne peut pas atteindre la base de données, c'est DB (réessayer), même si l'erreur vient d'une librairie d'accès aux données. Pendant ce temps, « l'email manque @ » est toujours VALIDATION (pas réessayer), même si c'est détecté profondément dans votre code.
Concevez des codes d'erreur qui restent stables dans le temps
Les codes d'erreur n'aident que s'ils signifient la même chose la semaine suivante. Traitez-les comme une API publique : améliorez le message et le correctif, mais gardez le code stable une fois déployé.
Choisissez un format lisible et assez court pour être copié-collé dans un chat de support :
- Utilisez
AREA-NNN(exemples :AUTH-001,DB-003,PAY-012) - Faites en sorte que le préfixe corresponde à votre taxonomie (AUTH, DB, FILE, RATE, PERM)
- Réservez des plages de numéros si vous avez de gros sous-systèmes (AUTH-100+ pour OAuth, AUTH-200+ pour les sessions)
- Ne réutilisez jamais des numéros, même si un problème est « résolu »
- Gardez les codes dans une source de vérité unique (un fichier ou une table dans le repo)
Décidez ce qui doit rester stable entre les versions : le code, sa signification (une phrase) et la catégorie. Ce qui peut changer : le texte utilisateur, les étapes suggérées, et les détails internes que vous loggez.
Séparez aussi le code de l'identifiant de corrélation. L'utilisateur voit AUTH-001. Vos journaux reçoivent correlation_id=8f3c... plus la trace et le contexte. Le support peut demander le code et l'ID de corrélation sans exposer d'internes sensibles.
Faites correspondre les échecs internes à des messages sûrs pour l'utilisateur
Les utilisateurs n'ont pas besoin de savoir ce qui a cassé à l'intérieur de votre app. Ils doivent savoir ce qui s'est passé, quoi faire ensuite, et comment obtenir de l'aide si besoin.
Un message sûr pour l'utilisateur a généralement trois parties :
- Ce qui s'est passé (brièvement)
- Que faire ensuite
- Ce qu'il faut partager avec le support (le code)
Exemple :
"Échec de la connexion. Veuillez réessayer ou réinitialiser votre mot de passe. Partagez ce code avec le support : AUTH-002."
Gardez le texte utilisateur séparé des détails développeur. Le message utilisateur ne doit jamais inclure des traces de pile, noms de tables, chemins d'endpoint, clés API, e-mails ou tokens bruts. Ces détails appartiennent aux journaux seulement, et même là ils doivent être assainis.
Un exemple réaliste : votre base de données lance UniqueViolation parce qu'un email existe déjà. En interne, cela peut contenir des détails SQL. Externement, renvoyez : « Cet email est déjà utilisé. Essayez plutôt de vous connecter. Partagez ce code avec le support : ACC-001. » Loggez le type d'exception et l'identifiant de requête, mais ne loggez pas l'email en clair.
Gardez les journaux utiles sans divulguer de données
Les journaux sont pour vous, pas pour vos utilisateurs. L'objectif est simple : lorsqu'un échec survient, vous pouvez retrouver la requête exacte, voir ce qui a cassé, et corriger rapidement, sans stocker mots de passe, tokens ou données clients.
Un bon défaut est de logger trois ancres à chaque fois : le code d'erreur, un identifiant de corrélation, et l'emplacement (service, module, route ou handler). Cela transforme une trace de pile aléatoire en quelque chose de searchable et regroupable.
Une checklist simple qui fonctionne pour la plupart des apps :
- Toujours logger :
error_code,correlation_id, chemin de la requête, et où cela est arrivé (endpoint plus fonction/module). - Logger l'identité en sécurité : un ID interne utilisateur suffit souvent ; évitez emails, noms ou adresses IP complètes sauf si nécessaire.
- Redactez ou hachez les champs sensibles : mots de passe, tokens, clés API, cookies, en-têtes d'auth, et secrets d'env.
- Utilisez les niveaux de logs avec intention : WARN pour des échecs attendus (mauvaise entrée, auth refusée), ERROR pour des crashs inattendus.
- Décidez de la rétention et des accès : conservez les journaux seulement aussi longtemps que nécessaire et limitez qui peut les consulter.
Un motif d'échec fréquent est le débogage « utile » qui imprime les requêtes complètes ou les variables d'environnement. Cela peut faire fuiter des secrets dans les journaux et les backups. Si vous héritez d'un code comme ça, corrigez d'abord le logging. Vous réduirez le risque immédiatement, même avant d'avoir résolu chaque bug.
Étapes pas à pas : implémenter des erreurs cohérentes dans votre app
Commencez par choisir une seule forme d'erreur que toutes les parties de l'app peuvent retourner. C'est ce qui rend les codes d'erreur cohérents possibles même quand les échecs proviennent de bibliothèques différentes.
Une forme simple et pratique ressemble à ceci :
{
"code": "AUTH_INVALID_CREDENTIALS",
"message": "Email or password is incorrect.",
"status": 401,
"correlationId": "b3f1d2..."
}
Puis implémentez-le en petites étapes :
- Créez un
AppError(ou équivalent) et un helper pour le construire. Rendezcodeetstatusobligatoires, et gardezmessagesûr pour l'utilisateur. - Ajoutez un gestionnaire global qui capture les exceptions non attrapées (middleware serveur, handler de gateway API, ou boundary d'erreur au niveau de l'app). Il doit toujours retourner la même forme.
- Centralisez le mapping des exceptions en un seul endroit. Exemple : mappez une erreur de contrainte unique DB à
CONFLICT_EMAIL_TAKENau lieu de laisser le texte brut fuiter. - Standardisez les codes de statut HTTP pour que les clients réagissent de façon prévisible.
- Ajoutez des tests qui vérifient à la fois le
codeet lemessage, pas seulement le statut.
Pour les codes de statut, gardez l'ensemble petit et prévisible :
- 400 pour entrée invalide
- 401 pour non connecté ou identifiants incorrects
- 403 pour connecté mais non autorisé
- 404 pour ressources manquantes
- 409 pour conflits (déjà existant)
Un scénario réaliste : un endpoint de connexion lance « Cannot read properties of undefined » parce que le corps de la requête est manquant. Avec un mapper, cela devient INPUT_MISSING_FIELDS avec un 400 et un message clair, tandis que les journaux conservent la trace de pile liée à un correlationId.
Sources d'erreurs courantes et comment les classer
La plupart des apps échouent aux mêmes endroits. Si vous nommez ces endroits et les traitez de la même façon à chaque fois, vous obtenez des tickets de support plus clairs et moins de messages « ça a planté ».
Une règle utile : classifiez selon ce que l'utilisateur peut faire ensuite, pas selon le texte exact de l'exception. Deux traces de pile peuvent sembler différentes mais signifier la même chose : « reconnectez-vous » ou « réessayez plus tard ».
Auth et permissions doivent séparer « nécessite connexion » de « accès refusé », pour que l'UI sache s'il faut demander une connexion ou afficher un message de permissions. Input et validation doivent renvoyer des codes corrigeables par l'utilisateur et, quand possible, pointer le champ concerné, sans exposer les détails serveur.
Base de données et stockage doivent dissocier « introuvable » de « requête échouée », car cela mène à des actions différentes (afficher un état vide vs réessayer plus tard). Les services externes doivent classer selon si le réessai est sûr maintenant, plus tard, ou jamais. Les fichiers et uploads doivent être assez explicites pour aider l'utilisateur à choisir un autre fichier, mais ne doivent jamais écrire le contenu du fichier dans les logs.
Erreurs communes qui empirent la gestion des erreurs
La plupart des implémentations échouent parce que les équipes optimisent pour « faire marcher » au lieu de « rester supportables ». Quelques habitudes transforment silencieusement des petits bugs en heures de recherche et peuvent faire fuiter des données sensibles.
Erreurs qui nuisent au support et à la confiance
Utiliser le message humain comme « code » est un piège courant. Les messages changent quand vous réécrivez le copy, traduisez le texte, ou ajoutez des détails. Le support ne peut plus chercher de façon fiable, et les clients ne peuvent pas construire de comportements stables.
Un autre piège est de commencer avec un catalogue énorme. Si vous créez 200+ codes le premier jour, personne ne les maintient, et les gens inventent de nouveaux codes au hasard. Un petit ensemble clair que vous réutilisez vaut mieux qu'une longue liste ignorée.
Le texte brut des exceptions ne doit jamais atteindre les utilisateurs ou les clients d'API. Il contient souvent des traces de pile, noms de tables, chemins de fichiers, ou indices utiles à un attaquant.
Logger les corps de requête complets est un autre risque évitable. C'est une façon facile de capturer mots de passe, tokens, clés API ou données personnelles. Les journaux doivent aider à déboguer sans devenir une fuite de données.
Enfin, ne mélangez pas différents types d'échecs. Traiter « introuvable » comme une « erreur serveur » cache de vrais problèmes de fiabilité et rend l'alerte bruyante.
Checklist rapide avant de publier
Avant la mise en production, faites un passage qui vérifie l'expérience utilisateur, le contrat API et ce que votre équipe verra à 2h du matin.
- Vue utilisateur : chaque échec affiche un message simple plus une étape suivante (réessayer, se reconnecter, contacter le support). Pas de traces de pile brutes.
- Vue API : chaque réponse d'erreur inclut un code d'erreur stable et un identifiant de corrélation qui apparaît aussi dans les journaux serveurs.
- Sécurité des journaux : les journaux ne contiennent jamais de secrets (clés API), tokens, mots de passe, codes à usage unique ou données de carte de paiement complètes. Si vous devez journaliser un identifiant, masquez-le.
- Comportement de réessai : les échecs réessayables sont clairement marqués et traités avec prudence (backoff court, réessais limités et message clair « réessayez »).
- Processus support : quelqu'un doit pouvoir diagnostiquer le problème en utilisant seulement le code d'erreur et l'identifiant de corrélation, sans demander à l'utilisateur de coller la sortie des outils développeur.
Un test simple : déclenchez trois échecs courants (mauvais mot de passe, session expirée, timeout serveur). Si l'utilisateur sait quoi faire ensuite et que votre équipe peut retrouver la ligne de log exacte via l'identifiant de corrélation, vous êtes prêt.
Un exemple réaliste : transformer un crash de connexion en un code clair
Problème fréquent de prototype : un flux de connexion fonctionne le lundi, puis une mise à jour rapide le mardi et soudain tout le monde est déconnecté. Certains utilisateurs voient « Quelque chose s'est mal passé », d'autres une page blanche, et votre équipe voit une trace de pile qui mentionne une erreur de base de données et une librairie d'auth en même temps.
Avec des codes d'erreur cohérents, vous décidez ce que signifie cette panne pour les utilisateurs et le support. Ici, l'app ne parvient pas à créer une session après validation des identifiants. Vous la classez comme problème d'authentification et la mappez à AUTH-003 (par exemple : « Échec de création de session »).
Ce que voit l'utilisateur quand AUTH-003 survient :
- « Nous n'avons pas pu vous connecter. Veuillez réessayer dans une minute. »
- « Si le problème persiste, contactez le support et communiquez ce code : AUTH-003 et ID : 7F2K9. »
Ce message est honnête, court et sûr. Il ne mentionne pas de tables, tokens, providers ou autres internes.
Pendant ce temps, vos journaux conservent les détails, mais uniquement ce que vous pouvez stocker en sécurité : le code, l'identifiant de corrélation, la route de requête, la version du build et la cause racine interne (par exemple, "DB timeout while writing session row"). Les champs sensibles doivent être redacted (email haché, IP tronquée, pas de mots de passe, pas de tokens complets).
Le support peut alors trier rapidement. Un utilisateur envoie « AUTH-003, 7F2K9 » et votre équipe peut extraire une seule trace de logs exacte au lieu de deviner.
Prochaines étapes pour nettoyer une base de code existante
Commencez par des preuves. Récupérez 1 à 2 semaines de journaux et les tickets de support les plus fréquents, puis listez les 20 messages d'erreur les plus courants rencontrés par les utilisateurs. Vous verrez généralement des répétitions : timeouts, échecs d'auth, enregistrements manquants et crashs génériques « Quelque chose s'est mal passé ».
Ajoutez la structure par petites itérations. Choisissez un endpoint à fort trafic ou un écran (connexion, paiement, upload de fichier) et introduisez les codes cohérents d'abord là. Quand c'est stable, étendez au suivant.
Un plan de déploiement qui marche en équipe réelle :
- Regroupez les erreurs top en 5 à 8 catégories (auth, validation, dépendance, permissions, introuvable, interne).
- Ajoutez une seule couche de mapping qui transforme les exceptions jetées en
{code, userMessage, logContext}. - Maintenez une mini-doc des codes : ce qu'ils signifient, qui en est responsable, et quand on peut en ajouter un nouveau.
- Ajoutez des tests pour le mapping (un test par code suffit au début).
- Revoyez les journaux pour confirmer que vous gardez un contexte utile tout en supprimant secrets et données personnelles.
Si vous avez hérité d'une base générée par IA, attendez-vous à des throws imprévisibles, des formes d'erreurs mélangées et des problèmes de sécurité cachés (secrets dans les journaux ou erreurs BD exposées dans les réponses). Dans ce cas, stabiliser la bordure d'erreur et nettoyer le logging donne souvent le gain le plus rapide.
Si vous souhaitez une seconde paire d'yeux sur une app générée par IA qui fuit des erreurs ou produit des journaux désordonnés, FixMyMess (fixmymess.ai) se concentre sur le diagnostic et la réparation des problèmes comme l'auth cassée, les journaux risqués et l'architecture spaghetti. Leur audit de code gratuit peut vous aider à identifier les points de défaillance majeurs avant d'engager des changements.
Questions Fréquentes
Pourquoi les erreurs jetées sont-elles si inconsistantes dans l'application ?
Les erreurs jetées varient selon l'appareil, le framework et le chemin de code, si bien qu'une même cause racine peut apparaître sous des messages totalement différents. Des codes d'erreur stables vous donnent une étiquette commune pour le support, l'analytique et le débogage, même si le texte visible change.
Combien de catégories de codes d'erreur devrais-je commencer ?
Commencez par un petit ensemble que vous pouvez mémoriser et appliquer, généralement 5 à 8 groupes. Regroupez selon ce que l'utilisateur peut faire ensuite (corriger l'entrée, se reconnecter, réessayer plus tard) plutôt que par des couches internes comme « repository » ou « service ».
Quel format de code d'erreur est robuste dans le temps ?
Utilisez un format lisible et stable comme AUTH-001 ou DB-003 et conservez la signification de chaque code une fois publié. Vous pouvez modifier le libellé du message au fil du temps, mais ne réutilisez pas les codes pour de nouvelles significations.
Le message visible par l'utilisateur doit-il être identique au code d'erreur ?
Non. Le code est un identifiant, pas une phrase : il doit rester inchangé même si vous réécrivez le texte de l'interface ou traduisez l'app. Placez l'explication lisible pour l'humain dans le champ message et conservez-le sûr pour les utilisateurs.
Que doit contenir au minimum une réponse d'erreur API ?
Conservez le code stable, un message clair, et évitez d'exposer des détails internes. Par défaut, retournez une courte explication, la marche à suivre et le code que l'utilisateur peut partager avec le support.
Où dois-je mapper les exceptions vers des codes et messages ?
Faites la correspondance des exceptions internes vers des messages sûrs pour l'utilisateur dans un seul endroit central, comme un gestionnaire global d'erreurs ou un middleware. C'est là que vous convertissez, par exemple, un timeout de base de données en un code cohérent et un message « réessayez » tout en conservant la trace de pile uniquement dans les journaux.
Que dois-je logger pour que le débogage soit rapide mais que les données restent sûres ?
Consignez un code d'erreur et un identifiant de corrélation, puis ajoutez juste assez de contexte pour reproduire le problème sans capturer de secrets ni de données personnelles. Évitez de logger mots de passe, jetons, clés API et corps de requête complets, même en débogage.
Comment décider si une erreur est réessayable ?
Utilisez le comportement par défaut de la catégorie : les erreurs de validation ne sont généralement pas retryables, tandis que les pannes de dépendances (base de données ou services tiers) le sont souvent. Si vous réessayez, faites-le doucement et de façon limitée pour ne pas surcharger un service déjà en difficulté.
Pourquoi est-ce problématique de traiter les 404 et 500 de la même manière ?
Les traiter de la même façon aggrave l'UX et les opérations : « introuvable » (404) est souvent un résultat normal, tandis que « erreur interne » (500) signale un problème de fiabilité. Donnez-leur des codes et statuts différents pour que l'interface affiche l'état adéquat et que vos alertes restent pertinentes.
Quelle est la manière la plus rapide de nettoyer cela dans un prototype généré par IA ?
Si vous héritez d'un code généré par IA, commencez par ajouter une bordure d'erreur globale et standardiser la forme d'erreur, puis nettoyez les journaux pour empêcher les fuites. Si vous voulez de l'aide, FixMyMess peut réaliser un audit de code gratuit et généralement rendre des prototypes cassés prêts pour la production en 48–72 heures.