Debug per principianti assoluti

Senza dubbio, il codice scritto dagli sviluppatori di software non sempre funziona come previsto. A volte, funziona in modo 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 rivela magicamente tutti i problemi o "bug" presenti nel codice. Per debug si intende l'esecuzione passo a passo del codice in uno strumento di debug, ad esempio 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 efficiente di un debugger è anche una competenza che si acquisisce con il tempo e la pratica ed è in realtà un'attività fondamentale per qualsiasi sviluppatore di 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

Prima di provare a risolverlo, è utile chiarire il problema riscontrato. Probabilmente si è già riscontrato un problema nel codice, altrimenti non si starebbe leggendo questo articolo per cercare di capire come risolverlo. Prima di iniziare il debug, assicurarsi quindi di aver identificato il problema che si sta tentando di risolvere:

  • Qual era il comportamento previsto del codice?

  • 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 riscontrato durante l'esecuzione del codice, in genere un errore di qualche tipo. Uno strumento di debug è in grado di passare al punto esatto nel codice in cui si è verificata l'eccezione e può aiutare ad analizzare le possibili correzioni.

    Se è successo qualcos'altro, qual è il sintomo del problema? Si ha già un'idea del punto in cui si è verificato il problema nel codice? Nel caso, ad esempio, di codice che visualizza del testo, la visualizzazione di testo non corretto indica che i dati non sono corretti oppure che il codice che ha impostato il testo visualizzato contiene un bug di qualche tipo. L'esecuzione del codice istruzione dopo istruzione in un debugger consente di esaminare ogni modifica alle variabili per comprendere esattamente quando e come vengono assegnati valori non corretti.

Esaminare i presupposti

Prima si analizzare un bug o un errore, riflettere sui presupposti che hanno fatto in modo che ci si aspettasse un determinato risultato. I presupposti nascosti o sconosciuti possono essere identificati in modo da identificare un problema anche quando si esamina la causa del problema in un debugger. Potresti avere un lungo elenco di possibili presupposti! Di seguito sono riportate alcune domande da porsi per verificare i propri presupposti.

  • Si sta usando l'API giusta, ovvero l'oggetto, la funzione, il metodo o la proprietà corretti? L'API che si sta usando potrebbe avere un funzionamento diverso da quello che ci si aspetta. Dopo aver esaminato la chiamata API nel debugger, la correzione può richiedere un viaggio nella documentazione per identificare l'API corretta.

  • L'API viene usata correttamente? Forse è stata usata l'API giusta, ma non nel modo corretto.

  • Il codice contiene errori di digitazione? Alcuni errori di digitazione, ad esempio un semplice errore di ortografia nel nome di una variabile, possono essere difficili da vedere, specialmente quando si lavora con linguaggi in cui non è necessario dichiarare le variabili prima di usarle.

  • È stata apportata una modifica al codice e si presuppone che non sia correlato al problema visualizzato?

  • Un oggetto o una variabile doveva contenere un determinato valore o un determinato tipo di valore diverso da quello che in realtà contiene?

  • Si è a conoscenza della finalità del codice? Spesso è più difficile eseguire il debug del codice di un altro utente. Se non si tratta del proprio codice, può essere necessario dedicare un po' di tempo a conoscere il codice prima di poterne eseguire il debug in modo efficace.

    Suggerimento

    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, testando l'eventuale presenza di errori in corrispondenza di ogni punto.

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 istruzione dopo istruzione in modalità di debug per individuare il punto in cui si è verificato il problema

Quando si esegue un'app normalmente, errori e risultati non corretti sono visibili solo al termine dell'esecuzione del codice. Un programma potrebbe anche essere chiuso in modo imprevisto senza alcuna indicazione sui motivi.

Quando si esegue un'app all'interno di un debugger, detta anche modalità di debug, il debugger monitora attivamente tutto ciò che avviene durante l'esecuzione del programma. Consente anche di sospendere l'app in qualsiasi momento per esaminarne lo stato e quindi eseguire la riga di codice in base alla riga per riga per controllare ogni dettaglio man mano che si verifica.

In Visual Studio si passa alla modalità di debug usando F5 (o il comando di menu Debug>Avvia debug o avvia debug sulla barra degli strumenti di debug).Icon showing Start Debugging button. In caso di eccezioni, l'Helper eccezioni di Visual Studio visualizza il punto esatto in cui si è verificata l'eccezione e offre altre informazioni utili. Per altre informazioni su come gestire le eccezioni nel codice, vedere Tecniche e strumenti di debug.

Se non è stata generata un'eccezione, è probabile che si abbia una buona idea della posizione in cui cercare il problema nel codice. Questo passaggio è il punto in cui si usano punti di interruzione con il debugger per dare la possibilità di esaminare il codice con maggiore attenzione. I punti di interruzione sono la caratteristica più basilare ed essenziale di un 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 oppure si può posizionare il cursore su una riga e premere F9.

