Pourquoi la plupart des bugs sont impossibles à reproduire

Matthieu Werner
Temps de lecture : 5 min
php performance
Pourquoi la plupart des bugs sont impossibles à reproduire
Pourquoi la plupart des bugs sont impossibles à reproduire

Il y a une phrase que tout développeur a déjà entendue, ou prononcée lui-même :

“Je n’arrive pas à reproduire.”

En général, elle arrive après plusieurs tentatives, des logs ajoutés un peu partout, et une certaine frustration. Le bug existe, il est confirmé en production, parfois critique… mais en local, tout fonctionne.

On finit souvent par conclure que le bug est “intermittent”, ou pire, “impossible à reproduire”.

Dans la réalité, il ne l’est presque jamais.


Le problème n’est pas le bug

Un bug n’est pas difficile à reproduire. Il est difficile à reproduire dans les bonnes conditions.

Ce qui manque, ce n’est pas une reproduction mais une compréhension complète du contexte dans lequel il apparaît.


Le mythe de l’environnement identique

On part souvent du principe que :

  • local = staging = production

Dans les faits, les différences sont nombreuses :

  • version de PHP / Node
  • configuration du runtime
  • latence réseau
  • volumétrie de données
  • comportement des dépendances externes

Un exemple simple : une requête externe qui met 50ms en local peut en prendre 400ms en production. Cela suffit à révéler des problèmes invisibles ailleurs.


L’état caché

Beaucoup de bugs ne dépendent pas uniquement du code exécuté.

Ils dépendent de l’état du système au moment précis où le code s’exécute.

  • cache déjà rempli ou non
  • session utilisateur
  • contenu de la base
  • feature flags
  • ordre d’exécution des requêtes

Un cas classique :

if (!$cache->has($key)) {
    $value = compute();
    $cache->set($key, $value);
}

En local, cela fonctionne parfaitement.

En production, sous charge, deux requêtes peuvent passer simultanément dans le has() avant que le set() ne soit exécuté.

Résultat :

  • double computation
  • état incohérent
  • voire corruption de données

Impossible à reproduire… tant que l’on ne reproduit pas la concurrence.


Le facteur temps

Certains bugs n’apparaissent que dans des conditions temporelles très précises.

  • race conditions
  • timeouts
  • expirations de cache
  • synchronisation entre services

Un exemple typique :

$order = $repository->find($id);

if ($order->isPaid()) {
    process($order);
}

En apparence, rien de problématique, mais si l’état "paid" change entre deux requêtes concurrentes, ou via un autre service, le comportement devient non déterministe.

Ce type de bug ne dépend pas uniquement du code, mais du moment exact où il s’exécute.


Le piège des logs classiques

Quand un bug survient, le réflexe est d’ajouter des logs.

logger()->info('processing order', ['id' => $id]);

C’est utile, mais insuffisant.

Le problème n’est pas le manque de logs mais le manque de contexte.

Sans :

  • correlation id
  • état complet
  • séquence d’exécution

les logs restent des fragments difficiles à assembler.


Le problème d’observabilité

La plupart des systèmes sont mal observés, non pas par manque d’outils, mais par manque de bons signaux. Un log isolé ne suffit pas et une stacktrace non plus.

Ce qu’il faut, c’est pouvoir répondre à des questions simples :

  • quelle requête a déclenché ce bug ?
  • dans quel contexte utilisateur ?
  • avec quelles données ?
  • après quelles autres actions ?

Sans cela, chaque investigation repart de zéro.


Ce qui fonctionne réellement

Avec le temps, certaines pratiques font une différence nette.

1. Corrélation

Associer chaque requête à un identifiant unique :

$requestId = bin2hex(random_bytes(8));

Et le propager partout :

  • logs
  • appels externes
  • messages async

2. Logs structurés

Plutôt que du texte :

logger()->info('processing order ' . $id);

Préférer :

logger()->info('processing_order', [
    'order_id' => $id,
    'user_id' => $userId,
    'status' => $status,
]);

Cela permet :

  • filtrage
  • agrégation
  • analyse

3. Tracing

Comprendre le parcours complet d’une requête :

  • API → service → DB → queue → worker

Avec des outils type :

  • OpenTelemetry
  • Datadog APM
  • AWS X-Ray

4. Reproduction contrôlée

Quand possible :

  • rejouer une requête réelle
  • utiliser les mêmes données
  • simuler la concurrence

Sans cela, on ne reproduit qu’une approximation.


Ce que font les systèmes qui tiennent

La différence n’est pas dans la qualité du code, elle est dans la capacité à comprendre ce qui se passe réellement en production. Les systèmes robustes ne sont pas ceux sans bugs, ce sont ceux où les bugs sont compréhensibles.


Conclusion

Un bug n’est presque jamais “impossible à reproduire”. Il est simplement mal observé, mal contextualisé, ou dépendant de conditions que l’on ne reproduit pas.


TL;DR

  • Un bug dépend d’un contexte, pas seulement d’un code
  • Environnement, état et timing sont déterminants
  • Les logs seuls ne suffisent pas
  • L’observabilité est la clé
  • Comprendre > reproduire

Auteur

Matthieu Werner

Partager cet article

Prêt à transformer vos idées ?

Découvrez comment La Programmerie peut donner vie à vos projets avec passion, expertise et une touche de magie technologique ✨

Email

Réponse garantie sous 24h

Téléphone

Disponible 9h-18h

Rendez-vous

Parlons de votre projet

Notes techniques

Recevez nos explorations par e-mail — articles et veille, sans spam.

Nous respectons votre vie privée et ne partageons jamais vos données.