Che cos'è uno schrödinbug?


52

Questa pagina wiki dice:

Uno schrödinbug è un bug che si manifesta solo dopo che qualcuno ha letto il codice sorgente o utilizzato il programma in modo insolito nota che non avrebbe mai dovuto funzionare in primo luogo, a quel punto il programma smette di funzionare prontamente per tutti fino a quando non viene risolto. Il file Jargon aggiunge: "Anche se ... sembra impossibile, succede; alcuni programmi ospitano schrödinbugs latenti per anni."

Ciò di cui si parla è molto vago ..

Qualcuno può fornire un esempio di come è uno schrödinbug (come in una situazione di fantasia / vita reale)?


15
Nota che la citazione è raccontata scherzosamente.

11
Penso che faresti meglio a capire lo shrodinbug se conoscessi il gatto di Shrodinger: en.wikipedia.org/wiki/Shrodingers_cat
Eimantas,

1
@Eimantas In realtà ora sono più confuso ma questo è un articolo interessante :)

Risposte:


82

Nella mia esperienza lo schema è questo:

  • Il sistema funziona, spesso per anni
  • È stato segnalato un errore
  • Lo sviluppatore indaga sull'errore e trova un po 'di codice che sembra essere completamente difettoso e dichiara che "non avrebbe mai potuto funzionare"
  • Il bug viene corretto e cresce la legenda del codice che non avrebbe mai potuto funzionare (ma che ha funzionato per anni)

Siamo logici qui. Codice che non avrebbe mai potuto funzionare ... non avrebbe mai potuto funzionare . Se ha fatto il lavoro, allora l'affermazione è falsa.

Quindi sto per dire che un bug esattamente come descritto (che sta osservando il codice imperfetto smette di funzionare) è palesemente senza senso.

In realtà ciò che è successo è una delle due cose:

1) Lo sviluppatore non ha compreso appieno il codice . In questo caso il codice è di solito un casino e da qualche parte in esso ha una maggiore ma non ovvia sensibilità a una condizione esterna (diciamo una specifica versione o configurazione del sistema operativo che governa il funzionamento di alcune funzioni in modo minore ma significativo). Questa condizione esterna viene modificata (ad esempio da un aggiornamento o modifica del server che si ritiene non sia correlato) e, in tal modo, il codice si rompe.

Lo sviluppatore quindi esamina il codice e, non comprendendo il contesto storico o avendo il tempo di rintracciare ogni possibile dipendenza e scenario, ha dichiarato che non avrebbe mai potuto funzionare e lo riscrive.

In questa situazione, la cosa da capire qui è che l'idea che "non avrebbe mai potuto funzionare" è dimostrabilmente falsa (perché l'ha fatto).

Questo non vuol dire che riscrivere è una cosa negativa - spesso non lo è, mentre è bello sapere esattamente cosa c'era che non andava spesso che richiede tempo e riscrivere la sezione del codice è spesso più veloce e ti permette di essere sicuro di aver corretto le cose.

2) In realtà non ha mai funzionato, nessuno lo ha mai notato . Ciò è sorprendentemente comune, in particolare nei sistemi di grandi dimensioni. In questo caso qualcuno di nuovo inizia e inizia a guardare le cose in un modo in cui nessuno ha mai fatto prima, o un processo aziendale cambia portando un caso marginale in precedenza minore nel processo principale e qualcosa che non ha mai funzionato (o ha funzionato, ma non tutto il tempo) viene trovato e segnalato.

Lo sviluppatore lo guarda e dichiara "non avrebbe mai potuto funzionare" ma gli utenti dicono "sciocchezze, lo usiamo da anni" e hanno ragione, ma qualcosa che considerano irrilevante (e di solito non menzionano fino al sviluppatore trova la condizione precisa a questo punto se ne vanno "oh sì, noi non facciamo che ora e non hanno fatto prima") è cambiato.

Qui lo sviluppatore ha ragione: non avrebbe mai funzionato e mai funzionato.

Ma in entrambi i casi una delle due cose è vera:

  • L'affermazione "non avrebbe mai potuto funzionare" è vera e non ha mai funzionato - la gente pensava semplicemente di averlo fatto
  • Ha funzionato e l'affermazione "non avrebbe mai potuto funzionare" è falsa e dipende da una (generalmente ragionevole) mancanza di comprensione del codice e delle sue dipendenze

1
Mi succede così spesso
genesi,

2
Grande comprensione del realismo di queste situazioni
StuperUser

1
Immagino che di solito sia il risultato di un momento "WTF". L'ho avuto una volta. Ho riletto un po 'di codice che ho scritto e mi sono reso conto che un bug recentemente notato avrebbe dovuto far crollare l'intera app. In realtà, dopo un'ulteriore ispezione, un altro componente che ho scritto era così buono da compensare gli errori.
Thaddee Tyl,

