Was ist ein Schrödinbug?


52

Diese Wiki-Seite erzählt:

Ein Schrödinbug ist ein Fehler, der sich erst bemerkbar macht, nachdem jemand, der den Quellcode liest oder das Programm auf ungewöhnliche Weise verwendet, bemerkt, dass er niemals hätte funktionieren dürfen. An diesem Punkt hört das Programm sofort auf, für alle zu arbeiten, bis er behoben ist. Die Jargon-Datei fügt hinzu: "Obwohl ... das sich unmöglich anhört, passiert es, dass einige Programme seit Jahren latente Schrödinbugs beherbergen."

Worüber gesprochen wird, ist sehr vage.

Kann jemand ein Beispiel dafür geben, wie ein Schrödinbug ist (wie in einer fiktiven / realen Situation)?


15
Beachten Sie, dass das Zitat im Scherz erzählt wird.

11
Ich denke, du würdest Shrodinbug besser verstehen, wenn du etwas über Shrodingers Katze wüsstest: en.wikipedia.org/wiki/Shrodingers_cat
Eimantas

1
@Eimantas Ich bin eigentlich jetzt mehr verwirrt, aber das ist ein interessanter Artikel :)

Antworten:


82

Nach meiner Erfahrung lautet das Muster wie folgt:

  • System funktioniert oft jahrelang
  • Ein Fehler wird gemeldet
  • Der Entwickler untersucht den Fehler und findet ein Stück Code, das völlig fehlerhaft zu sein scheint, und erklärt, dass es "niemals hätte funktionieren können".
  • Der Fehler wird behoben und die Legende des Codes, der nie funktioniert haben könnte (aber jahrelang funktioniert hat), wächst

Lassen Sie uns hier logisch sein. Code, der niemals hätte funktionieren können ... hätte niemals funktionieren können . Wenn es tut Arbeit dann ist die Aussage falsch.

Also werde ich sagen, dass ein Fehler, der genau so ist, wie er beschrieben wurde (wenn man den fehlerhaften Code beachtet, funktioniert er nicht mehr), Unsinn ist.

In Wirklichkeit ist das, was passiert ist, eines von zwei Dingen:

1) Der Entwickler hat den Code nicht vollständig verstanden . In diesem Fall ist der Code normalerweise ein Durcheinander, und irgendwo in ihm besteht eine große, aber nicht offensichtliche Empfindlichkeit gegenüber externen Bedingungen (z. B. einer bestimmten Betriebssystemversion oder -konfiguration, die die Funktionsweise einiger Funktionen auf eine geringfügige, aber wichtige Weise regelt). Diese externe Bedingung wird geändert (z. B. durch ein Server-Upgrade oder eine Änderung, von der angenommen wird, dass sie nichts damit zu tun hat), wodurch der Code beschädigt wird.

Der Entwickler sieht sich dann den Code an und erklärt, da er den historischen Kontext nicht versteht oder nicht die Zeit hat, alle möglichen Abhängigkeiten und Szenarien nachzuvollziehen, dass er niemals hätte funktionieren können, und schreibt ihn neu.

In dieser Situation muss man verstehen, dass die Vorstellung, dass "es niemals hätte funktionieren können", nachweislich falsch ist (weil es so war).

Das heißt nicht, dass das Umschreiben eine schlechte Sache ist - es ist oft nicht so, während es schön ist, genau zu wissen, was falsch war. Oft ist das zeitaufwendig, und das Umschreiben des Codeabschnitts ist oft schneller und ermöglicht es Ihnen, sicherzugehen, dass Sie Dinge repariert haben.

2) Eigentlich hat es nie funktioniert, nur hat es noch niemand bemerkt . Dies ist insbesondere bei großen Systemen überraschend häufig. In diesem Fall beginnt und beginnt jemand Neues, die Dinge auf eine Weise zu betrachten, die noch niemand zuvor getan hat, oder ein Geschäftsprozess ändert sich und bringt einen zuvor geringfügigen Randfall in den Hauptprozess wird gefunden und gemeldet.

Der Entwickler sieht es sich an und erklärt, "es hätte nie funktionieren können", aber die Benutzer sagen "Unsinn, wir benutzen es schon seit Jahren" und sie haben irgendwie recht, aber etwas, das sie für irrelevant halten (und das sie normalerweise bis zum nicht erwähnen Entwickler findet den genauen Zustand , an welchem Punkt sie gehen „oh ja, wir tun das jetzt und haben nicht vor“) geändert hat.

