Debug dei programmi MQL5

Mykola Demko | 11 gennaio, 2022

Introduzione

Questo articolo è destinato principalmente ai programmatori che hanno già imparato il linguaggio ma non padroneggiano completamente lo sviluppo del programma. Evidenzia i problemi chiave che ogni sviluppatore affronta durante il debug di un programma. Quindi, cos'è il debug?

Debugging è una fase dello sviluppo del programma destinata a rilevare e rimuovere gli errori di esecuzione del programma. Durante il processo di debug, uno sviluppatore analizza un'applicazione cercando di rilevare possibili problemi. I dati per l'analisi vengono ricevuti osservando le variabili e l'esecuzione del programma (quali funzioni vengono chiamate e quando).

Esistono due tecnologie di debug complementari:

Supponiamo che tu conosca MQL5, incluse variabili, strutture, ecc. Ma non hai ancora sviluppato programmi da solo. La prima cosa che eseguirai è una compilazione (compilation). In effetti, questa è la prima fase del debug.


1. Compilazione

Compilation vuole dire tradurre un codice sorgente da un linguaggio di programmazione di alto livello a uno di livello inferiore.

Il compilatore MetaEditor traduce i programmi in un bytecode, non in un codice nativo (segui il link per i dettagli). Ciò consente di sviluppare programmi crittografati. Inoltre, un bytecode può essere lanciato sia nei sistemi operativi a 32 che a 64 bit.

Ma torniamo alla compilazione, che è la prima fase del debug. Dopo aver premuto F7 (o il pulsante Compila), MetaEditor 5 riporterà tutti gli errori che hai commesso durante la scrittura del codice. La scheda "Errori" della finestra "Casella degli strumenti" contiene la descrizione degli errori rilevati e la loro posizione. Evidenzia la riga della descrizione accanto al cursore e premi Invio per andare direttamente all'errore.

Solo due tipi di errori vengono visualizzati dal compilatore:

Gli errori di sintassi sono spesso causati da negligenza. Ad esempio, "," e ";" può essere facilmente confuso quando si dichiarano le variabili:

int a; b; // incorrect declaration

Il compilatore restituirà un errore in caso di tale dichiarazione. La dichiarazione corretta sarà la seguente:

int a, b; // correct declaration

o:

int a; int b; // correct declaration

Anche gli avvertimenti non dovrebbero essere ignorati (molti sviluppatori sono troppo incuranti nei loro confronti). Se MetaEditor 5 ha restituito avvisi durante la compilazione, verrà creato un programma ma non vi è alcuna garanzia che funzionerà come previsto.

Gli avvertimenti sono solo la punta dell'iceberg che nasconde i maggiori sforzi degli sviluppatori MQL5 per sistematizzare gli errori di battitura comuni dei programmatori.

Supponiamo di confrontare due variabili:

if(a==b) { } // if a is equal to b, then ...

Ma a causa di un errore di battitura o per dimenticanza, usi "=" invece di "==". In questo caso, il compilatore interpreta il codice nel modo seguente:

if(a=b) { } // assign b to a, if a is true, then ... (unlike MQL4, it is applicable in MQL5)

Come possiamo vedere, questo errore di battitura può cambiare drasticamente il funzionamento del programma. Pertanto, il compilatore mostrerà l'avviso per questa riga.

Riassumiamo: la compilazione è la prima fase del debug. Gli avvisi del compilatore non devono essere ignorati.

Fig. 1. Debug dei dati durante la compilazione

Fig. 1. Debug dei dati durante la compilazione.


2. Debugger

La seconda fase di debug utilizza Debugger (tasto rapido F5). Il debugger avvia il programma in modalità emulazione eseguendolo passo passo. Debugger è una nuova funzionalità di MetaEditor 5, poiché è assente in MetaEditor 4. Ecco perché non c'è esperienza di utilizzo da parte dei programmatori che passano da MQL4 a MQL5.

L'interfaccia del debugger ha tre pulsanti principali e tre ausiliari:

Questa è l'interfaccia del debugger. Ma come dovremmo usarlo? Il debug del programma può iniziare dalla riga, in cui un programmatore ha impostato una funzione di debug DebugBreak() speciale, o da un punto di interruzione che può essere impostato premendo il pulsante F9 o facendo clic su un pulsante speciale sulla barra degli strumenti:

Fig. 2. Impostazione dei breakpoint