1
@Thaddee - L'ho già visto prima, ma ho anche visto due bug nei moduli di codice che si chiamavano annullandosi a vicenda in modo che funzionasse davvero. Guarda uno dei due e si erano rotti ma insieme stavano bene.
Jon Hopkins,

7
@Jon Hopkins: ho anche avuto un caso di 2 bug che si annullano a vicenda, e questo è davvero sorprendente. Ho trovato un bug, ho pronunciato la famigerata affermazione "non avrebbe mai potuto funzionare", ho cercato più a fondo per capire perché funzionasse comunque, e ho trovato un altro bug che ha corretto il primo, almeno nella maggior parte dei casi. Sono rimasto davvero sbalordito dalla scoperta e dal fatto che con UNO dei bug, la conseguenza sarebbe stata catastrofica!
Alexis Dufrenoy,

54

Poiché tutti menzionano un codice che non avrebbe mai dovuto funzionare, ti fornirò un esempio in cui mi sono imbattuto, circa 8 anni fa, in un progetto VB3 morente che veniva convertito in .net. Sfortunatamente il progetto ha dovuto essere aggiornato fino al completamento della versione .net - e sono stato l'unico lì a capire anche da remoto VB3.

C'era una funzione molto importante che veniva chiamata centinaia di volte per ogni calcolo: calcolava l'interesse mensile per i piani pensionistici a lungo termine. Riprodurrò le parti interessanti.

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 parte contrassegnata da una stella aveva il codice più importante; è stata l'unica parte che ha effettuato il calcolo effettivo. Chiaramente questo non avrebbe mai dovuto funzionare, giusto?

Ci sono voluti molti debug, ma alla fine ho trovato la causa: IsYearlyInterestModeera Trueed Not IsYearlyInterestModeera anche vero. Questo perché da qualche parte lungo la linea qualcuno lo ha lanciato su un numero intero, quindi in una funzione che dovrebbe impostarlo su true lo ha incrementato (se è 0 per Falsesarebbe impostato su 1, che è VB True, quindi posso vedere la logica lì), quindi riportalo su un valore booleano. E mi è rimasta una condizione che non può mai accadere e tuttavia accade sempre.


7
Epilogo: non ho mai risolto quella funzione; Ho appena corretto il sito di chiamata non riuscito per inviare 2 come tutti gli altri.
configuratore

quindi vuoi dire che viene utilizzato quando le persone fraintendono il codice?
Pacerier,

