Quale processo usi normalmente quando tenti di eseguire il debug di un problema / problema / bug con il tuo software? [chiuso]


15

Molte persone sembrano considerare il debug come un'arte, piuttosto che una scienza. Per quelli qui che lo trattano come una scienza, piuttosto che un'arte - quale processo (s) usi normalmente di fronte a un nuovo problema / bug / problema?

Risposte:


13

In termini molto generali, quello che faccio è:

  1. Prova a isolare il problema. Pensa a cosa è cambiato quando il bug è apparso per la prima volta. A cosa stai lavorando? Quale parte del codice stavi cambiando? Il 99% dei miei bug è stato risolto in questo modo. Di solito è qualcosa di sciocco.

  2. Se ho un'idea di dove sia il problema, dai un'occhiata al codice che sembra essere la causa. Leggilo. Leggilo anche a voce alta. Mi chiedo: "Cosa sto cercando di ottenere?". Per alcuni tipi di problemi: potrebbe avere alcuni effetti collaterali o potrebbe essere influenzato dal codice in qualche altro posto in un modo a cui non avevo pensato?

  3. Prova in vari modi per analizzare cosa non va, dove e quando (vedi sotto).

  4. Se non ho ancora idea, controllo se una versione precedente della mia fonte ha lo stesso problema, provo a trovare quando nella mia timeline di sviluppo è apparso il problema per la prima volta. Per fare questo devi lavorare con un buon sistema di controllo della versione, come git (git ha una funzione chiamata bisect esattamente per questo tipo di debug).

  5. Se ancora non ne ho idea, fai una pausa ... in realtà spesso aiuta.

  6. Torna al tavolo da disegno - rivedi come dovrebbe funzionare il tuo programma e se questo ha davvero senso.

Dipende davvero dal tipo di problema, ma supponendo che io abbia un'idea generale di dove potrebbe essere il problema, quindi:

  • Se sospetto che il problema si trovi in ​​una parte del codice / modifica recente, provo prima a rimuovere / commentare / modificare o qualsiasi altra cosa per far scomparire il bug rendendo il codice più semplice, quindi riportare il codice problematico e prendere un buona visione.

  • Esegui un debugger con punti di interruzione (se possibile) e dai un'occhiata a come i miei dati sembrano cercare di trovare quando inizia a comportarsi male, per avere un'idea migliore di dove le cose vanno male.


1
+1 per fare una pausa. I problemi più difficili diventano più difficili solo quando sei frustrato e durante la sesta ora esegui il debug. Sapere quando fare una pausa è una delle abilità di debug più utili che ho ottenuto.
Brad Gardner,

Risposta fantastica. Non posso fare di meglio.
EricBoersma,

1
Proprio come il mio approccio, ma hai dimenticato la parte in cui chiedi a un collega di dare una rapida occhiata e notano immediatamente l'errore di ortografia ...
ChrisAnnODell

1
Risposta eccellente. Voglio solo aggiungere che un'oncia di prevenzione vale una libbra di cura. Una grande parte del mio processo di debug è mentre sto programmando in primo luogo, faccio solo piccole modifiche incrementali e compilo, collaudo e commetto localmente tra di loro. In questo modo se appare improvvisamente un bug, la probabile lista dei sospetti è molto piccola e facile da vedere con un bzr qdiffcomando.
Karl Bielefeldt,

8

Cerco di utilizzare lo sviluppo test-driven ( TDD ). Scrivo un test che replica il bug, quindi provo a far passare il test. A volte l'atto di scrivere il test aiuta a trovare il bug.

Questo mi tiene fuori dal debugger per la maggior parte del tempo e fornisce test di regressione per impedire la reintroduzione del bug.

Alcuni link:


4
Penso che questa risposta sia estremamente incompleta. Non capisco tanti voti positivi.
Alex

1
Ottiene solo molti voti perché include l'acronimo magico: TDD.
Bjarke Freund-Hansen,

@Alex - Ho aggiunto alcuni link. Quello "Trova un ERRORE, Scrivi un TEST" ha un esempio. Posso approfondire questo, ma è davvero così semplice.
TrueWill,