Hier hat der Entwickler Recht - es hätte nie funktionieren können und es hat nie funktioniert.

Aber in jedem Fall ist eines von zwei Dingen wahr:

  • Die Behauptung "es hätte nie funktionieren können" ist wahr und es hat nie funktioniert - die Leute dachten nur, es würde funktionieren
  • Es hat funktioniert und die Aussage "es hätte nie funktionieren können" ist falsch und beruht auf einem (normalerweise vernünftigen) Mangel an Verständnis für den Code und seine Abhängigkeiten

1
Passiert mir so oft
Genesis

2
Toller Einblick in den Realismus dieser Situationen
StuperUser

1
Ich würde vermuten, dass es normalerweise das Ergebnis eines "WTF" -Moments ist. Ich hatte das einmal. Ich habe einen Code, den ich geschrieben habe, erneut gelesen und festgestellt, dass ein kürzlich aufgetretener Fehler dazu führen sollte, dass die gesamte App ausfällt. Nach weiterer Prüfung war eine andere Komponente, die ich geschrieben habe, so gut, dass sie die Fehler kompensierte.
Thaddee Tyl

1
@Thaddee - Ich habe das schon mal gesehen, aber ich habe auch zwei Fehler in Codemodulen gesehen, die sich gegenseitig aufriefen, so dass es tatsächlich funktionierte. Schauen Sie sich eine an und sie waren kaputt, aber zusammen waren sie in Ordnung.
Jon Hopkins

7
@ Jon Hopkins: Ich habe auch einen Fall von 2 Bugs, die sich gegenseitig annullieren, und das ist wirklich überraschend. Ich habe einen Fehler gefunden, die berüchtigte Aussage "Es hätte nie funktionieren können" ausgesprochen, tiefer gesucht, um herauszufinden, warum es überhaupt funktioniert hat, und einen weiteren Fehler gefunden, der zumindest in den meisten Fällen den ersten Fehler korrigiert hat. Ich war wirklich verblüfft von der Entdeckung und der Tatsache, dass mit nur EINEM der Bugs die Konsequenz katastrophal gewesen wäre!
Alexis Dufrenoy

54

Da jeder Code erwähnt, der niemals hätte funktionieren dürfen, möchte ich Ihnen ein Beispiel nennen, das ich vor ungefähr 8 Jahren bei einem aussterbenden VB3-Projekt gesehen habe, das in .net konvertiert wurde. Leider musste das Projekt auf dem neuesten Stand gehalten werden, bis die .net-Version vollständig war - und ich war der einzige dort, der VB3 sogar aus der Ferne verstand.

Es gab eine sehr wichtige Funktion, die Hunderte Male für jede Berechnung aufgerufen wurde - sie berechnete monatliche Zinsen für langfristige Pensionspläne. Ich werde die interessanten Teile reproduzieren.

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

Der mit einem Stern markierte Teil hatte den wichtigsten Code; es war der einzige Teil, der tatsächlich gerechnet hat. Klar, das hätte niemals funktionieren dürfen, oder?

Es dauerte eine Menge Debugging, aber ich fand schließlich die Ursache: IsYearlyInterestModewar Trueund Not IsYearlyInterestModewar auch wahr. Das liegt daran, dass irgendwo entlang der Linie jemand sie in eine Ganzzahl umgewandelt hat und sie dann in einer Funktion, die sie auf true setzen soll, inkrementiert hat (wenn sie 0 ist False, würde sie auf 1 gesetzt werden, was VB ist True, damit ich die Logik sehen kann dort), dann werfen Sie es zurück in einen Booleschen Wert. Und ich hatte einen Zustand, der niemals eintreten kann und doch die ganze Zeit auftritt.


7
Epilog: Ich habe diese Funktion nie repariert. Ich habe gerade die fehlgeschlagene Call-Site gepatcht, um 2 einzusenden, wie alle anderen.
Konfigurator

Sie meinen, es wird verwendet, wenn Leute den Code falsch interpretieren?
Pacerier