1
@Pacerier: Più spesso quando il codice è così disordinato che funziona correttamente solo per caso. Nel mio esempio, nessuno sviluppatore intendeva IsYearlyInterestModevalutare sia vero che non vero; lo sviluppatore originale che ha aggiunto alcune righe (inclusa una delle ifs in realtà non ha capito come funziona - è appena successo e quindi è stato abbastanza buono.
Configuratore

16

Non conosco un esempio del mondo reale, ma per semplificarlo con una situazione di esempio:

  • Un bug non viene notato per un certo periodo, poiché l'applicazione non esegue il codice in condizioni che ne causano il fallimento.
  • Qualcuno lo nota facendo qualcosa al di fuori del normale uso (o ispezionando la fonte).
  • Ora che il bug viene notato, l'applicazione non riesce anche fino a quando le condizioni normali, fino a quando il bug non viene corretto.

Ciò può accadere perché il bug corromperà alcuni stati dell'applicazione che causano errori nelle condizioni precedentemente normali.


4
Una spiegazione è che ci sono stati degli errori casuali nel software, che nessuno è stato in grado di collegare mentalmente. Pertanto, si riteneva che quegli errori fossero di causa naturale (come guasti hardware casuali). Una volta letto il codice sorgente, le persone sono ora in grado di mettere in relazione tutti gli errori casuali precedenti con questa causa e si renderanno conto che non avrebbe mai dovuto funzionare in primo luogo.
rwong,

4
Una seconda spiegazione è che esiste una parte nel software implementata con un modello di catena di responsabilità. Ogni gestore è scritto in modo robusto, nonostante quel gestore abbia un bug critico. Ora, il primo gestore fallirà sempre, ma a causa del secondo gestore (che ha sovrapposizioni di responsabilità) tenta di eseguire lo stesso compito, l'operazione generale sembrerebbe avere successo. Se si verifica qualche cambiamento nel secondo modulo, come un cambiamento nell'area di responsabilità, ciò causerebbe un errore generale, sebbene il vero bug si trovi in ​​una posizione diversa.
rwong,

13

Un esempio di vita reale. Non riesco a mostrare il codice, ma la maggior parte delle persone si collega a questo.

Abbiamo una grande libreria interna di funzioni di utilità in cui lavoro. Un giorno cerco una funzione per fare una cosa particolare e trovo che Frobnicate()provi ad usarla. Uh-oh: si scopre che Frobnicate()restituisce sempre un codice di errore.

Scavando nell'implementazione, trovo alcuni errori logici di base Frobnicate()che lo fanno sempre fallire. Nel controllo del codice sorgente posso vedere che la funzione non è stata modificata da quando è stata scritta, il che significa che la funzione non ha mai funzionato come previsto. Perché nessuno l'ha notato? Cerco il resto della lista di arruolamento della fonte e trovo che tutti i chiamanti esistenti Frobnicate()ignorano il valore di ritorno (e quindi contengono bug sottili propri). Se cambio queste funzioni per controllare il valore restituito come dovrebbero, anche loro iniziano a fallire.

Questo è un caso comune della condizione n. 2 che Jon Hopkins ha menzionato nella sua risposta, ed è deprimentemente comune nelle grandi biblioteche interne.


... che è una buona ragione per evitare di scrivere una biblioteca interna ovunque sia utilizzabile una esterna. Sarà più testato e quindi avrà molte meno brutte sorprese (sono preferibili le librerie open-source, perché puoi sistemarle se lo fanno comunque).
Jan Hudec,

Sì, ma se i programmatori ignorano i codici di ritorno non è colpa della libreria. (A proposito, quando è stata l'ultima volta che hai controllato il codice di ricodifica di printf()?)
JensG

Questo è esattamente il motivo per cui sono state inventate le eccezioni controllate.
Kevin Krumwiede,

10

Ecco un vero Schrödinbug che ho visto in alcuni codici di sistema. Un demone root deve comunicare con un modulo kernel. Quindi il codice del kernel crea alcuni descrittori di file:

int pipeFDs[1];

quindi imposta la comunicazione su una pipe che verrà collegata a una pipe denominata:

int pipeResult = pipe(pipeFDs);

Questo non dovrebbe funzionare. pipe()scrive due descrittori di file nell'array, ma c'è solo spazio per uno. Ma per circa sette anni ha fatto il lavoro; l'array si trovava davanti a uno spazio inutilizzato nella memoria che è stato cooptato in un descrittore di file.

Quindi, un giorno, ho dovuto trasferire il codice su una nuova architettura. Ha smesso di funzionare e il bug che non avrebbe mai dovuto funzionare è stato scoperto.


5

Un corollario dello Schrödinbug è l' Heisenbug - che descrive un bug che scompare (o appare occasionalmente) quando si tenta di investigare e / o risolverlo.

Gli Heisenbugs sono mitici piccoli e intelligenti furfanti che corrono e si nascondono quando viene caricato un debugger, ma escono dal legno una volta che hai smesso di guardare.

In realtà, questi di solito sembrano essere causati da uno dei seguenti:

  • l'impatto di tale ottimizzazione, con cui il codice compilato -DDEBUGè ottimizzato a un livello diverso dalla build di rilascio
  • sottili differenze di temporizzazione dovute a bus di comunicazione del mondo reale o interruzioni leggermente diverse dai carichi fittizi "perfetti" simulati

Entrambi evidenziano l'importanza del test del codice di rilascio sull'apparecchiatura di rilascio, nonché del test unità / modulo / sistema mediante emulatori.


Perché non ho notato la risposta di S.Lote e il commento di Delnan prima di pubblicare questo?
Andrew

Ho poca esperienza ma ne ho trovati un paio. Stavo lavorando in un ambiente NDK Android. Quando il debugger ha trovato un punto di interruzione, ha bloccato solo i thread Java, non quelli C ++, rendendo possibili alcune chiamate perché gli elementi sono stati inizializzati su C ++. Se lasciato senza debugger, il codice Java andrebbe più veloce di C ++ e proverebbe a usare valori non ancora inizializzati.
MLProgrammer-CiM

Ho scoperto un Heisenbug nel nostro utilizzo l' API del database Django qualche mese fa: quando DEBUG = True, il nome dei "parametri" arg in una query SQL non elaborata cambia. Lo usavamo come parola chiave arg per chiarezza a causa della lunghezza della query, che si interruppe completamente quando era il momento di passare al sito beta, doveDEBUG = False
Izkata,

2

Ho visto alcuni Schödinbugs e sempre per lo stesso motivo:

La politica aziendale prevedeva che tutti dovessero utilizzare un programma.
Nessuno lo usava davvero (soprattutto perché non c'era formazione per questo.)
Ma non potevano dirlo alla direzione. Quindi tutti dovevano dire "Uso questo programma da 2 anni e non ho mai riscontrato questo bug fino ad oggi."
Il programma non ha mai funzionato, tranne una minoranza di utenti (inclusi gli sviluppatori che l'hanno scritto).

In un caso, il programma era stato sottoposto a numerosi test, ma non sul database reale (che era considerato troppo sensibile, quindi è stata utilizzata una versione falsa).


1

Ho un esempio della mia storia, è stato circa 25 anni fa. Ero un bambino a programmare rudimentalmente la grafica in Turbo Pascal. TP aveva una libreria chiamata BGI che includeva alcune funzioni che ti consentivano di copiare una regione dello schermo in un blocco di memoria basato su puntatore, e poi di blitarlo altrove. Combinato con lo xor blitting su uno schermo in bianco e nero, potrebbe essere usato per fare semplici animazioni.

Volevo fare un ulteriore passo avanti e fare sprite. Ho scritto un programma che disegnava grandi blocchi e controlli per colorarli, così come li hai riprodotti come pixel, producendo un semplice programma di disegno per creare sprite, che poteva poi copiare in memoria. C'era solo un problema, per usare questi sprite benedetti, avrebbero dovuto essere salvati in un file in modo che altri programmi potessero leggerli. Ma TP non aveva modo di serializzare l'allocazione di memoria basata su puntatore. I manuali hanno dichiarato chiaramente che non potevano essere scritti su file.

Ho trovato un pezzo di codice che, con successo, ha scritto su un file. E ho iniziato a scrivere un programma di test che ha generato uno sprite dal mio programma di disegno su uno sfondo, mentre andavo verso la creazione di un gioco. E ha funzionato magnificamente. Il giorno successivo, tuttavia, ha smesso di funzionare. Non mostrava altro che un pasticcio confuso. Non ha mai funzionato di nuovo. Ho creato un nuovo sprite, e ha funzionato perfettamente, fino a quando non ha funzionato, ed è stato di nuovo un pasticcio confuso.

Ci è voluto molto tempo ma alla fine ho capito cosa stava succedendo. Il programma di disegno non stava, come pensavo, salvando i dati dei pixel copiati su file - stava salvando il puntatore stesso. Quando il programma successivo ha letto il file, ha finito con un puntatore allo stesso blocco di memoria - che conteneva ancora ciò che l'ultimo programma aveva scritto lì (questo era su MS-DOS, la gestione della memoria era inesistente). Ma ha funzionato ... fino a quando non hai riavviato, o hai eseguito qualsiasi cosa che avesse riutilizzato quella stessa area di memoria, e poi hai avuto un pasticcio confuso perché stavi mandando un mucchio di dati completamente non correlati al blocco di memoria video.

Non avrebbe mai dovuto funzionare, non avrebbe mai nemmeno dovuto funzionare (e su qualsiasi sistema operativo reale non avrebbe funzionato), ma continuava a funzionare e, una volta rotto, è rimasto rotto.


0

Questo succede sempre quando le persone usano i debugger.

L'ambiente di debug è diverso dall'ambiente di produzione effettivo, senza debugger.

L'esecuzione con un debugger può mascherare cose come gli overflow dello stack perché i frame dello stack del debugger mascherano il bug.


Non penso che si riferisca alla differenza tra il codice in esecuzione in un debugger e quando compilato.
Jon Hopkins,

26
Non è uno schrödinbug, è un heisenbug .

@delnan: è al limite, IMO. Trovo che sia una cosa indeterminata perché ci sono gradi di libertà inconoscibili. Mi piace riservare heisenbug per cose in cui la misurazione di una cosa in realtà disturba un'altra (ad es. Condizioni di gara, impostazioni dell'ottimizzatore, limitazioni della larghezza di banda della rete, ecc.)
S. Lott

@ S.Lott: La situazione che descrivi implica l'osservazione che cambia le cose facendo casino con i frame dello stack o simili. (Il peggior esempio che abbia mai visto è stato che il debugger eseguiva in modo pacifico e "corretto" carichi di valori di registro di segmento non validi in modalità a passaggio singolo. Il risultato erano alcune routine in RTL che venivano spedite nonostante il caricamento di un puntatore in modalità reale mentre in modalità protetta Dal momento che veniva solo copiato e non sottoposto a dereferenziazione, si comportava perfettamente.)
Loren Pechtel,

0

Non ho mai visto un vero schrodinbug e non penso che possano esistere: scoprirlo non romperà le cose.

Piuttosto, qualcosa è cambiato che ha rivelato un insetto che si nascondeva da secoli. Qualunque cosa sia cambiata è ancora cambiata e quindi il bug continua a comparire mentre allo stesso tempo qualcuno trova il bug.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.