Fig. 2. Impostazione dei punti di interruzione.

Senza punti di interruzione, il debugger eseguirà semplicemente il programma e segnalerà che il debug ha esito positivo ma non vedrai nulla. Usando DebugBreak, puoi saltare parte del codice che non ti interessa e iniziare il controllo passo passo del programma dalla riga che ritieni problematica.

Quindi, abbiamo lanciato il debugger, messo DebugBreak nel posto giusto e ora stiamo esaminando l'esecuzione del programma. Che novità ci sono? Come può aiutarci a capire cosa sta succedendo con il programma?

Prima di tutto, guarda la parte sinistra della finestra del debugger. Visualizza il nome della funzione e il numero della linea in cui ti trovi ora. Secondo, guarda il lato destro della finestra. È vuoto ma puoi inserire il nome di qualsiasi variabile nel campo Espressione. Immettere il nome della variabile per vedere il suo valore corrente nel campo Valore.

La variabile può anche essere selezionata e aggiunta usando il tasto di scelta rapida [Shift+F9] o dal menu contestuale come mostrato di seguito:

Fig. 3. Aggiunta del controllo delle variabili durante il debug

Fig. 3. Aggiunta di controllo delle variabili durante il debug.

Pertanto, puoi tenere traccia di una riga di codice, in cui ti trovi al momento, e visualizzare i valori delle variabili importanti. Analizzando tutto ciò, alla fine sarai in grado di capire se il programma sta funzionando correttamente.

Non c'è bisogno di preoccuparsi che la variabile che ti interessa sia dichiarata localmente mentre non hai ancora raggiunto la funzione in cui è dichiarata. Mentre sei al di fuori dell'ambito della variabile, avrà il valore di "Identificatore sconosciuto". Significa che la variabile non è dichiarata. Ciò non causerà l'errore del debugger. Dopo aver raggiunto l'ambito della variabile, ne vedrai il valore e il tipo.

Fig. 4. Processo di debug - visualizzazione dei valori delle variabili

Fig. 4. Processo di debug. Visualizzazione dei valori delle variabili.

Queste sono le principali funzionalità del debugger. La sezione Tester mostra cosa non può essere fatto nel debugger.


3. Profiler

Il codice profiler è un'importante aggiunta al debugger. In effetti, questa è l'ultima fase del debug del programma che consiste nella sua ottimizzazione.

Il profiler viene chiamato dal menu MetaEditor 5 facendo clic sul pulsante "Avvia profilazione". Invece dell'analisi passo passo del programma offerta dal debugger, il profiler esegue il programma. Se un programma è un indicatore o un Expert Advisor, il profiler funzionerà finché il programma non sarà scaricato. Lo scarico può essere eseguito rimuovendo un indicatore o un Expert Advisor dal grafico, nonché facendo clic su "Interrompi profilazione".

La profilazione ci fornisce importanti statistiche: quante volte ogni funzione è stata chiamata, quanto tempo è stato speso per la sua esecuzione. Forse sarai un po' confuso dalle statistiche in termini percentuali. È necessario comprendere che le statistiche non considerano le funzioni annidate. Pertanto, la somma di tutti i valori percentuali supererà notevolmente il 100%.

Ma nonostante ciò, il profiler rimane ancora un potente strumento per l'ottimizzazione dei programmi che consente agli utenti di visualizzare quale funzione dovrebbe essere ottimizzata per la velocità e dove è possibile risparmiare memoria.

Fig. 5. Risultati dell'operazione Profiler

Fig. 5. Risultati dell'operazione Profiler.


4. Interattività

Ad ogni modo, penso che le funzioni di visualizzazione dei messaggi - Print e Comment siano i principali strumenti di debug. Innanzitutto, sono molto facili da usare. In secondo luogo, i programmatori che passano a MQL5 dalla versione precedente del linguaggio li conoscono già.

La funzione "Stampa" invia il parametro passato al file di registro e alla scheda dello strumento Esperti come stringa di testo. A sinistra del testo vengono visualizzati l'ora di invio e il nome del programma che ha chiamato la funzione. Durante il debug, la funzione viene utilizzata per definire quali valori ​sono contenuti nelle variabili