1
@Pacerier: Häufiger, wenn der Code so durcheinander ist, dass er nur aus Versehen richtig funktioniert. In meinem Beispiel wollte kein Entwickler IsYearlyInterestModesowohl als wahr als auch als nicht wahr bewerten. Der ursprüngliche Entwickler, der ein paar Zeilen hinzufügte (einschließlich einer der ifs verstand nicht wirklich, wie es funktioniert - es funktionierte einfach so, dass es gut genug war.
Konfigurator

16

Ich kenne kein reales Beispiel, aber um es mit einer Beispielsituation zu vereinfachen:

  • Ein Fehler wird eine Zeit lang nicht bemerkt, da die Anwendung den Code nicht unter Bedingungen ausführt, die dazu führen, dass er fehlschlägt.
  • Jemand bemerkt es, indem er etwas außerhalb des normalen Gebrauchs tut (oder die Quelle inspiziert).
  • Nachdem der Fehler festgestellt wurde, schlägt die Anwendung bis zur Behebung des Fehlers auch unter normalen Bedingungen fehl.

Dies kann passieren, weil der Fehler einen Zustand der Anwendung beschädigt, der unter den zuvor normalen Bedingungen zu Fehlern führt.


4
Eine Erklärung ist, dass es zufällige Fehler in der Software gab, die niemand mental miteinander verknüpfen konnte. Aus diesem Grund wurde angenommen, dass diese Fehler eine natürliche Ursache haben (z. B. zufällige Hardwarefehler). Sobald der Quellcode gelesen wurde, können die Benutzer nun alle vorherigen zufälligen Fehler mit dieser einen Ursache in Beziehung setzen und erkennen, dass dies niemals hätte funktionieren dürfen.
Rwong

4
Eine zweite Erklärung ist, dass in der Software ein Teil enthalten ist, der mit einem Chain-of-Responsibility-Muster implementiert ist. Jeder Handler ist robust geschrieben, obwohl ein Handler einen kritischen Fehler aufweist. Jetzt schlägt der erste Handler immer fehl, aber da der zweite Handler (der überlappende Zuständigkeiten aufweist) versucht, dieselbe Aufgabe auszuführen, scheint der gesamte Vorgang erfolgreich zu sein. Wenn sich im zweiten Modul etwas ändert, z. B. der Verantwortungsbereich, führt dies zu einem Gesamtfehler, obwohl sich der eigentliche Fehler an einer anderen Stelle befindet.
Rwong

13

Ein reales Beispiel. Ich kann keinen Code anzeigen, aber die meisten Leute werden sich darauf beziehen.

Wir haben eine große interne Bibliothek von Utility-Funktionen, in denen ich arbeite. Eines Tages suche ich nach einer Funktion, um eine bestimmte Sache zu tun, und ich finde, ich Frobnicate()versuche, sie zu benutzen. Uh-oh: Es stellt sich heraus, dass Frobnicate()immer ein Fehlercode zurückgegeben wird.

Wenn ich mich mit der Implementierung befasse, finde ich einige grundlegende logische Fehler Frobnicate(), die dazu führen , dass sie immer fehlschlagen. In der Quellcodeverwaltung kann ich sehen, dass die Funktion seit dem Schreiben nicht geändert wurde, was bedeutet, dass die Funktion nie wie beabsichtigt funktioniert hat . Warum ist das niemandem aufgefallen? Ich durchsuche den Rest der Quelleneintragung und stelle fest, dass alle vorhandenen Aufrufer von Frobnicate()den Rückgabewert ignorieren (und daher eigene subtile Fehler enthalten). Wenn ich diese Funktionen ändere, um den Rückgabewert wie gewünscht zu überprüfen, schlagen sie ebenfalls fehl.

Dies ist ein häufiger Fall von Bedingung Nr. 2, den Jon Hopkins in seiner Antwort erwähnt hat, und er ist in großen internen Bibliotheken bedrückend häufig.


Dies ist ein guter Grund, um das Schreiben einer internen Bibliothek zu vermeiden, wo immer eine externe verwendbar ist. Es wird mehr getestet und hat somit weit weniger böse Überraschungen (Open-Source-Bibliotheken sind vorzuziehen, weil Sie sie beheben können, wenn sie es trotzdem tun).
Jan Hudec

Ja, aber wenn Programmierer die Rückkehrcodes ignorieren, ist das nicht die Schuld der Bibliothek. (Übrigens, wann haben Sie das letzte Mal den Retcode von überprüft printf()?)
JensG

Genau deshalb wurden geprüfte Ausnahmen erfunden.
Kevin Krumwiede

10

Hier ist ein echter Schrödinbug, den ich in einem Systemcode gesehen habe. Ein Root-Daemon muss mit einem Kernel-Modul kommunizieren. Der Kernel-Code erzeugt also einige Dateideskriptoren:

int pipeFDs[1];

Richtet dann die Kommunikation über eine Pipe ein, die an eine Named Pipe angehängt wird:

int pipeResult = pipe(pipeFDs);

Das sollte nicht funktionieren. pipe()Schreibt zwei Dateideskriptoren in das Array, aber es ist nur Platz für eine. Aber für etwa sieben Jahre es tat Arbeit; Das Array befand sich zufällig vor einem ungenutzten Speicherplatz, der als Dateideskriptor verwendet wurde.

Dann musste ich eines Tages den Code auf eine neue Architektur portieren. Es hat aufgehört zu funktionieren, und der Fehler, der niemals hätte funktionieren dürfen, wurde entdeckt.


5

Eine Folge des Schrödinbugs ist der Heisenbug, der einen Fehler beschreibt, der verschwindet (oder gelegentlich auftritt), wenn versucht wird, ihn zu untersuchen und / oder zu beheben.

Heisenbugs sind mythisch kluge kleine Blighter, die laufen und sich verstecken, wenn ein Debugger geladen wird, aber aus dem Holzwerk herauskommen, wenn Sie aufgehört haben zu schauen.

In der Realität scheinen diese normalerweise auf die eine oder andere der folgenden Ursachen zurückzuführen zu sein:

  • Die Auswirkungen dieser Optimierung, mit der Code kompiliert wurde, -DDEBUGsind auf eine andere Ebene als beim Release-Build optimiert
  • Subtile Zeitunterschiede aufgrund realer Kommunikationsbusse oder Interrupts, die sich geringfügig von simulierten "perfekten" Dummy-Lasten unterscheiden

Beide unterstreichen die Bedeutung des Testens des Release-Codes auf Release-Geräten sowie des Tests von Einheiten / Modulen / Systemen mit Emulatoren.


Warum habe ich S.Lotes Antwort und Delnans Kommentar nicht bemerkt, bevor ich das gepostet habe?
Andrew

Ich habe wenig Erfahrung, aber ein paar davon gefunden. Ich habe in einer Android NDK-Umgebung gearbeitet. Als der Debugger einen Haltepunkt fand, wurden nur die Java-Threads angehalten, nicht die C ++ - Threads. Dadurch wurden einige Aufrufe möglich, da die Elemente in C ++ initialisiert wurden. Ohne Debugger würde der Java-Code schneller als C ++ sein und versuchen, Werte zu verwenden, die noch nicht initialisiert wurden.
MLProgrammer-CiM

Ich habe vor ein paar Monaten einen Heisenbug in unserer Verwendung der Django- Datenbank-API entdeckt: Wann DEBUG = Trueändert sich der Name des Arguments "parameters" in eine unformatierte SQL-Abfrage? Wir hatten es wegen der Länge der Abfrage als Schlüsselwortargument verwendet, das völlig kaputt ging, als es an der Zeit war, auf die Beta-Site zu pushen, auf derDEBUG = False
Izkata

2

Ich habe ein paar Schödinbugs gesehen und immer aus dem gleichen Grund:

Die Unternehmensrichtlinien erforderten, dass jeder ein Programm verwenden sollte.
Niemand hat es wirklich benutzt (hauptsächlich, weil es kein Training dafür gab.)
Aber sie konnten es dem Management nicht sagen. Also musste jeder sagen "Ich benutze dieses Programm seit 2 Jahren und bin bis heute noch nie auf diesen Fehler gestoßen."
Das Programm hat nie wirklich funktioniert, mit Ausnahme einer Minderheit von Benutzern (einschließlich der Entwickler, die es geschrieben haben).

In einem Fall wurde das Programm ausgiebig getestet, jedoch nicht in der realen Datenbank (die als zu vertraulich eingestuft wurde, weshalb eine gefälschte Version verwendet wurde).


1

Ich habe ein Beispiel aus meiner eigenen Geschichte, das war vor etwa 25 Jahren. Ich war ein Kind, das rudimentäre Grafikprogramme in Turbo Pascal ausführte. TP hatte eine Bibliothek mit dem Namen BGI, die einige Funktionen enthielt, mit denen Sie einen Bereich des Bildschirms in einen zeigerbasierten Speicherblock kopieren und ihn dann an einer anderen Stelle aufteilen konnten. In Kombination mit Xor-Blitting auf einem Schwarzweiß-Bildschirm können einfache Animationen erstellt werden.

Ich wollte noch einen Schritt weiter gehen und Sprites machen. Ich habe ein Programm geschrieben, mit dem große Blöcke und Steuerelemente gezeichnet wurden, um sie einzufärben. Dabei wurden diese als Pixel reproduziert. Es wurde ein einfaches Zeichenprogramm zum Erstellen von Sprites erstellt, das dann in den Speicher kopiert werden konnte. Es gab nur ein Problem: Um diese ausgeblendeten Sprites zu verwenden, mussten sie in einer Datei gespeichert werden, damit andere Programme sie lesen konnten. TP hatte jedoch keine Möglichkeit, die zeigerbasierte Speicherzuordnung zu serialisieren. Aus den Handbüchern ging hervor, dass sie nicht in eine Datei geschrieben werden konnten.

Ich habe mir einen Code ausgedacht, der erfolgreich in eine Datei geschrieben hat. Und ich habe angefangen, ein Testprogramm zu schreiben, das ein Sprite aus meinem Zeichenprogramm auf einem Hintergrund ausblendet - auf dem Weg zum Erstellen eines Spiels. Und es hat wunderbar funktioniert. Am nächsten Tag hörte es jedoch auf zu arbeiten. Es zeigte nichts als ein verstümmeltes Durcheinander. Es hat nie wieder funktioniert. Ich habe ein neues Sprite erstellt, und es hat einwandfrei funktioniert - bis es nicht mehr funktioniert hat und es wieder ein verstümmeltes Durcheinander war.

Es hat lange gedauert, aber irgendwann habe ich herausgefunden, was passiert ist. Das Zeichenprogramm hat, wie ich dachte, die kopierten Pixeldaten nicht in einer Datei gespeichert - es hat den Zeiger selbst gespeichert. Wenn das nächste Programm die Datei las, endete es mit einem Zeiger auf denselben Speicherblock - der immer noch das enthielt, was das letzte Programm dort geschrieben hatte (dies war unter MS-DOS, Speicherverwaltung war nicht vorhanden). Aber es hat funktioniert ... bis Sie einen Neustart durchgeführt haben oder irgendetwas ausgeführt haben, bei dem derselbe Speicherbereich erneut verwendet wurde, und dann kam es zu einem verstümmelten Durcheinander, weil Sie eine Reihe von Daten, die nichts mit dem Videospeicher zu tun hatten, auf den Videospeicherblock geschoben haben.

Es hätte niemals funktionieren sollen, es hätte niemals funktionieren sollen (und auf keinem echten Betriebssystem hätte es so ausgesehen), aber es tat es immer noch und sobald es kaputt war, blieb es kaputt.


0

Dies passiert die ganze Zeit, wenn Leute Debugger benutzen.

Die Debugging-Umgebung unterscheidet sich von der tatsächlichen Produktionsumgebung ohne Debugger.

Das Ausführen mit einem Debugger kann Dinge wie Stapelüberläufe maskieren, da die Stapelrahmen des Debuggers den Fehler maskieren.


Ich glaube nicht, dass es sich um den Unterschied zwischen Code handelt, der in einem Debugger ausgeführt und kompiliert wird.
Jon Hopkins

26
Das ist kein Schrödinbug, das ist ein Heisenbug .

@delnan: Es ist am Rande, IMO. Ich finde es eine unbestimmte Sache, weil es unerklärliche Freiheitsgrade gibt. Ich reserviere heisenbug gerne für Dinge, bei denen das Messen einer Sache die andere tatsächlich stört (z. B. Rennbedingungen, Optimierungseinstellungen, Einschränkungen der Netzwerkbandbreite usw.)
S.Lott,

@ S.Lott: Die Situation, die Sie beschreiben, beinhaltet die Beobachtung, dass sich Dinge ändern, indem Sie mit den Stapelrahmen oder dergleichen herumspielen. (Das schlimmste Beispiel, das ich je gesehen habe, war, dass der Debugger im Einzelschrittmodus Ladevorgänge ungültiger Segmentregisterwerte friedlich und "korrekt" ausführte. Das Ergebnis waren einige Routinen in der RTL, die trotz des Ladens eines Realmoduszeigers im geschützten Modus ausgeliefert wurden Da es nur kopiert und nicht dereferenziert wurde, hat es sich perfekt verhalten.)
Loren Pechtel

0

Ich habe noch nie einen wahren Schrödinbug gesehen und ich glaube nicht, dass sie existieren können - es wird nichts kaputt machen, wenn ich finde, dass es nichts kaputt macht.

Vielmehr hat sich etwas geändert, das einen Fehler aufgedeckt hat, der seit Ewigkeiten lauert. Was auch immer geändert wurde, wird immer noch geändert und der Fehler wird angezeigt, während gleichzeitig jemand den Fehler findet.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.