Qu'est-ce qu'un schrödinbug?


52

Cette page wiki raconte:

Un schrödinbug est un bogue qui ne se manifeste qu'après que quelqu'un qui lit le code source ou utilise le programme d'une manière inhabituelle constate qu'il n'aurait jamais dû fonctionner, puis le programme cesse immédiatement de fonctionner pour tout le monde jusqu'à ce qu'il soit corrigé. Le fichier jargon ajoute: "Bien que ... cela paraisse impossible, cela arrive; certains programmes hébergent des schrödinbugs latents depuis des années."

Ce dont on parle est très vague.

Quelqu'un peut-il donner un exemple de la situation d'un schrödinbug (comme dans une situation fictive / réelle)?


15
Notez que la citation est dite en plaisantant.

11
Je pense que vous feriez mieux de comprendre shrodinbug si vous connaissiez le chat de Shrodinger: fr.wikipedia.org/wiki/Shrodingers_cat
Eimantas Le

1
@Eimantas, je suis maintenant plus confus mais c'est un article intéressant :)

Réponses:


82

D'après mon expérience, le schéma est le suivant:

  • Le système fonctionne, souvent pendant des années
  • Une erreur est signalée
  • Le développeur étudie l'erreur et trouve un morceau de code qui semble complètement défectueux et déclare qu'il "n'aurait jamais pu fonctionner"
  • Le bogue est corrigé et la légende du code qui n'aurait jamais pu fonctionner (mais l'a été pendant des années) s'agrandit

Soyons logique ici. Un code qui n'aurait jamais pu fonctionner… n'aurait jamais pu fonctionner . Si cela a fonctionné, la déclaration est fausse.

Donc, je vais dire qu'un bogue exactement tel que décrit (observer le code défectueux l'empêche de fonctionner) est manifestement un non-sens.

En réalité, il s’agit d’une des deux choses suivantes:

1) Le développeur n'a pas bien compris le code . Dans ce cas, le code est généralement un désordre et sa sensibilité à certaines conditions externes est importante mais non évidente (par exemple, une version ou une configuration de système d'exploitation spécifique qui régit le fonctionnement de certaines fonctions de manière mineure mais significative). Cette condition externe est modifiée (par exemple par une mise à niveau du serveur ou une modification dont on pense qu'elle n'est pas liée) et, ce faisant, provoque la rupture du code.

Le développeur examine ensuite le code et, ne comprenant pas le contexte historique ou n'ayant pas le temps de retracer toutes les dépendances et scénarios possibles, a déclaré qu'il n'aurait jamais pu fonctionner et l'a réécrit.

Dans ce cas, la chose à comprendre ici est que l’idée selon laquelle "cela n’aurait jamais pu fonctionner" est manifestement fausse (car elle l’a fait).

Cela ne veut pas dire que la réécrire est une mauvaise chose - ce n'est souvent pas le cas, bien qu'il soit agréable de savoir exactement ce qui ne va pas souvent, cela prend beaucoup de temps et que la réécriture de la section de code est souvent plus rapide et vous permet de vous assurer que vous avez corrigé les choses.

2) En fait, cela n'a jamais fonctionné, mais personne ne l'a jamais remarqué . Ceci est étonnamment commun, en particulier dans les grands systèmes. Dans ce cas, une nouvelle personne commence et commence à regarder les choses comme personne ne l’a fait auparavant, ou un processus opérationnel a été modifié, ce qui a amené un cas mineur auparavant dans le processus principal, et quelque chose qui n’a jamais vraiment fonctionné (ou qui a fonctionné certains, mais pas tous). le temps) est trouvé et rapporté.

