Aikido

Traiter les erreurs dans les blocs de capture : pourquoi les blocs de capture vides brisent les systèmes de production

Lisibilité

Règle
Gestion des erreurs dans catch . 
Les blocs catch vides silencieusement avaler erreurs, 
rendant débogage difficile. 
Langues prises en charge : Java, C, C++, PHP, JavaScript, 
TypeScript, Go, Python

Introduction

Les blocs catch vides sont l'un des anti-modèles les plus dangereux du code de production. Lorsque des exceptions sont capturées mais non traitées, l'erreur disparaît sans laisser de trace. L'application continue de fonctionner avec un état corrompu, des données invalides ou des opérations ratées qui auraient dû interrompre l'exécution. Les utilisateurs constatent des échecs silencieux lorsque des fonctionnalités ne fonctionnent pas, mais ne reçoivent aucun message d'erreur. Les équipes opérationnelles n'ont pas de journaux pour déboguer. La seule indication que quelque chose ne va pas vient quelques heures ou quelques jours plus tard, lorsque des défaillances en cascade rendent le système inutilisable.

Pourquoi c'est important

Débogage et réponse aux incidents : Les blocs de capture vides éliminent les journaux d'erreurs. Les ingénieurs ne disposent d'aucune trace de pile, d'aucun message d'erreur ni d'aucune indication sur le moment ou l'endroit où l'échec s'est produit, ce qui rend les problèmes pratiquement impossibles à reproduire.

Corruption silencieuse des données : Lorsque des opérations de base de données ou des appels d'API échouent à l'intérieur de blocs catch vides, l'application continue comme si elles avaient réussi. Les enregistrements sont partiellement mis à jour, les transactions sont incomplètes et, lorsque la corruption est découverte, la piste d'audit a disparu.

Vulnérabilités en matière de sécurité : Les blocs catch vides masquent les défaillances de sécurité telles que les erreurs d'authentification ou les contrôles d'autorisation. Un attaquant qui déclenche une exception dans un chemin critique pour la sécurité peut contourner entièrement les protections si l'erreur est avalée silencieusement.

Défaillances en cascade : Lorsque des erreurs sont avalées, l'application continue dans un état invalide. Les opérations suivantes, qui dépendent du résultat de l'opération ayant échoué, échoueront également, créant ainsi une chaîne de défaillances qui détourne les ingénieurs de la véritable cause première.

Exemples de code

❌ Non conforme :

async function updateUserProfile(userId, profileData) {
    try {
        await db.users.update(userId, profileData);
        await cache.invalidate(`user:${userId}`);
        await searchIndex.update(userId, profileData);
    } catch (error) {
        // TODO: handle error
    }

    return { success: true };
}

Pourquoi c'est faux : si une opération échoue, l'erreur est ignorée silencieusement et la fonction renvoie le succès. La base de données peut être mise à jour, mais l'invalidation du cache peut échouer, laissant des données périmées. Ou bien la mise à jour de l'index de recherche échoue, rendant l'utilisateur introuvable, sans qu'aucun journal ou alerte n'indique le problème.

✅ Conforme :

async function updateUserProfile(userId, profileData) {
    try {
        await db.users.update(userId, profileData);
        await cache.invalidate(`user:${userId}`);
        await searchIndex.update(userId, profileData);
        return { success: true };
    } catch (error) {
        logger.error('Failed to update user profile', {
            userId,
            error: error.message,
            stack: error.stack
        });
        throw new ProfileUpdateError(
            'Unable to update profile',
            { cause: error }
        );
    }
}

Pourquoi cela est-il important ? Chaque erreur est enregistrée avec son contexte, ce qui permet d'obtenir des informations de débogage. L'erreur se propage à l'appelant, ce qui permet de gérer les erreurs au niveau approprié. Les systèmes de surveillance peuvent signaler ces erreurs, et l'application échoue rapidement au lieu de continuer avec un état invalide.

Conclusion

Les blocs catch vides ne sont jamais acceptables dans un code de production. Chaque exception capturée doit être journalisée au minimum, et la plupart doivent se propager aux appelants ou déclencher des actions de récupération spécifiques. Si vous avez réellement besoin d'ignorer une erreur, documentez-en la raison avec un commentaire expliquant la justification commerciale. Le défaut devrait toujours être de traiter les erreurs de manière explicite, et non de les ignorer silencieusement.