Per illustrare questi concetti si userà un codice di esempio che contiene 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.

  1. È necessario che Visual Studio sia installato e che sia installato il carico di lavoro Sviluppo desktop .NET.

    Se non è ancora stato installato Visual Studio, accedere alla pagina Download di Visual Studio per installarlo gratuitamente.

    Se è necessario installare il carico di lavoro ma visual Studio è già disponibile, selezionare Strumenti Recupera strumenti>e funzionalità. Verrà avviato il Programma di installazione di Visual Studio. Scegliere il carico di lavoro Sviluppo per desktop .NET, quindi scegliere Modifica.

  2. Aprire 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 App console per .NET. Scegliere Avanti. Digitare un nome di progetto come ConsoleApp_FirstApp e selezionare Avanti.

    Scegliere il framework di destinazione consigliato o .NET 8 e quindi scegliere Crea.

    Se non viene visualizzato il modello di progetto App console per .NET, passare a Strumenti Ottieni strumenti>e funzionalità, che apre il Programma di installazione di Visual Studio. Scegliere il carico di lavoro Sviluppo per desktop .NET, quindi scegliere Modifica.

    Visual Studio crea il progetto console, visualizzato in Esplora soluzioni nel riquadro destro.

  3. 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}
        }
    }
    

    La finalità di questo codice è visualizzare il nome della galassia, la distanza dalla 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 galassia, distanza, tipo di galassia.

Eseguire l'app

Premere F5 o il pulsante Icon showing Start Debugging button.Avvia debug nella barra degli strumenti debug, che si trova sopra l'editor di codice.

L'app viene avviata e il debugger non visualizza alcuna eccezione. 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

  1. Con l'app ancora in esecuzione, inserire un punto di interruzione.

    Fare clic con il pulsante destro del Console.WriteLine mouse accanto al metodo per ottenere il menu di scelta rapida e scegliere Inserisci punto>di interruzione dal menu a comparsa.

    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.

  2. Selezionare il pulsante RiavviaIcon showing RestartApp button in Debug toolbar. nella barra degli strumenti debug (CTRL + MAIUSC + F5).

    L'app verrà sospesa in corrispondenza del punto di interruzione impostato. L'evidenziazione gialla indica dove viene sospeso il debugger (la riga gialla di codice non è ancora stata eseguita).

  3. Passare il puntatore sulla variabile GalaxyType a destra e quindi, a sinistra dell'icona a forma di chiave inglese, espandere theGalaxy.GalaxyType. Si può osservare che GalaxyType contiene una proprietà MyGType e il valore della proprietà è impostato su Spiral.

    Screenshot of the Visual Studio Debugger with a line of code in yellow and a menu open below the Galaxy GalaxyType property.

    "Spiral" è effettivamente il valore corretto che ci si aspettava di stampare 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.

  4. Nello stesso codice, durante il debug, posizionare il cursore alla fine di theGalaxy.GalaxyType e modificarlo in theGalaxy.GalaxyType.MyGType. Anche se è possibile apportare la modifica, l'editor di codice mostra un errore che indica che non può compilare questo codice. In Visual Basic l'errore non viene visualizzato e questa sezione di codice funziona.

  5. Premere F11 (Esegui debug>istruzione in o il pulsante Esegui istruzione nella barra degli strumenti di debug) per eseguire la riga di codice corrente.

    F11 fa avanzare il debugger (ed esegue il codice) un'istruzione alla volta. F10 (Esegui istruzione dettagliata) è un comando simile ed entrambi sono utili per imparare a usare il debugger.

    Viene visualizzata la finestra di dialogo Modifica e continuazione che indica che le modifiche non possono essere compilate.

    Screenshot of the Visual Studio Debugger with a line of code highlighted in red and a message box with the Edit option selected.

    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 sul pulsante RiavviaIcon showing Restart app button in Debug toolbar..

  6. Selezionare Modifica nella finestra di messaggio Modifica e continuazione . Un messaggio di errore viene ora visualizzato nella finestra Elenco errori. L'errore indica che 'object' non contiene una definizione per MyGType.

    Screenshot of the Visual Studio Debugger with a line of code highlighted in red and an Error List window with two errors listed.

    Anche se ogni galassia viene impostata con un oggetto di tipo GType (che ha la MyGType proprietà ), il debugger non riconosce l'oggetto theGalaxy come oggetto di tipo GType. Cosa sta succedendo? Esaminare tutto il codice che imposta il tipo di galassia. Durante questa operazione, si noterà che la classe GType include certamente una proprietà MyGType, ma qualcosa non va. Il messaggio di errore relativo a object è l'indizio; per l'interprete del linguaggio, il tipo è un oggetto di tipo object anziché un oggetto di tipo GType.

  7. Esaminando il codice relativo all'impostazione del tipo di galassia, si nota che la proprietà GalaxyType della classe Galaxy è specificata come object invece di GType.

    public object GalaxyType { get; set; }
    
  8. Modificare il codice precedente come segue:

    public GType GalaxyType { get; set; }
    
  9. Selezionare il pulsante RiavviaIcon showing Restart app button in Debug toolbar. nella barra degli strumenti di debug (CTRL + MAIUSC + F5) per ricompilare il codice e riavviare.

    Ora, quando il debugger viene sospeso in corrispondenza di Console.WriteLine, passando il puntatore su theGalaxy.GalaxyType.MyGType si noterà che il valore è impostato correttamente.

  10. Rimuovere il punto di interruzione facendo clic sul relativo cerchio nel margine sinistro oppure fare clic con il pulsante destro del mouse e scegliere Punto di interruzione>Elimina punto di interruzione, quindi premere F5 per continuare.

    L'app viene eseguita e visualizza l'output. È bello, ma si nota una cosa. Ci si aspettava che la piccola galassia della nuvola magellanica si presenta come una galassia irregolare nell'output della console, ma non mostra alcun tipo di galassia.

    Tadpole  400,  Spiral
    Pinwheel  25,  Spiral
    Cartwheel, 500,  Lenticular
    Small Magellanic Cloud .2,
    Andromeda  3,  Spiral
    Maffei 1,  Elliptical
    
  11. Impostare un punto di interruzione su questa riga di codice prima dell'istruzione switch (prima dell'istruzione Select in Visual Basic).

    public GType(char type)
    

    Il tipo di galassia viene impostato in questo codice. Esaminarlo quindi in dettaglio.

  12. Selezionare il pulsante RiavviaIcon showing Restart app button in Debug toolbar. nella barra degli strumenti debug (CTRL + MAIUSC + F5) per riavviare.

    Il debugger viene sospeso sulla riga di codice in cui è stato impostato il punto di interruzione.

  13. Passare il puntatore sulla variabile type. Si noterà il valore S (dopo il codice carattere). Si è interessati a un valore di I, come si sa che è un tipo di galassia irregolare.

  14. Premere F5 e passare di nuovo il puntatore sulla variabile type. Ripetere questo passaggio finché non si vedrà il valore I nella variabile type.

    Screenshot of the Visual Studio Debugger with a line of code in yellow and a window with the type variable value of 73 I.

  15. A questo punto, premere F11 (Esegui debug>istruzione in).

  16. Premere F11 fino a quando non si arresta sulla riga di codice nell'istruzione switch per il valore 'I' (Select istruzione per Visual Basic). Qui si noterà un evidente problema derivante da un errore di digitazione. È previsto che il codice passi a dove viene impostato MyGType come tipo di galassia irregolare, ma il debugger ignora completamente questo codice e si sospende nella default sezione dell'istruzione switch (Else istruzione in Visual Basic).

    Screenshot showing the typo error.

    Esaminando il codice, si nota un errore di digitazione nell'istruzione case 'l'. Il valore dovrebbe essere case 'I'.

  17. Selezionare nel codice per case 'l' e sostituirlo con case 'I'.

  18. Rimuovere il punto di interruzione e quindi selezionare il pulsante Riavvia per riavviare l'app.

    A questo punto i bug sono stati risolti e viene visualizzato l'output previsto.

    Premere un tasto qualsiasi per terminare l'app.

