Fiabilité5 min de lecture

Erreurs critiques avalées : détection et gestion des échecs silencieux

Comprendre pourquoi intercepter une exception sans la traiter ni la propager détruit la cohérence du système, et comment concevoir une gestion d'erreur robuste sur les chemins critiques.

Dans le développement applicatif, le réflexe d'enrober du code à risque dans un bloc try/catch est universel. Cependant, lorsque le bloc d'interception se contente d'ignorer l'erreur capturée (corps de catch vide, simple commentaire indicatif, ou promesse terminée par .catch(() => {})), le remède s'avère infiniment pire que le mal.

Ce comportement est qualifié d'erreur critique avalée (swallowed error ou silent catch).

Loin d'améliorer la stabilité de l'application, faire disparaître une exception sur un chemin critique empêche l'invalidation d'état, désactive les alertes de production et engendre des corruptions de données indétectables.


Le modèle mental : échec maîtrisé vs silence destructeur

Une application résiliente ne cherche pas à prétendre que les erreurs n'arrivent jamais. Elle s'assure qu'un échec sur une opération critique laisse une trace observable et interrompt la chaîne de causalité.

sequenceDiagram
    participant User as Utilisateur / Client
    participant Server as Serveur d'Application
    participant DB as Base de Données
    participant Stripe as Passerelle de Paiement

    User->>Server: Valider la commande
    Server->>Stripe: Débit de la carte (Succès)
    Server->>DB: Insertion de la commande
    Note over DB: Échec : Violation de contrainte SQL
    Note over Server: catch (err) { /* vide */ }
    Server-->>User: "Commande validée avec succès !"
    Note over User,Server: Résultat : Client débité mais commande inexistante en base

Dans ce scénario possible, le serveur a « avalé » l'échec de persistance en base. La réponse HTTP émise vers le navigateur affiche une confirmation trompeuse. Le client est débité de son argent, mais son panier reste vide dans le système d'information. Retrouver l'origine du litige sans trace ni exception relève du cauchemar opérationnel.


Distinguer le critique de l'accessoire

Toutes les opérations d'une application n'ont pas la même importance vitale. La règle GVO-REL-SWALLOWED-CRITICAL-ERROR-V1 du moteur d'audit GVO opère une distinction nette entre deux familles de traitements :

1. Les opérations critiques (Interdiction formelle de catch vide)

  • Les mutations en base de données (insert, update, upsert, delete) ;
  • Les transactions financières et appels de facturation (Stripe, LemonSqueezy) ;
  • L'émission de communications contractuelles (emails de confirmation, SMS de vérification) ;
  • L'écriture de fichiers d'audit, de sécurité ou de conformité ;
  • Les appels d'inférence d'IA dont dépend une décision métier subséquente.

Sur ces chemins, une exception doit impérativement déclencher soit une annulation (rollback), soit une alerte traçable, soit une propagation vers le gestionnaire global d'erreur.

2. Les opérations accessoires (Dégradation gracieuse tolérée)

  • L'envoi de télémétrie ou de statistiques de tracking ;
  • La purge de caches temporaires ;
  • L'émission de logs secondaires en mode best-effort.

Sur ces opérations annexes, un échec ne doit pas bloquer la requête principale de l'utilisateur. Mais même dans ce cas, l'erreur doit idéalement faire l'objet d'un signalement d'observabilité discret sans impacter le retour utilisateur.


Les trois formes classiques de l'anti-pattern

Forme 1 : Le catch vide en bloc synchrone ou asynchrone

// ANTI-PATTERN 1 : LE CATCH LITTÉRALEMENT VIDE
try {
  await db.transaction.commit();
} catch (error) {
  // L'erreur disparaît corps et biens dans le néant
}

Forme 2 : Le commentaire passif en guise de traitement

// ANTI-PATTERN 2 : LE COMMENTAIRE TROMPEUR
try {
  await stripe.subscriptions.cancel(subId);
} catch (e) {
  // TODO: gérer l'erreur plus tard
}

Forme 3 : La promesse avec swallow délibéré

// ANTI-PATTERN 3 : L'ENCLUME ASYNCHRONE
persistAuditLog(auditData).catch(() => {});

Pattern recommandé : contextualiser, réparer ou propager

Pour traiter proprement les erreurs sur les chemins sensibles :

1. Propager avec contexte technique enrichi

Si la fonction courante n'est pas en mesure de corriger la situation, elle doit relancer l'exception en y adjoignant des métadonnées exploitables :

// PATTERN RECOMMANDÉ : PROPAGATION CONTEXTUALISÉE
export async function finalizeOrder(orderId: string, payload: OrderPayload) {
  try {
    return await db.orders.create({ data: payload });
  } catch (error) {
    // Journalisation exploitable avec identifiant technique stable
    console.error('[COMMANDE_ECHEC] Impossible de finaliser la commande', {
      orderId,
      cause: error instanceof Error ? error.message : String(error),
    });
    // Relance de l'erreur pour interrompre la transaction appelante
    throw new Error(`Échec de finalisation de la commande ${orderId}`);
  }
}

2. Définir des codes de retour d'erreur explicites

Si votre architecture privilégie le modèle des résultats typés (Result<T, E>) plutôt que les exceptions :

// PATTERN TYPÉ : RETOUR EXPLICITE DE STATUT D'ÉCHEC
export async function transferFunds(fromId: string, toId: string, amount: number) {
  try {
    await executeBankTransfer(fromId, toId, amount);
    return { success: true };
  } catch (err) {
    trackCriticalIncident('TRANSFER_FAILED', { fromId, toId, amount, err });
    return {
      success: false,
      error: 'Virement impossible, aucun débit n\'a été effectué.'
    };
  }
}

Ce que GVO observe lors de l'audit

Le moteur d'audit de fiabilité GVO analyse structurellement vos blocs d'interception d'erreurs :

  1. Repérage des chemins sensibles : Le moteur cible les Server Actions, Route Handlers (app/api/), webhooks, consumers et fonctions serveur.
  2. Analyse des opérations enrobées : Identification des écritures ORM, appels Stripe, envois de mails ou mutations critiques logées dans le corps du try.
  3. Inspection linéaire du bloc catch : Détection des blocs où le flux de code entre le début du catch et sa fermeture ne contient aucune instruction exécutable (rejet, log contextuel, modification d'état ou retour explicite).
  4. Verdict de preuve :
    • CONFIRMED : Une opération critique est contenue dans un try dont le catch correspondant est vide ou sans traitement.
    • DISMISSED : L'opération est accessoire, ou le bloc catch implémente un traitement d'erreur observable.

Vérifier ce point sur votre projet

GVO confronte le code et les configurations de votre application aux règles de sécurité réelles.