Debug per principianti assoluti
Invariabilmente, il codice che scriviamo come sviluppatori di software non fa sempre quello che ci aspettavamo che facesse. A volte fa qualcosa di completamente diverso! Quando si verifica l'imprevisto, l'attività successiva consiste nel capire perché e, anche se si potrebbe essere tentati di continuare a guardare il codice per ore, è più semplice ed efficiente usare uno strumento di debug o un debugger.
Un debugger, purtroppo, non è qualcosa che può magicamente rivelare tutti i problemi o i "bug" nel codice. Il debugging significa eseguire il codice passo per passo con uno strumento di debug come Visual Studio, per trovare il punto esatto in cui è stato commesso un errore di programmazione. Si comprende quindi quali correzioni è necessario apportare nel codice e negli strumenti di debug spesso consentono di apportare modifiche temporanee in modo da poter continuare a eseguire il programma.
L'uso di un debugger è anche una competenza che richiede tempo e pratica per imparare, ma è in definitiva un'attività fondamentale per ogni sviluppatore software. In questo articolo vengono presentati i principi di base del debug e vengono forniti suggerimenti per iniziare.
Chiarire il problema ponendosi le domande giuste
Consente di chiarire il problema riscontrato prima di provare a risolverlo. Ci aspettiamo che tu abbia già incontrato un problema nel tuo codice, altrimenti non saresti qui a cercare di capire come risolverlo. Quindi, prima di avviare il debug, assicurarsi di aver identificato il problema che si sta tentando di risolvere:
Cosa ci si aspetta che il codice faccia?
Che cosa è successo invece?
Se si verifica un errore (eccezione) durante l'esecuzione dell'app, può essere una buona cosa! Un'eccezione è un evento imprevisto rilevato durante l'esecuzione del codice, in genere un errore di qualche tipo. Uno strumento di debug consente di passare alla posizione esatta nel codice in cui si è verificata l'eccezione e può essere utile per analizzare le possibili correzioni.
Se è successo qualcos'altro, qual è il sintomo del problema? Si sospetta già dove si è verificato questo problema nel codice? Ad esempio, se il codice visualizza testo, ma il testo non è corretto, si sa che i dati non sono corretti o che il codice che imposta il testo visualizzato presenta un qualche tipo di bug. Analizzando il codice passo passo in un debugger, è possibile esaminare ogni modifica alle variabili per individuare esattamente quando e come vengono assegnati valori non corretti.
Esamina le tue supposizioni
Prima di analizzare un bug o un errore, considerare i presupposti che hanno determinato un determinato risultato. I presupposti nascosti o sconosciuti possono ostacolare l'identificazione di un problema, anche quando si sta osservando direttamente la causa del problema in un debugger. Potresti avere un lungo elenco di possibili presupposti! Ecco alcune domande da porsi per sfidare i tuoi presupposti.
Si sta usando l'API corretta, ovvero l'oggetto, la funzione, il metodo o la proprietà corretti? Un'API in uso potrebbe non eseguire le operazioni che si ritiene. Dopo aver esaminato la chiamata API nel debugger, la correzione può richiedere un viaggio nella documentazione per identificare l'API corretta.
Si usa correttamente un'API? Forse hai usato l'API corretta ma non l'hai usata nel modo giusto.
Il codice contiene errori di digitazioni? Alcuni errori di digitazione, ad esempio un semplice errore di ortografia di un nome di variabile, possono essere difficili da vedere, soprattutto quando si usano linguaggi che non richiedono che le variabili vengano dichiarate prima di essere usate.
È stata apportata una modifica al codice e si presuppone che non sia correlato al problema visualizzato?
Si prevede che un oggetto o una variabile contenga un determinato valore (o un determinato tipo di valore) diverso da quello che è realmente successo?
Si conosce la finalità del codice? Spesso è più difficile eseguire il debug del codice di un altro utente. Se il codice non è il tuo, è possibile che sia necessario dedicare tempo a imparare esattamente cosa fa il codice prima di poter eseguirne il debug in modo efficace.
Mancia
Quando si scrive codice, iniziare con piccole dimensioni e iniziare con il codice che funziona. (Un buon codice di esempio è utile qui). In alcuni casi, è più facile correggere un set di codice di grandi dimensioni o complicato iniziando con una piccola parte di codice che illustra l'attività principale che si sta tentando di ottenere. È quindi possibile modificare o aggiungere codice in modo incrementale, verificando in ogni punto gli errori.
Interrogando i presupposti, è possibile ridurre il tempo necessario per individuare un problema nel codice. È anche possibile ridurre il tempo necessario per risolvere un problema.
Eseguire il codice in modalità di debug per individuare dove si è verificato il problema
Quando si esegue normalmente un'app, vengono visualizzati errori e risultati non corretti solo dopo l'esecuzione del codice. Un programma potrebbe anche terminare in modo imprevisto senza indicare perché.
Quando si esegue un'app all'interno di un debugger, chiamata anche modalità di debug , il debugger monitora attivamente tutto ciò che accade durante l'esecuzione del programma. Consente anche di sospendere l'app in qualsiasi momento per esaminarne lo stato e quindi eseguire il codice riga per riga per osservare ogni dettaglio man mano che avviene.
In Visual Studio si passa alla modalità di debug usando
Se non hai ottenuto un'eccezione, probabilmente hai una buona idea di dove cercare il problema nel tuo codice. Questo passaggio consente di usare punti di interruzione con il debugger per offrire all'utente la possibilità di esaminare il codice con maggiore attenzione. I punti di interruzione sono la funzionalità di base e essenziale del debug affidabile. Un punto di interruzione indica dove Visual Studio deve sospendere il codice in esecuzione in modo da poter esaminare i valori delle variabili o il comportamento della memoria, la sequenza in cui viene eseguito il codice.
In Visual Studio è possibile impostare rapidamente un punto di interruzione facendo clic sul margine sinistro accanto a una riga di codice. In alternativa, posizionare il cursore su una riga e premere F9.
Per illustrare questi concetti, verranno illustrati alcuni esempi di codice che contengono già diversi bug. Si usa C#, ma le funzionalità di debug si applicano a Visual Basic, C++, JavaScript, Python e ad altri linguaggi supportati. Viene fornito anche il codice di esempio per Visual Basic, ma gli screenshot si trovano in C#.
Creare un'app di esempio (con alcuni bug)
Successivamente, si crea un'applicazione con alcuni bug.
È necessario che Visual Studio sia installato e che sia installato il carico di lavoro sviluppo di applicazioni desktop .NET
. Se non hai già installato Visual Studio, vai alla pagina di download di Visual Studio per installarlo gratuitamente.
Se devi installare il pacchetto di lavoro ma Visual Studio è già disponibile, seleziona Tools>Get Tools and Features. Viene avviato il programma di installazione di Visual Studio. Scegliere il carico di lavoro sviluppo di applicazioni desktop .NET, quindi scegliere Modifica.
Apri Visual Studio.
Nella finestra iniziale scegliere Crea un nuovo progetto. Digitare console nella casella di ricerca, selezionare C# o Visual Basic come linguaggio e quindi scegliere Console App per .NET. Scegliere Avanti. Digitare ConsoleApp_FirstApp come nome del progetto e selezionare Avanti.
Se si usa un nome di progetto diverso, sarà necessario modificare il valore dello spazio dei nomi in modo che corrisponda al nome del progetto quando si copia il codice di esempio.
Puoi scegliere il framework di destinazione consigliato o .NET 8, e poi selezionare Crea.
Se non vedi il modello di progetto Console App per .NET, vai su Tools>Get Tools and Features, che apre il Visual Studio Installer. Scegliere il carico di lavoro sviluppo di applicazioni desktop .NET, quindi scegliere Modifica.
Visual Studio crea il progetto console visualizzato in esplora soluzioni nel riquadro destro.
In Program.cs (o Program.vb) sostituire tutto il codice predefinito con il codice seguente. Selezionare prima la scheda del linguaggio corretta, C# o Visual Basic.
using System; using System.Collections.Generic; namespace ConsoleApp_FirstApp { class Program { static void Main(string[] args) { Console.WriteLine("Welcome to Galaxy News!"); IterateThroughList(); Console.ReadKey(); } private static void IterateThroughList() { var theGalaxies = new List<Galaxy> { new Galaxy() { Name="Tadpole", MegaLightYears=400, GalaxyType=new GType('S')}, new Galaxy() { Name="Pinwheel", MegaLightYears=25, GalaxyType=new GType('S')}, new Galaxy() { Name="Cartwheel", MegaLightYears=500, GalaxyType=new GType('L')}, new Galaxy() { Name="Small Magellanic Cloud", MegaLightYears=.2, GalaxyType=new GType('I')}, new Galaxy() { Name="Andromeda", MegaLightYears=3, GalaxyType=new GType('S')}, new Galaxy() { Name="Maffei 1", MegaLightYears=11, GalaxyType=new GType('E')} }; foreach (Galaxy theGalaxy in theGalaxies) { Console.WriteLine(theGalaxy.Name + " " + theGalaxy.MegaLightYears + ", " + theGalaxy.GalaxyType); } // Expected Output: // Tadpole 400, Spiral // Pinwheel 25, Spiral // Cartwheel, 500, Lenticular // Small Magellanic Cloud .2, Irregular // Andromeda 3, Spiral // Maffei 1, 11, Elliptical } } public class Galaxy { public string Name { get; set; } public double MegaLightYears { get; set; } public object GalaxyType { get; set; } } public class GType { public GType(char type) { switch(type) { case 'S': MyGType = Type.Spiral; break; case 'E': MyGType = Type.Elliptical; break; case 'l': MyGType = Type.Irregular; break; case 'L': MyGType = Type.Lenticular; break; default: break; } } public object MyGType { get; set; } private enum Type { Spiral, Elliptical, Irregular, Lenticular} } }
Il nostro intento per questo codice è quello di visualizzare il nome della galassia, la distanza alla galassia e il tipo di galassia tutto in un elenco. Per eseguire il debug, è importante comprendere la finalità del codice. Ecco il formato per una riga dall'elenco che si vuole visualizzare nell'output:
nome della galassia, distanza, tipo di galassia.
Eseguire l'app
Premere F5 o il pulsante Avvia debug pulsante nella barra degli strumenti debug, che si trova sopra l'editor di codice.
L'app si avvia e non ci sono eccezioni mostrate dal debugger. Tuttavia, l'output visualizzato nella finestra della console non è quello previsto. Ecco l'output previsto:
Tadpole 400, Spiral
Pinwheel 25, Spiral
Cartwheel, 500, Lenticular
Small Magellanic Cloud .2, Irregular
Andromeda 3, Spiral
Maffei 1, Elliptical
Tuttavia, viene visualizzato questo output:
Tadpole 400, ConsoleApp_FirstApp.GType
Pinwheel 25, ConsoleApp_FirstApp.GType
Cartwheel, 500, ConsoleApp_FirstApp.GType
Small Magellanic Cloud .2, ConsoleApp_FirstApp.GType
Andromeda 3, ConsoleApp_FirstApp.GType
Maffei 1, 11, ConsoleApp_FirstApp.GType
Esaminando l'output e il codice, sappiamo che GType
è il nome della classe che archivia il tipo di galassia. Stiamo cercando di mostrare il tipo di galassia effettivo (ad esempio "Spiral"), non il nome della classe!
Eseguire il debug dell'app
Con l'app ancora in esecuzione, inserire un punto di interruzione.
Nel ciclo
foreach
, fare clic con il pulsante destro del mouse accanto al metodoConsole.WriteLine
per aprire il menu contestuale e selezionare Breakpoint>Inserisci Breakpoint dal sottomenu.foreach (Galaxy theGalaxy in theGalaxies) { Console.WriteLine(theGalaxy.Name + " " + theGalaxy.MegaLightYears + ", " + theGalaxy.GalaxyType); }
Quando si imposta il punto di interruzione, viene visualizzato un punto rosso nel margine sinistro.
Come si nota un problema nell'output, si avvia il debug esaminando il codice precedente che imposta l'output nel debugger.
Selezionare l'icona Riavvia nella barra degli strumenti di debug (Ctrl + Shift + F5).
L'app viene sospesa nel punto di interruzione impostato. L'evidenziazione gialla indica dove viene sospeso il debugger (la riga gialla di codice non è ancora stata eseguita).
Posiziona il cursore sulla variabile
GalaxyType
sulla destra e quindi, a sinistra dell'icona della chiave, espanditheGalaxy.GalaxyType
. Si noterà cheGalaxyType
contiene una proprietàMyGType
e il valore della proprietà è impostato suSpiral
."Spiral" è in realtà il valore corretto che ci si aspettava di visualizzare nella console! È quindi un buon punto di partenza che è possibile accedere al valore in questo codice durante l'esecuzione dell'app. In questo scenario si usa l'API non corretta. Vediamo se è possibile risolvere questo problema durante l'esecuzione del codice nel debugger.
Nello stesso codice, durante il debug, posiziona il cursore alla fine di
theGalaxy.GalaxyType
e modificalo intheGalaxy.GalaxyType.MyGType
. Anche se è possibile apportare la modifica, l'editor di codice mostra un errore (riga ondulata rossa). In Visual Basic l'errore non viene visualizzato e questa sezione di codice funziona.Premere F11 (Debug>Passa dentro o il pulsante Passa dentro nella barra degli strumenti di debug) per eseguire la riga di codice corrente.
F11 avanza il debugger (e esegue il codice) un'istruzione alla volta. F10 (Step Over) è un comando simile ed entrambi sono utili per imparare a usare il debugger.
Quando si tenta di avanzare con il debugger, viene visualizzata la finestra di dialogo Ricaricamento a caldo che indica che le modifiche non possono essere compilate.
Viene visualizzata la finestra di dialogo Modifica e continuazione che indica che le modifiche non possono essere compilate.
Nota
Per eseguire il debug del codice di esempio di Visual Basic, ignorare i passaggi successivi fino a quando non viene richiesto di fare clic sull'icona Riavvia.
Selezionare Modifica nella finestra del messaggio di Hot Reload o di Modifica e Continua . Ora visualizzi un messaggio di errore nella finestra elenco errori. L'errore indica che il
'object'
non contiene una definizione perMyGType
.Anche se ogni galassia viene impostata con un oggetto di tipo
GType
(che ha la proprietàMyGType
), il debugger non riconosce l'oggettotheGalaxy
come oggetto di tipoGType
. Cosa sta succedendo? Si vuole esaminare qualsiasi codice che imposta il tipo di galassia. Quando si esegue questa operazione, si noterà che la classeGType
ha sicuramente una proprietà diMyGType
, ma qualcosa non è corretto. Il messaggio di errore suobject
risulta essere l'indizio; all'interprete linguistico, il tipo sembra essere un oggetto di tipoobject
anziché un oggetto di tipoGType
.Esaminando il codice correlato all'impostazione del tipo di galassia, si trova la proprietà
GalaxyType
della classeGalaxy
viene specificata comeobject
anzichéGType
.public object GalaxyType { get; set; }
Modificare il codice precedente come segue:
public GType GalaxyType { get; set; }
Selezionare l'icona
pulsante nella barra degli strumenti di debug (Riavvia che mostra il pulsante Riavvia app nella barra degli strumenti Debug. CTRL MAIUSC F5 ) per ricompilare il codice e riavviare.Ora, quando il debugger viene sospeso in
Console.WriteLine
, è possibile passare il puntatore del mouse sutheGalaxy.GalaxyType.MyGType
e verificare che il valore sia impostato correttamente.Rimuovere il punto di interruzione facendo clic sul cerchio del punto di interruzione nel margine sinistro oppure facendo clic con il pulsante destro del mouse e scegliendo punto di interruzione>Elimina punto di interruzione), quindi premere F5 per continuare.
L'app si esegue e visualizza l'output. È bello, ma si nota una cosa. Si prevedeva che la galassia Piccola Nube di Magellano si presentasse come una galassia irregolare nell'output della console, ma non mostra nessun tipo di galassia.
Tadpole 400, Spiral Pinwheel 25, Spiral Cartwheel, 500, Lenticular Small Magellanic Cloud .2, Andromeda 3, Spiral Maffei 1, Elliptical
Impostare un punto di interruzione su questa riga di codice prima dell'istruzione
switch
(prima dell'istruzioneSelect
in Visual Basic).public GType(char type)
Questo codice è dove è impostato il tipo di galassia, quindi vogliamo dare un'occhiata più da vicino.
Selezionare l'icona Riavvia nella barra degli strumenti di debug (Ctrl + Maiusc + F5) per riavviare.
Il debugger viene sospeso nella riga di codice in cui si imposta il punto di interruzione.
Passare il puntatore del mouse sulla variabile
type
. Viene visualizzato un valore diS
(seguendo il codice carattere). Sei interessato a un valore diI
, dato che sai che è un tipo di galassia irregolare.Premere F5 e passare di nuovo il puntatore del mouse sulla variabile
type
. Ripetere questo passaggio finché non viene visualizzato un valore diI
nella variabiletype
.A questo punto, premere F11 (Debug>Passo nel).
Premere F11 fino a quando non si interrompe la riga di codice nell'istruzione
switch
per un valore "I" ( istruzioneSelect
per Visual Basic). Qui viene visualizzato un problema chiaro risultante da un errore di digitatura. Ci si aspettava che il codice avanzasse al punto in cui impostaMyGType
come tipo di galassia irregolare, ma il debugger ignora completamente questo codice e si ferma sulla sezionedefault
dell'istruzioneswitch
(istruzioneElse
in Visual Basic).Esaminando il codice, viene visualizzato un errore di digitazione nell'istruzione
case 'l'
. Deve esserecase 'I'
.Selezionare il codice
case 'l'
e sostituirlo concase 'I'
.Rimuovere il punto di interruzione e quindi selezionare il pulsante Riavvia per riavviare l'app.
I bug sono stati corretti ora e viene visualizzato l'output previsto.
Premere un tasto qualsiasi per completare l'app.
Sommario
Quando viene visualizzato un problema, usare il debugger e i comandi dei passaggi , ad esempio F10 e F11 per trovare l'area del codice con il problema.
Nota
Se è difficile identificare l'area del codice in cui si verifica il problema, impostare un punto di interruzione nel codice eseguito prima che si verifichi il problema e quindi usare i comandi di passaggio fino a visualizzare il manifesto del problema. È anche possibile usare
Quando si trova l'area del codice con il problema, usare il debugger per analizzare. Per individuare la causa di un problema, esaminare il codice del problema durante l'esecuzione dell'app nel debugger:
Controllare le variabili e verificare se contengono il tipo di valori che devono contenere. Se si trova un valore non valido, scoprire dove è stato impostato il valore non valido (per trovare dove è stato impostato il valore, potrebbe essere necessario riavviare il debugger, esaminare lo stack di chiamate o entrambi).
Controllare se l'applicazione sta eseguendo il codice previsto. Nell'applicazione di esempio, ci aspettavamo che il codice per l'istruzione
switch
impostasse il tipo di galassia su Irregolare, ma l'app ha saltato il codice a causa dell'errore di digitazione.
Mancia
Si usa un debugger per individuare i bug. Uno strumento di debug può trovare bug per te solo se conosce l'intento del tuo codice. Uno strumento può conoscere la finalità del codice solo se lo sviluppatore esprime tale finalità. Scrivere unit test è il modo di farlo.
Passaggi successivi
In questo articolo sono stati appresi alcuni concetti generali relativi al debug. Successivamente, è possibile iniziare a ottenere altre informazioni sul debugger.