7

Ci sono un certo numero di definizioni per la parola scienza, ma sembra che tu possa riferirti a quello che potrebbe essere definito più accuratamente il " metodo scientifico ". Il metodo scientifico potrebbe essere riassunto come l'osservazione di alcuni fenomeni (presumibilmente un bug o un comportamento inatteso del programma), la formulazione di un'ipotesi o ipotesi per spiegare il comportamento e la sperimentazione più probabile per dimostrarlo (scrivere un test che riproduca il problema in modo affidabile).

I tipi di bug (fenomeni) che possono verificarsi sono praticamente infiniti e alcuni non richiedono necessariamente un processo ben definito. Ad esempio, a volte osservi un bug e sai immediatamente cosa lo ha causato semplicemente perché conosci molto bene il codice. Altre volte, sai che dato un certo input (azione, serie di passaggi, ecc.), Si verifica un risultato errato (crash, output errato, ecc.). In questi casi, spesso non richiede molto pensiero "scientifico". Qualche pensiero può aiutare a ridurre lo spazio di ricerca, ma un metodo comune è semplicemente quello di scorrere il codice in un debugger e vedere dove le cose sono andate male.

Le situazioni che trovo più interessanti e forse degne di un processo scientifico sono quelle in cui ti viene consegnato un risultato finale e ti viene chiesto di spiegare come è successo. Un esempio evidente di questi è una discarica. Puoi caricare il dump di arresto anomalo e osservare lo stato del sistema e il tuo compito è spiegare come è arrivato in quello stato. Il dump di crash (o core) può mostrare un'eccezione, un deadlock, un errore interno o uno stato "indesiderabile" come definito dall'utente (ad esempio, lentezza). Per queste situazioni, seguo generalmente i passaggi in questo senso:

  • Osservazione ristretta : studiare le informazioni che circondano direttamente il problema specifico, se applicabile. Le cose ovvie qui sono lo stack di chiamate, le variabili locali se riesci a vederle, le righe di codice che circondano il problema. Questo tipo di studio specifico sulla posizione non è sempre applicabile. Ad esempio, lo studio di un sistema "lento" potrebbe non avere una posizione di partenza ovvia come questa, ma un incidente o una situazione di errore interno avranno probabilmente un punto di interesse immediato e ovvio. Un passaggio specifico qui potrebbe essere quello di utilizzare strumenti come windbg (esegui! Analizza -v su un dump di crash caricato e guarda cosa ti dice).

  • Ampia osservazione : studiare altre parti del sistema. Esaminare lo stato di tutti i thread nel sistema, esaminare eventuali informazioni globali (numero di utenti / operazioni / articoli, transazioni / processi / widget attivi, ecc.), Informazioni di sistema (SO), ecc. Se l'utente ha fornito dettagli esterni , pensa a quelli associati a ciò che hai osservato. Ad esempio, se ti hanno detto che il problema si verifica ogni martedì pomeriggio, chiediti cosa potrebbe significare.

  • supporre: Questa è la parte veramente divertente (e non sono faceto sul fatto che sia divertente). Spesso richiede una grande quantità di pensiero logico al contrario. Può essere molto divertente pensare a come il sistema è arrivato allo stato attuale. Sospetto che questa sia la parte che molte persone considerano un'arte. E suppongo che potrebbe essere se il programmatore inizia a lanciare casualmente cose per vedere cosa si attacca. Ma con l'esperienza, questo può essere un processo abbastanza ben definito. Se pensi molto logicamente a questo punto, è spesso possibile definire possibili insiemi di percorsi che hanno portato a un determinato stato. So che siamo nello stato S5. Perché ciò accada, è necessario che si verifichino S4a o S4b e forse S3 prima di S4a, ecc. Più spesso, possono esserci più elementi che potrebbero portare a un determinato stato. A volte può essere utile scrivere su un blocco per appunti un semplice diagramma di flusso o di stato o una serie di passaggi relativi al tempo. I processi attuali qui varieranno notevolmente a seconda della situazione, ma in questo momento il pensiero serio (e il riesame delle fasi precedenti) spesso forniranno una o più risposte plausibili. Si noti inoltre che una parte estremamente importante di questo passaggio è l'eliminazione di cose impossibili. La rimozione dell'impossibile può aiutare a tagliare lo spazio della soluzione (ricorda cosa ha detto Sherlock Holmes su ciò che resta dopo aver eliminato l'impossibile). Si noti inoltre che una parte estremamente importante di questo passaggio è l'eliminazione di cose impossibili. La rimozione dell'impossibile può aiutare a tagliare lo spazio della soluzione (ricorda cosa ha detto Sherlock Holmes su ciò che resta dopo aver eliminato l'impossibile). Si noti inoltre che una parte estremamente importante di questo passaggio è l'eliminazione di cose impossibili. La rimozione dell'impossibile può aiutare a tagliare lo spazio della soluzione (ricorda cosa ha detto Sherlock Holmes su ciò che resta dopo aver eliminato l'impossibile).

  • Esperimento : in questa fase, prova a riprodurre il problema in base alle ipotesi derivate nel passaggio precedente. Se hai preso il pensiero serio nel passaggio precedente, questo dovrebbe essere molto semplice. A volte "imbroglio" e modifico la base di codice per aiutare un determinato test. Ad esempio, recentemente stavo indagando su un incidente che ho concluso proveniva da una condizione di gara. Per verificarlo, ho semplicemente messo un Sleep (500) tra un paio di righe di codice per consentire a un altro thread di fare le sue cose cattive al momento "giusto". Non so se questo è permesso nella scienza "reale", ma è perfettamente ragionevole nel codice che possiedi.

Se riesci a riprodurlo, è probabile che tu abbia quasi finito (tutto ciò che resta è il semplice passo per risolverlo ... ma è per un altro giorno). Assicurati di controllare il nuovo test nel sistema di test di regressione. E dovrei sottolineare che intendevo che quella precedente affermazione sul fatto che fosse semplice essere ironici. La ricerca di una soluzione e la sua attuazione possono richiedere un ampio lavoro. Ritengo che la correzione di un bug non faccia parte del processo di debug ma sia piuttosto uno sviluppo. E se la correzione è del tutto implicata, dovrebbe richiedere una certa quantità di progettazione e revisione.


La maggior parte dei bug che ho visto non sono stati riproducibili in modo affidabile e, per il sottoinsieme che erano, la maggior parte richiedeva ancora un lavoro di debug significativo dopo essere stati riprodotti, prima che potesse iniziare qualsiasi lavoro per risolverli. Anche se invece di dire "riesci a riprodurlo", dici, "riesci a restringere un test unitario che esercita chiaramente il bug", direi che il lavoro di debug non è finito. Per me, il debug è terminato quando entrambi ho una correzione, posso provare a risolvere il problema e ho prove affidabili che la mia correzione è ciò che risolve effettivamente le cose.
Blueberryfields

Sono d'accordo che può essere un bel po 'di lavoro per risolverlo. Stavo davvero usando il sarcasmo nelle mie parole "semplice passaggio per risolverlo", ma questo non viene molto bene nel tipo.

4

Prova a ridurre il test case. Quando è abbastanza piccolo, di solito è più facile individuare il codice corrispondente che causa il problema.

È probabile che un nuovo check-in stia causando il problema e che la build giornaliera precedente andasse bene. In tal caso, il registro delle modifiche dal controllo del codice sorgente dovrebbe aiutarti a decidere chi catturare.

Inoltre, se ti interessa C / C ++, considera di eseguire valgrind o purificare per isolare i problemi relativi alla memoria.


2

La parte più difficile del debug è isolare il problema, in particolare quando il problema viene nascosto sotto diversi livelli. Al college ho studiato la registrazione musicale, e stranamente c'era una lezione di Studio Electronics che si applica direttamente qui. Userò il debug di un ambiente di studio come illustrazione del processo di debug sistematico.

  1. Metti alla prova i tuoi contatori. Utilizzando un tono di prova a una tensione calibrata nota, lo strumento dovrebbe leggere "U" (guadagno unitario). Traduzione: se i tuoi strumenti sono rotti, non puoi usarli per capire cos'altro non va.
  2. Testare ogni fase componente / guadagno lavorando all'indietro dalla fine. Utilizzando lo stesso tono di prova applicato all'ingresso dello stage, non dovrebbero esserci cambiamenti all'uscita dello stage. Traduzione: isolando ogni oggetto dall'output all'indietro, stiamo creando fiducia nel nostro codice fino a quando non troviamo il punto in cui è incasinato. Se i tuoi strumenti impiegano alcuni livelli per segnalare il problema, devi sapere che i livelli intermedi non stanno contribuendo ad esso.

Il codice di debug non è davvero così diverso. Il debug è molto più semplice quando il codice genera un'eccezione. È possibile tracciare all'indietro dalla traccia dello stack di quell'eccezione e impostare i punti di interruzione nelle posizioni chiave. Di solito subito dopo aver impostato una variabile o sulla linea che chiama il metodo che genera l'eccezione. È possibile che uno o più valori non siano corretti. Se non è giusto (un null quando non dovrebbe esserci, o il valore è fuori range), allora è un processo per scoprire perché non è giusto. I punti di interruzione in un IDE sono equivalenti ai punti di prova elettronici (progettati per la sonda di un contatore per controllare il circuito).

Ora, una volta che avrò attraversato quella parte difficile della scoperta di dove sia il mio vero problema, scriverò alcuni test unitari per verificarlo in futuro.


2

Con i cattivi bug che faccio fatica a rintracciare nel tardo pomeriggio, la mia strategia più efficace è alzarmi e andarmene per qualche minuto. Di solito nuove idee su possibili fonti di errore iniziano a fluire dopo soli 30 secondi.


2

Per un approccio più pratico:

  1. Se il bug è correlato a un'eccezione non gestita, guarda la traccia dello stack. Il riferimento nullo, l'indice fuori dai limiti ecc. E le tue eccezioni definite sono le più comuni, puoi assegnare questo bug a uno sviluppatore junior, è probabilmente facile e una buona esperienza di apprendimento.

  2. Se ciò non accade su ogni macchina, è probabilmente una forma di problema di gara / threading. Questi sono super divertenti da rintracciare, mettere il tuo programmatore senior annoiato su di esso. Un sacco di registrazione, buona conoscenza e buoni strumenti lo fanno.

  3. Un'altra grande classe di bug è quando il team di test o il / i cliente / i non gradiscono un comportamento particolare. Ad esempio, a loro non piace che tu decida di visualizzare gli ID utente o che durante la ricerca non ottieni il completamento automatico. Si tratta di bug autentici, considera la possibilità di avere una migliore gestione del prodotto e gli sviluppatori con una visione più ampia. Uno sviluppatore dovrebbe impiegare un tempo relativamente breve per "risolvere" questo problema se costruisce il sistema pensando all'espansione.

  4. L'80% di tutti gli altri bug viene risolto disponendo di buoni sistemi di registrazione e raccogliendo informazioni sufficienti per risolverli. Utilizza la traccia incorporata con più livelli di sistemi di registrazione complessi come Log4Net / Log4J

  5. i bug delle prestazioni sono una categoria a parte, la regola golder qui è "misura prima, correggi dopo!", e rimarrai sorpreso nel vedere quanti sviluppatori indovinano dove si trova il problema e vai subito a risolverlo solo per vedere successivamente una riduzione del 3-4% nei tempi di risposta.


Se potessi fare +1 su ognuno di quei 5 individualmente, lo farei!
jmort253,

1

Ho due approcci di flusso:

  1. Dividi il problema dato in parti più piccole e poi conquista ogni parte più piccola seguendo il Divide and ConquerParadigma.
  2. Ogni volta che sono in dubbio riguardo a valori diversi da quelli che ho appena stampato i valori delle variabili per vedere cosa sta esattamente entrando e uscendo dalla variabile.

Questo approccio mi ha aiutato la maggior parte delle volte.

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.