Riepilogo

Quando si nota un problema, usare il debugger e i comandi di esecuzione, ad esempio F10 e F11, per trovare l'area di codice con il problema.

Nota

Se è difficile identificare l'area di codice in cui si verifica il problema, impostare un punto di interruzione nel codice in esecuzione prima del punto in cui si verifica il problema e quindi usare i comandi di esecuzione fino a individuarlo. È anche possibile usare punti di analisi per registrare i messaggi nella finestra Output. Esaminando i messaggi registrati (e prestando particolare attenzione a quelli non ancora registrati), è spesso possibile isolare l'area di codice con il problema. Per restringere la ricerca potrebbe essere necessario ripetere il processo diverse volte.

Dopo aver trovato l'area di codice con il problema, usare il debugger per analizzarlo. Per trovare la causa di un problema, ispezionare il codice problematico durante l'esecuzione dell'app nel debugger:

  • Esaminare le variabili e verificare se contengono il tipo di valori che devono contenere. Se si trova un valore non valido, individuare il punto in cui è stato impostato. A questo scopo, può essere necessario riavviare il debugger, controllare lo stack di chiamate o entrambi.

  • Controllare se l'applicazione esegue il codice previsto. Nell'applicazione di esempio, ad esempio, è previsto che il codice per l'istruzione switch imposti il tipo di galassia su Irregolare, ma l'app ha ignorato il codice a causa dell'errore di digitazione.

Suggerimento

Un debugger aiuta a trovare i bug. Uno strumento di debug sarà in grado di individuare i bug automaticamente solo se conosce la finalità del codice. Uno strumento può conoscere la finalità del codice solo se lo sviluppatore esprime tale finalità. Per farlo, occorre scrivere unit test.

Passaggi successivi

In questo articolo si sono appresi alcuni concetti di debug generali. In seguito, è possibile apprendere di più sul debugger.