Oltre ai valori delle variabili, a volte è necessario conoscere la sequenza delle chiamate di tali funzioni. Le macro "__FUNCTION__" e "__FUNCSIG__" possono essere utilizzate per questo. La prima macro restituisce una stringa con il nome della funzione da cui viene chiamata, mentre la seconda visualizza inoltre l'elenco dei parametri della funzione chiamata.

Di seguito puoi vedere l'uso delle macro:

//+------------------------------------------------------------------+
//| Example of displaying data for debugging                             |
//+------------------------------------------------------------------+
void myfunc(int a)
  {
   Print(__FUNCSIG__); // display data for debugging 
//--- here is some code of the function itself
  }

Preferisco usare la macro "__FUNCSIG__" in quanto mostra la differenza tra overloaded functions (con lo stesso nome ma parametri diversi).

Spesso è necessario saltare alcune chiamate o anche concentrarsi su una particolare chiamata di funzione. Per questi scopi, la funzione di stampa può essere protetta da una condizione. Ad esempio, print può essere chiamato solo dopo la 1013a iterazione:

//+------------------------------------------------------------------+
//| Example of data output for debugging                             |
//+------------------------------------------------------------------+
void myfunc(int a)
  {
//--- declare the static counter
   static int cnt=0;
//--- condition for the function call
   if(cnt==1013)
      Print(__FUNCSIG__," a=",a); // data output for debugging
//--- increment the counter
   cnt++;
//--- here is some code of the function itself
  }

Lo stesso può essere fatto per la funzione Comment, che visualizza i commenti nell'angolo in alto a sinistra di un grafico. Questo è un grande vantaggio, poiché non è necessario passare da nessuna parte durante il debug. Tuttavia, quando si utilizza la funzione, ogni nuovo commento cancella quello precedente. Questo può essere visto come uno svantaggio (anche se a volte è conveniente).

Per eliminare questo inconveniente si può applicare il metodo di scrittura aggiuntiva di una nuova stringa alla variabile. Innanzitutto, la variabile di tipo string viene dichiarata (nella maggior parte dei casi, globalmente) e inizializzata dal valore vuoto. Quindi, ogni nuova stringa di testo viene posizionata all'inizio con il carattere di avanzamento riga aggiunto, mentre il valore precedente della variabile viene aggiunto alla fine.

string com=""; // declare the global variable for storing debugging data
//+------------------------------------------------------------------+
//| Example of data output for debugging                             |
//+------------------------------------------------------------------+
void myfunc(int a)
  {
//--- declare the static counter
   static int cnt=0;
//--- storing debugging data in the global variable
   com=(__FUNCSIG__+" cnt="+(string)cnt+"\n")+com;
   Comment(com); // вывод информации для отладки
//--- increase the counter
   cnt++;
//--- here is some code of the function itself
  }

Qui arriviamo a un'altra opportunità per visualizzare i contenuti del programma in dettaglio: la stampa su file. Le funzioni di stampa e commento potrebbero non essere sempre adatte per grandi volumi di dati o per la stampa ad alta velocità. Il primo a volte potrebbe non avere abbastanza tempo per visualizzare le modifiche (poiché le chiamate potrebbero precedere il display causando confusione), il secondo perché funziona ancora più lentamente. Inoltre, il commento non può essere riletto ed esaminato nei dettagli.

La stampa su file è il metodo più conveniente per l'output dei dati quando è necessario controllare la sequenza delle chiamate o registrare grandi quantità di dati. Va comunque tenuto presente che la stampa non viene utilizzata ad ogni iterazione ma alla fine del file, mentre il salvataggio dei dati su variabile stringa viene utilizzato ad ogni iterazione secondo il principio sopra descritto (l'unica differenza è che il nuovi dati vengono inoltre scritti alla fine della variabile).

string com=""; // declare the global variable for storing debugging data
//+------------------------------------------------------------------+
//| Program shutdown                                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- saving data to the file when closing the program
   WriteFile();
  }
//+------------------------------------------------------------------+
//| Example of data output for debugging                             |
//+------------------------------------------------------------------+
void myfunc(int a)
  {
//--- declare the static counter
   static int cnt=0;
//--- storing debugging data in the global variable
   com+=__FUNCSIG__+" cnt="+(string)cnt+"\n";
//--- increment the counter
   cnt++;
//--- here is some code of the function itself
  }