FAQ

Vous avez des questions ?

Que faire si j'ai vraiment besoin d'ignorer certaines erreurs ?

Documentez-la explicitement avec un commentaire expliquant pourquoi l'erreur peut être ignorée. Enregistrez l'erreur au niveau de débogage afin qu'elle apparaisse dans les journaux verbeux mais ne déclenche pas d'alertes. Examinez si le fait d'ignorer l'erreur pourrait conduire à un état invalide. Même pour les erreurs "attendues" comme les erreurs de cache ou les dépassements de délai réseau, la journalisation aide les équipes d'exploitation à comprendre les modèles de comportement du système.

Dois-je toujours enregistrer les erreurs dans les blocs catch ?

La journalisation est généralement une bonne idée car vous ne pouvez pas déboguer les problèmes sans voir ce qui a échoué. Dans certains cas, il est possible de retracer le problème sans journalisation, par exemple en relançant immédiatement l'erreur pour qu'elle soit traitée ailleurs, ou si l'application doit se bloquer et redémarrer en cas d'échec critique. Mais une bonne journalisation est toujours utile.

Quelle est la différence entre journaliser et relancer les erreurs ?

La journalisation enregistre ce qui s'est passé à des fins de débogage et de surveillance. Le relancement propage l'erreur aux appelants afin qu'ils puissent décider comment réagir. Faites les deux : enregistrez l'erreur avec le contexte au point de défaillance, puis relancez-la (éventuellement enveloppée dans un type d'erreur plus spécifique) pour laisser les appelants s'occuper de la récupération. N'enregistrez pas la même erreur à plusieurs niveaux, cela crée du bruit.

Comment gérer les erreurs qui se produisent dans les blocs "finally" ?

Les blocs de fin doivent rarement générer des erreurs. S'ils doivent effectuer des opérations sujettes à erreur (comme la fermeture de ressources), ils doivent les inclure dans leur propre try-catch. Enregistrez toutes les erreurs, mais ne les laissez pas masquer l'erreur initiale. Certains langages fournissent une syntaxe pour gérer à la fois l'erreur principale et les erreurs du bloc finally, utilisez ces mécanismes pour préserver les deux contextes d'erreur.

Quel est l'impact sur les performances de l'enregistrement de chaque erreur ?

La journalisation est bon marché par rapport au coût du débogage des problèmes de production sans journaux. Les cadres de journalisation modernes sont hautement optimisés. Si vous avez tellement d'erreurs que la journalisation a un impact sur les performances, corrigez les erreurs plutôt que de les cacher. Des taux d'erreur élevés indiquent des problèmes graves que des blocs de capture vides ne feront qu'aggraver.

Les blocs catch doivent-ils toujours renvoyer des erreurs ou peuvent-ils renvoyer des valeurs d'erreur ?

Cela dépend du langage et de l'architecture. En JavaScript, avec les promesses, le fait de lancer une erreur à partir de catch se propage au gestionnaire d'erreur suivant. Renvoyer un objet d'erreur à partir de catch résout la promesse avec cette erreur, ce qui est généralement faux. Familiarisez-vous avec la sémantique de gestion des erreurs de votre langage. En général, laissez les erreurs se propager à moins que vous ne puissiez les récupérer de manière significative.

Comment gérer les erreurs dans les opérations asynchrones qui n'ont pas de try-catch ?

Utilisez des gestionnaires .catch() sur les promesses, des récepteurs d'événements d'erreur sur les émetteurs d'événements, ou des rappels d'erreur dans les API basées sur des rappels. N'ignorez jamais les gestionnaires de rejet ou les rappels d'erreur. Les rejets de promesses non gérées doivent être surveillés au niveau du processus et traités comme des échecs critiques. Node.js moderne peut se terminer sur des rejets non gérés, ce qui est mieux qu'un échec silencieux.

Obtenir la sécurité gratuitement

Sécurisez votre code, votre cloud et votre environnement d'exécution dans un système central.
Trouvez et corrigez rapidement et automatiquement les vulnérabilités.

Aucune carte de crédit n'est requise | Scanner les résultats en 32sec.