Le développeur l'examine et déclare «cela n'aurait jamais pu fonctionner», mais les utilisateurs disent «un non-sens, nous l'utilisons depuis des années» et ils ont en quelque sorte raison mais quelque chose qu'ils considèrent comme non pertinent (et qu'ils oublient généralement de mentionner développeur trouve la condition exacte à quel point ils vont « oh oui, nous faisons que maintenant et ne pas avant ») a changé.

Ici, le développeur a raison: cela n'aurait jamais fonctionné et cela n'a jamais fonctionné.

Mais dans les deux cas, l'une des deux choses est vraie:

  • L'affirmation "cela n'aurait jamais pu fonctionner" est vraie et cela n'a jamais fonctionné - les gens pensaient que c'était le cas
  • Cela a fonctionné et l'affirmation "cela n'aurait jamais pu fonctionner" est fausse et se résume à un manque de compréhension (généralement raisonnable) du code et de ses dépendances.

1
Cela m'arrive si souvent
genèse le

2
Grand aperçu du réalisme de ces situations
StuperUser

1
Je suppose que c'est généralement le résultat d'un moment "WTF". J'ai eu ça une fois. J'ai relu le code que j'avais écrit et je me suis rendu compte qu'un bogue récemment remarqué aurait dû faire tomber toute l'application. En fait, après une inspection plus poussée, un autre élément que j’ai écrit était si bon qu’il compensait les erreurs.
Thaddee Tyl

1
@Thaddee - J'ai déjà vu cela auparavant, mais j'ai également vu deux bogues dans les modules de code qui s'appelaient en s'annulant, ce qui fonctionnait réellement. Regardez l'un ou l'autre et ils étaient cassés mais ensemble ils allaient bien.
Jon Hopkins

7
@ Jon Hopkins: J'ai également eu un cas de deux bugs qui s'annulent, et c'est vraiment surprenant. J'ai trouvé un bogue, énonçant la déclaration infâme «ça n'aurait jamais pu fonctionner», j'ai cherché plus avant pourquoi cela fonctionnait quand même, et j'ai trouvé un autre bogue qui a en quelque sorte corrigé le premier, du moins dans la plupart des cas. J'ai été vraiment surpris par cette découverte et par le fait qu'avec UN seul des insectes, les conséquences auraient été catastrophiques!
Alexis Dufrenoy

54

Puisque tout le monde mentionne un code qui n'aurait jamais dû fonctionner, je vais vous donner un exemple que j'ai rencontré il y a environ 8 ans sur un projet VB3 en train de mourir converti au format .net. Malheureusement, le projet a dû être mis à jour jusqu'à la finalisation de la version .net - et j'étais le seul à même comprendre VB3 à distance.

Il y avait une fonction très importante qui était appelée des centaines de fois pour chaque calcul: elle calculait les intérêts mensuels pour les régimes de retraite à long terme. Je vais reproduire les parties intéressantes.

Function CalculateMonthlyInterest([...], IsYearlyInterestMode As Boolean, [...]) As Double
    [about 30 lines of code]
    If IsYearlyInterestMode Then
        [about 30 lines of code]
        If Not IsYearlyInterestMode Then
            [about 30 lines of code (*)]
        End If
    End If
End Function

La partie marquée d'une étoile avait le code le plus important; c'était la seule partie qui a fait le calcul réel. Clairement, cela n'aurait jamais dû fonctionner, non?

Il a fallu beaucoup de débogage, mais j’ai finalement trouvé la cause: IsYearlyInterestModec’était Trueet Not IsYearlyInterestModeétait également vrai. C'est parce que quelque part dans la ligne, quelqu'un le convertit en un entier, puis dans une fonction censée le définir comme étant le véritable incrémenté (s'il est égal à 0 car Falseil serait défini à 1, ce qui correspond à VB True, je peux donc voir la logique là-bas), puis jette-le à un booléen. Et je me suis retrouvé avec une maladie qui ne peut jamais arriver et qui arrive tout le temps.


7
Épilogue: Je n'ai jamais réglé cette fonction. Je viens de patcher le site d’appel défaillant pour en envoyer 2 comme tous les autres.
configurateur

alors vous voulez dire qu'il est utilisé quand les gens mal interprètent le code?
Pacerier

1
@ Pacerier: Le plus souvent, lorsque le code est si désordonné qu'il ne fonctionne correctement que par accident. Dans mon exemple, aucun développeur n’a voulu IsYearlyInterestModeévaluer à la fois vrai et faux; le développeur original qui a ajouté quelques lignes (dont l’une des ifs ne comprenait pas vraiment comment cela fonctionne - c’est juste arrivé, donc c’est assez bon.
configurateur

16

Je ne connais pas d'exemple du monde réel, mais simplifions-le avec un exemple de situation:

  • Un bogue n'est pas remarqué pendant un certain temps, car l'application n'exécute pas le code dans des conditions qui entraînent son échec.
  • Quelqu'un le remarque en faisant quelque chose en dehors de l'utilisation normale (ou en inspectant la source).
  • Maintenant que le bogue est remarqué, l'application échoue jusqu'à ce que les conditions normales soient respectées, jusqu'à ce que le bogue soit résolu.

Cela peut se produire car le bogue corrompra un certain état de l'application qui entraînerait des échecs dans des conditions précédemment normales.


4
Une explication est qu'il y avait eu des échecs aléatoires dans le logiciel, que personne ne pouvait se lier mentalement. Ainsi, ces erreurs ont été considérées comme étant de cause naturelle (telles que des défaillances matérielles aléatoires). Une fois que le code source est lu, les utilisateurs sont désormais en mesure de relier toutes les erreurs aléatoires antérieures à cette cause, et se rendent compte que cela n'aurait jamais dû fonctionner.
Rwong

4
Une deuxième explication est qu’une partie du logiciel est implémentée avec un modèle de chaîne de responsabilité. Chaque gestionnaire est écrit de manière robuste, bien qu’un seul ait un bogue critique. Maintenant, le premier gestionnaire échouera toujours, mais étant donné que le deuxième gestionnaire (dont les responsabilités se chevauchent) tente d'accomplir la même tâche, l'opération globale semble avoir réussi. S'il y a un changement dans le deuxième module, tel qu'un changement dans la zone de responsabilité, cela provoquera un échec global, bien que le vrai bogue se trouve à un autre emplacement.
Rwong

13

Un exemple concret. Je ne peux pas montrer de code, mais la plupart des gens vont comprendre cela.

Nous avons une grande bibliothèque interne de fonctions utilitaires où je travaille. Un jour, je cherche une fonction pour faire quelque chose en particulier et je trouve que je l’ Frobnicate()essaye. Uh-oh: il s'avère que Frobnicate()renvoie toujours un code d'erreur.

En creusant dans l'implémentation, je trouve quelques erreurs de logique de base Frobnicate()qui la font toujours échouer. Dans le contrôle de source, je constate que la fonction n'a pas été modifiée depuis sa rédaction, ce qui signifie que la fonction n'a jamais fonctionné comme prévu. Pourquoi personne ne l'a remarqué? Je cherche dans le reste de l'enrôlement source et constate que tous les appelants existants Frobnicate()ignorent la valeur de retour (et contiennent donc des bogues subtils qui leur sont propres). Si je modifie ces fonctions pour vérifier la valeur de retour comme elles le devraient, elles commencent également à échouer.

Il s'agit d'un cas courant de la condition n ° 2 que Jon Hopkins a mentionné dans sa réponse et qui est décourageant dans les grandes bibliothèques internes.


... ce qui constitue une bonne raison d'éviter d'écrire une bibliothèque interne chaque fois qu'une bibliothèque externe est utilisable. Il sera plus testé et aura donc bien moins de telles surprises (les bibliothèques à code source ouvert sont préférables, car vous pouvez les réparer si elles le font de toute façon).
Jan Hudec

Oui, mais si les programmeurs ignorent les codes de retour, ce n'est pas la faute de la bibliothèque. (Au fait, c'était quand la dernière fois que vous avez vérifié le code retour de printf()?)
JensG 21/02/2014

C'est exactement pourquoi les exceptions vérifiées ont été inventées.
Kevin Krumwiede

10

Voici un vrai Schrödinbug que j'ai vu dans un code système. Un démon racine doit communiquer avec un module du noyau. Donc, le code du noyau crée des descripteurs de fichier:

int pipeFDs[1];

établit ensuite la communication sur un tuyau qui sera attaché à un tuyau nommé:

int pipeResult = pipe(pipeFDs);

Cela ne devrait pas marcher. pipe()écrit deux descripteurs de fichier dans le tableau, mais il n’ya qu’un espace pour un. Mais pendant environ sept ans, cela a fonctionné. le tableau se trouvait avant un espace inutilisé dans la mémoire qui a été coopté pour devenir un descripteur de fichier.

Puis, un jour, j'ai dû porter le code sur une nouvelle architecture. Il a cessé de fonctionner et le bogue qui n'aurait jamais dû fonctionner a été découvert.


5

Le corollaire du Schrödinbug est le Heisenbug - qui décrit un bogue qui disparaît (ou apparaît occasionnellement) lorsqu’il tente d’enquêter et / ou de le corriger.

Les Heisenbugs sont de petits fous astucieux et mythiques qui courent et se cachent quand un débogueur est chargé, mais sortent des rouages ​​une fois que vous avez cessé de regarder.

En réalité, ils semblent généralement être causés par l'un ou l'autre des facteurs suivants:

  • l'impact de cette optimisation, où le code compilé avec -DDEBUGest optimisé à un niveau différent de celui de l'édition
  • des différences de temps subtiles dues à des bus de communication du monde réel ou des interruptions étant légèrement différentes des charges factices "parfaites" simulées

Les deux soulignent l'importance de tester le code de version sur l'équipement de version, ainsi que le test d'unité / module / système à l'aide d'émulateurs.


Pourquoi n'ai-je pas remarqué la réponse de S.Lote et le commentaire de Delnan avant de poster ceci?
Andrew

J'ai peu d'expérience mais j'ai trouvé quelques exemples. Je travaillais dans un environnement NDK Android. Lorsque le débogueur a trouvé un point d'arrêt, il n'a arrêté que les threads Java, pas ceux C ++, rendant ainsi certains appels possibles car les éléments ont été initialisés en C ++. Sans le débogueur, le code Java irait plus vite que C ++ et essaierait d'utiliser des valeurs qui n'étaient pas encore initialisées.
MLProgrammer-CiM

J'ai découvert un Heisenbug dans notre utilisation de l' API de base de données Django il y a quelques mois: Quand DEBUG = True, le nom de l'argument "parameters" d'une requête SQL brute change. Nous l'avions utilisé comme argument de mots-clés pour plus de clarté en raison de la longueur de la requête, qui a complètement cassé au moment de passer au site bêta, oùDEBUG = False
Izkata le

2

J'ai vu quelques Schödinbugs et toujours pour la même raison:

La politique de l'entreprise exigeait que tout le monde soit supposé utiliser un programme.
Personne ne l’utilisait vraiment (principalement parce qu’il n’y avait aucune formation),
mais ils ne pouvaient pas le dire à la direction. Donc tout le monde devait dire "J'utilise ce programme depuis 2 ans et je n'ai jamais rencontré ce bogue jusqu'à aujourd'hui."
Le programme n'a jamais vraiment fonctionné, sauf pour une minorité d'utilisateurs (y compris les développeurs qui l'ont écrit).

Dans un cas, le programme avait été soumis à de nombreux tests, mais pas à la base de données réelle (jugée trop sensible, une fausse version a donc été utilisée).


1

J'ai un exemple tiré de ma propre histoire, c'était il y a environ 25 ans. J'étais un enfant en train de faire de la programmation graphique rudimentaire en Turbo Pascal. TP possédait une bibliothèque appelée BGI qui comprenait certaines fonctions vous permettant de copier une région de l’écran dans un bloc de mémoire basé sur un pointeur, puis de la copier ailleurs. Combiné au xor-blitting sur un écran noir et blanc, il peut être utilisé pour une animation simple.

Je voulais aller plus loin et faire des sprites. J'ai écrit un programme qui dessinait de gros blocs et des commandes pour les colorier. Il reproduisait ces pixels en pixels, produisant ainsi un simple programme de dessin permettant de créer des sprites, qu'il pouvait ensuite copier en mémoire. Il y avait juste un problème, pour utiliser ces images-objets blites, il fallait les enregistrer dans un fichier pour que d'autres programmes puissent les lire. Mais TP n'avait aucun moyen de sérialiser l'allocation de mémoire basée sur un pointeur. Les manuels disaient carrément qu'ils ne pouvaient pas être écrits.

Je suis arrivé avec un morceau de code qui a réussi à écrire dans un fichier. Et a commencé à écrire un programme de test qui intégrait un sprite de mon programme de dessin sur un arrière-plan - sur le moyen de créer un jeu. Et cela a fonctionné, magnifiquement. Le lendemain cependant, il a cessé de fonctionner. Il a montré rien d'autre qu'un désordre tronqué. Cela n'a plus jamais fonctionné. J'ai créé un nouveau sprite, et cela a fonctionné à la perfection - jusqu'à ce que ce ne soit plus le cas, et c'était à nouveau un gâchis confus.

Cela a pris beaucoup de temps mais finalement j'ai compris ce qui se passait. Le programme de dessin ne consistait pas, comme je le pensais, à enregistrer les données de pixels copiées dans un fichier, mais à enregistrer le pointeur lui-même. Lorsque le programme suivant a lu le fichier, il s'est retrouvé avec un pointeur sur le même bloc de mémoire - qui contenait toujours ce que le dernier programme y avait écrit (c'était sur MS-DOS, la gestion de la mémoire n'existait pas). Mais cela a fonctionné ... jusqu'à ce que vous redémarriez ou que vous utilisiez tout ce qui avait réutilisé la même zone de mémoire, puis vous avez eu un désordre déformé parce que vous transfériez un tas de données totalement non liées au bloc de mémoire vidéo.

Cela n'aurait jamais dû fonctionner, il ne devrait même jamais sembler fonctionner (et sur n'importe quel système d'exploitation réel, cela n'aurait pas été le cas), mais c'était toujours le cas, et une fois qu'il s'est cassé, il est resté cassé.


0

Cela arrive tout le temps quand les gens utilisent des débogueurs.

L'environnement de débogage est différent de l'environnement de production réel - pas de débogueur -.

L'exécution avec un débogueur peut masquer des problèmes tels que les débordements de pile, car les cadres de pile du débogueur masquent le bogue.


Je ne pense pas que cela se réfère à la différence entre le code en cours d'exécution dans un débogueur et une fois compilé.
Jon Hopkins

26
Ce n'est pas un schrödinbug, c'est un heisenbug .

@delnan: C'est au bord, IMO. Je trouve cela indéterminé, car il existe des degrés de liberté inconnaissables. J'aime réserver heisenbug à des tâches où mesurer une chose en gêne (conditions de course, réglages de l'optimiseur, limitations de la bande passante du réseau, etc.)
S.Lott le

@ S.Lott: La situation que vous décrivez implique que l'observation change les choses en jouant avec les cadres de pile ou similaires. (Le pire exemple de ce type que j'ai jamais vu est que le débogueur exécutait paisiblement et correctement les charges de registres de segments non valides en mode pas à pas. Il en résultait que certaines routines de la RTL étaient livrées malgré le chargement d'un pointeur en mode réel en mode protégé. Comme il était seulement copié et non déréférencé, il se comportait parfaitement.)
Loren Pechtel

0

Je n'ai jamais vu de vrai schrodinbug et je ne pense pas qu'ils puissent exister - le trouver ne casse pas les choses.

Plutôt, quelque chose a changé qui a révélé un bogue qui se cache depuis des lustres. Ce qui a changé est toujours changé et ainsi le bogue continue à apparaître pendant que quelqu'un le trouve en même temps.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.