//+------------------------------------------------------------------+
//| Save data to file                                                |
//+------------------------------------------------------------------+
void WriteFile(string name="Отладка")
  {
//--- open the file
   ResetLastError();
   int han=FileOpen(name+".txt",FILE_WRITE|FILE_TXT|FILE_ANSI," ");
//--- check if the file has been opened
   if(han!=INVALID_HANDLE)
     {
      FileWrite(han,com); // печать данных
      FileClose(han);     // закрытие файла
     }
   else
      Print("File open failed "+name+".txt, error",GetLastError());
  }

La funzione WriteFile viene chiamata in OnDeinit. Pertanto, tutte le modifiche avvenute nel programma vengono scritte nel file.

Nota: se il tuo registro è troppo grande, sarebbe opportuno memorizzarlo in diverse variabili. Il modo migliore per farlo è mettere il contenuto della variabile di testo nella cella dell'array di tipo stringa e azzerare la variabile com (preparazione alla fase successiva del lavoro).

Dovrebbe essere fatto dopo ogni 1-2 milioni di stringhe (voci non ricorrenti). Innanzitutto, eviterai la perdita di dati causata dall'overflow di variabili (a proposito, non sono stato in grado di farlo nonostante tutti i miei sforzi, poiché gli sviluppatori hanno lavorato duramente sul tipo di stringa). In secondo luogo, e il più importante, sarai in grado di visualizzare i dati in diversi file invece di aprire un file enorme nell'editor.

Per non tenere costantemente traccia della quantità di stringhe salvate, è possibile utilizzare la separazione delle funzioni per lavorare con i file in tre parti. La prima parte sta aprendo il file, la seconda sta scrivendo su file ad ogni iterazione e la terza sta chiudendo il file.

//--- open the file
int han=FileOpen("Debugging.txt",FILE_WRITE|FILE_TXT|FILE_ANSI," ");
//--- print data
if(han!=INVALID_HANDLE) FileWrite(han,com);
if(han!=INVALID_HANDLE) FileWrite(han,com);
if(han!=INVALID_HANDLE) FileWrite(han,com);
if(han!=INVALID_HANDLE) FileWrite(han,com);
//--- close the file
if(han!=INVALID_HANDLE) FileClose(han);

Ma questo metodo dovrebbe essere usato con cautela. Se l'esecuzione del programma non riesce (ad esempio, a causa di zero divide), potresti ricevere un file aperto ingestibile che può interrompere il lavoro del tuo sistema operativo.

Inoltre, sconsiglio vivamente di utilizzare l'intero ciclo open-write-close ad ogni iterazione. La mia esperienza personale dice che il tuo disco rigido morirà in pochi mesi in quel caso.


5. Tester

Quando si esegue il debug di Expert Advisor, spesso è necessario verificare l'attivazione di alcune condizioni particolari. Ma il suddetto debugger lancia un Expert Advisor solo in modalità in tempo reale e potresti aspettare parecchio tempo prima che queste condizioni siano finalmente attivate.

In effetti, condizioni di trading specifiche possono verificarsi raramente. Sappiamo però che accadono ma sarebbe una cosa assurda aspettarli per mesi. Quindi cosa possiamo fare?

Lo strategy tester di strategia può essere d'aiuto in questo caso. Le stesse funzioni Stampa e Commento vengono utilizzate per il debug. Il commento occupa sempre il primo posto per valutare la situazione, mentre la funzione Stampa viene utilizzata per un'analisi più dettagliata. Il tester memorizza i dati visualizzati nei registri del tester (directory separata per ogni agente del tester).

Per avviare un Expert Advisor al giusto intervallo, localizzo l'ora (dove si verificano gli errori, secondo me), imposto la data necessaria nel tester e la lancio in modalità di visualizzazione a tutti i tick.

Vorrei anche ricordare che ho preso in prestito questo metodo di debug da MetaTrader 4 dove era quasi l'unico modo per eseguire il debug di un programma durante la sua esecuzione.

Fig. 6. Debug con lo Strategy Tester

Fig. 6. Debug utilizzando lo Strategy Tester.


6. Debug in OOP

La programmazione orientata agli oggetti, apparsa in MQL5, ha influenzato il processo di debug. Durante il debug delle procedure, è possibile navigare facilmente nel programma utilizzando solo i nomi delle funzioni. Tuttavia, in OOP, è spesso necessario conoscere l'oggetto da cui vengono chiamati diversi metodi. È particolarmente importante quando gli oggetti sono progettati verticalmente (usando l'ereditarietà). Modelli (introdotti di recente in MQL5) possono aiutare in questo caso.

La funzione template consente di ricevere il tipo di puntatore come valore di tipo stringa.

template<typename T> string GetTypeName(const T &t) { return(typename(T)); }

Uso questa proprietà per il debug nel modo seguente:

//+------------------------------------------------------------------+
//| Base class contains the variable for storing the type             |
//+------------------------------------------------------------------+
class CFirst
  {
public:
   string            m_typename; // variable for storing the type
   //--- filling the variable by the custom type in the constructor
                     CFirst(void) { m_typename=GetTypeName(this); }
                    ~CFirst(void) { }
  };
//+------------------------------------------------------------------+
//| Derived class changes the value of the base class variable  |
//+------------------------------------------------------------------+
class CSecond : public CFirst
  {
public:
   //--- filling the variable by the custom type in the constructor
                     CSecond(void) { m_typename=GetTypeName(this); }
                    ~CSecond(void) {  }
  };

La classe base contiene la variabile per la memorizzazione del suo tipo (la variabile è inizializzata nel costruttore di ogni oggetto). La classe derivata usa anche il valore di questa variabile per memorizzare il suo tipo. Pertanto, quando viene chiamata la macro, aggiungo semplicemente la variabile m_typename ricevendo non solo il nome della funzione chiamata ma anche il tipo dell'oggetto che ha chiamato questa funzione.

Il puntatore stesso può essere derivato per un riconoscimento più accurato degli oggetti, consentendo agli utenti di differenziare gli oggetti in base ai numeri. All'interno dell'oggetto, questo viene fatto come segue:

Print((string)this); // print pointer number inside the class

All'esterno, appare come segue:

Print((string)GetPointer(pointer)); // print pointer number outside the class

Inoltre, la variabile per memorizzare il nome dell'oggetto può essere utilizzata all'interno di ogni classe. In tal caso, è possibile passare il nome dell'oggetto come parametro del costruttore durante la creazione di un oggetto. Ciò ti consentirà non solo di dividere gli oggetti per il loro numero, ma anche di capire cosa rappresenta ciascun oggetto (come li chiamerai). Questo metodo può essere realizzato in modo simile al riempimento della variabile m_typename.


7. Tracciamento

Tutti i metodi sopra menzionati si completano a vicenda e sono molto importanti per il debug. Tuttavia, esiste un altro metodo che non è così popolare: il tracciamento.

Questo metodo è usato raramente a causa della sua complessità. Quando rimani bloccato e non capisci cosa sta succedendo, il tracciamento può aiutarti.

Questo metodo consente di comprendere la struttura dell'applicazione, la sequenza e gli oggetti delle chiamate. Usando la traccia, capirai cosa c'è di sbagliato nel programma. Inoltre, il metodo fornisce una panoramica del progetto.

La traccia viene eseguita nel modo seguente. Crea due macro:

//--- opening substitution  
#define zx Print(__FUNCSIG__+"{");
//--- closing substitution
#define xz Print("};");

Questi stanno aprendo zx e chiudendo le macro xz di conseguenza. Mettiamoli a funzionare corpi che devono essere rintracciati:

//+------------------------------------------------------------------+
//| Example of function tracing                                       |
//+------------------------------------------------------------------+
void myfunc(int a,int b)
  {
   zx
//--- here is some code of the function itself
   if(a!=b) { xz return; } // exit in the middle of the function
//--- here is some code of the function itself
   xz return;
  }

Se la funzione contiene l'uscita in base alle condizioni, dovremmo impostare la chiusura xz nell'area protetta prima di ogni ritorno. Ciò impedirà l'interruzione della struttura di tracciamento.

Si noti che la macro sopra menzionata viene utilizzata solo per semplificare l'esempio. È meglio usare la stampa su file per la traccia. Inoltre, uso un trucco per stampare su file. Per vedere l'intera struttura di traccia, inserisco i nomi delle funzioni nella seguente costruzione sintattica:

if() {...}

Il file risultante è impostato con l'estensione ".mqh" che consente di aprirlo in MetaEditor e utilizzare styler [Ctrl+,] per visualizzare la struttura di tracciamento.

Il codice di tracciamento completo è mostrato di seguito:

string com=""; // declare global variable for storing debugging data
//--- opening substitution
#define zx com+="if("+__FUNCSIG__+"){\n";
//--- closing substitution
#define xz com+="};\n"; 
//+------------------------------------------------------------------+
//| Program shutdown                                      |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- //--- saving data to the file when closing the program
   WriteFile();
  }
//+------------------------------------------------------------------+
//| Example of the function tracing                                       |
//+------------------------------------------------------------------+
void myfunc(int a,int b)
  {
   zx
//--- here is some code of the function itself
   if(a!=b) { xz return; } // exit in the middle of the function
//--- here is some code of the function itself
   xz return;
  }
//+------------------------------------------------------------------+
//| Save data to file                                              |
//+------------------------------------------------------------------+
void WriteFile(string name="Tracing")
  {
//--- open the file
   ResetLastError();
   int han=FileOpen(name+".mqh",FILE_WRITE|FILE_TXT|FILE_ANSI," ");
//--- check if the file has opened
   if(han!=INVALID_HANDLE)
     {
      FileWrite(han,com); // print data
      FileClose(han);     // close the file
     }
   else
      Print("File open failed "+name+".mqh, error",GetLastError());
  }

Per iniziare a tracciare dal luogo particolare, le macro dovrebbero essere integrate da condizioni:

bool trace=0; // variable for protecting tracing by condition
//--- opening substitution
#define zx if(trace) com+="if("+__FUNCSIG__+"){\n";
//--- closing substitution
#define xz if(trace) com+="};\n";

In questo caso, sarai in grado di abilitare o disabilitare la traccia dopo aver impostato il valore "vero" o "falso" sulla variabile "traccia" dopo un determinato evento o in un determinato luogo.

Se la traccia non è già richiesta, ma potrebbe essere necessaria in seguito o non c'è abbastanza tempo per cancellare la fonte al momento, può essere disabilitata modificando i valori delle macro con quelli vuoti:

//--- substitute empty values
#define zx
#define xz

Di seguito è allegato il file con l'Expert Advisor standard contenente le modifiche per il tracciamento. I risultati della traccia possono essere visualizzati nella directory File dopo aver avviato Expert Advisor sul grafico (viene creato il file tracing.mqh). Ecco il passaggio dal testo del file risultante:

if(int OnInit()){
};
if(void OnTick()){
if(void CheckForOpen()){
};
};
if(void OnTick()){
if(void CheckForOpen()){
};
};
if(void OnTick()){
if(void CheckForOpen()){
};
};
//--- ...

Nota che la struttura delle chiamate nidificate non può essere definita chiaramente nel file appena creato inizialmente, ma sarai in grado di vedere la sua intera struttura dopo aver usato lo styler del codice. Di seguito è riportato il testo del file risultante dopo aver utilizzato lo styler:

if(int OnInit())
  {
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
if(void OnTick())
  {
   if(void CheckForOpen())
     {
     };
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
if(void OnTick())
  {
   if(void CheckForOpen())
     {
     };
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
if(void OnTick())
  {
   if(void CheckForOpen())
     {
     };
  };
//--- ...

Questo è solo il mio trucco e non un esempio di come dovrebbe essere eseguita la traccia. Ognuno è libero di eseguire il tracciamento a modo suo. La cosa principale è che la traccia rivela la struttura delle chiamate di funzione.


Nota importante sul debug

Se si implementano modifiche al codice durante il debug, utilizzare il wrapping delle chiamate di funzioni MQL5 dirette. Di seguito è come è fatto:

//+------------------------------------------------------------------+
//| Example of wrapping a standard function in a shell function      |
//+------------------------------------------------------------------+
void DebugPrint(string text) { Print(text); }

Ciò ti consentirà di cancellare facilmente il codice al termine del debug:

Lo stesso vale per le variabili utilizzate nel debug. Pertanto, prova a utilizzare variabili e funzioni dichiarate globalmente. Ciò ti eviterà di cercare costruzioni perse nelle profondità della tua applicazione.


Conclusione

Il debug è una parte importante del lavoro del programmatore. Una persona che non è in grado di eseguire il debug del programma non può essere definita programmatore. Ma il debug principale viene sempre eseguito nella tua testa. Questo articolo mostra solo alcuni metodi utilizzati nel debug. Ma senza la comprensione dei principi di funzionamento dell'applicazione, questi metodi non saranno di alcuna utilità.

Ti auguro un debug di successo!