English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Depuração dos programas do MQL5

Depuração dos programas do MQL5

MetaTrader 5Exemplos | 24 março 2014, 09:26
4 431 0
Mykola Demko
Mykola Demko

Introdução

Este artigo é destinado principalmente aos programadores que já tenham aprendido a linguagem, mas ainda não tenham dominado completamente o desenvolvimento do programa. Ele destaca as principais questões que todo desenvolvedor lida ao depurar um programa. Então, o que é depuração?

Depuração (debugging) é um estágio no desenvolvimento do programa destinado a detectar e remover os erros de execução do programa. Durante o processo de depuração, um desenvolvedor analisa uma aplicação tentando detectar possíveis problemas. Os dados para análise são recebidos através da observação das variáveis e da execução do programa (quais funções são ativadas e quando).

Existem duas tecnologias de depuração complementares:

  • Utilizando o Depurador - utilitário que mostra a execução passo a passo do programa desenvolvido.
  • Exposição interativa dos estados das variáveis e das invocações das funções em uma tela, no jornal ou em um arquivo.

Vamos presumir que você conheça o MQL5, incluindo as variáveis, as estruturas, etc. Mas que você ainda não tenha desenvolvido programas sozinho. A primeira coisa que você irá realizar é uma compilação. De fato, esse é o primeiro estágio da depuração.


1. Compilação

Compilação é a tradução de um código-fonte de uma linguagem de programação de alto nível para uma de nível mais baixo.

O compilador MetaEditor traduz os programas para um bytecode, não um código nativo (siga o link para detalhes). Isso permite o desenvolvimento de programas criptografados. Além disso, um bytecode pode ser lançado em sistemas operacionais de 32 e 64 bits.

Mas vamos voltar para a compilação, que é o primeiro estágio da depuração. Após pressionar F7 (ou o botão Compilar), o MetaEditor 5 relatará todos os erros que você cometeu ao escrever o código. A guia "Erros" da janela da "Caixa de ferramentas" contém a descrição dos erros detectados e sua localização. Destaque a linha da descrição pelo cursor e pressione Enter para ir diretamente para o erro.

Apenas dois tipos de erros são mostrados pelo compilador:

  • Erros sintáticos (mostrados em vermelho) - um código-fonte não pode ser compilado até que eles sejam eliminados.
  • Avisos (mostrados em amarelo) - um código será cumprido, mas seria melhor corrigir tais erros.

Erros da sintaxe são normalmente causados por descuido. Por exemplo, "," e ";" pode ser facilmente confundido ao definir as variáveis:

int a; b; // incorrect declaration

O compilador retornará um erro no caso de tal declaração. A declaração correta será a seguinte:

int a, b; // correct declaration

Ou:

int a; int b; // correct declaration

Os avisos também não devem ser ignorados (muitos desenvolvedores são muito negligentes com eles). Se o MetaEditor retornou avisos durante a compilação, um programa será criado, mas não há garantia de que ele funcionará como você planejou.

Os avisos são apenas a ponta do iceberg escondendo os grandes esforços dos desenvolvedores do MQL5 para sistematizar os erros comuns de digitação dos programadores.

Suponha que você comparará duas variáveis:

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

Mas como resultado de um erro de digitação ou devido a esquecimento, você use "=" ao invés de "==". Nesse caso, o compilador interpreta o código da seguinte maneira:

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

Como você pode ver, esse erro de digitação pode mudar o funcionamento do programa dramaticamente. Portanto, o compilador mostrará o aviso para essa linha.

Vamos resumir: a compilação é o primeiro estágio da depuração. Os avisos do compilador não devem ser ignorados.

Fig. 1. Depurando dados durante a compilação

Fig. 1. Dados da depuração durante a compilação.


2. Depurador

O segundo estágio da depuração é o uso do Depurador (tecla de atalho F5). O depurador lança seu programa no modo de emulação executando o mesmo passo a passo. O depurador é um novo recurso do MetaEditor 5 que está ausente no MetaEditor 4. Esta é a razão pela qual não existe experiência no uso dele pelos programadores que mudam do MQL4 para o MQL5.

A interface do depurador tem três botões principais e três auxiliares:

  • Iniciar [F5] - inicia a depuração.
  • Pausar [Break] - pausa a depuração.
  • Parar [Shift+F5] - para a depuração.
  • Intervir [F11] - o usuário é movido para dentro da função ativada nesta linha.
  • Ignorar [F10] - o depurador ignora um corpo da função ativada nesta cadeia e move para a próxima linha.
  • Sair [Shift+F11] - um usuário sai de um corpo da função em que ele ou ela está atualmente localizado.

Essa é a interface do depurador. Mas como devemos utilizá-la? A depuração do programa deve começar de uma linha na qual um programador tenha configurado uma função de depuração especial DebugBreak(), ou de um ponto de interrupção que pode ser configurado ao pressionar o botão F9 ou ao clicar em um botão especial na barra de ferramentas:

Fig. 2. Configurando os pontos de interrupção

Fig. 2. Configurando os pontos de interrupção.

Sem os pontos de interrupção, o depurador simplesmente executará o programa e relatará que a depuração foi bem sucedida, mas você não verá nada. Utilizando o DebugBreak, você pode ignorar alguns dos códigos que você não está interessado e começar a verificação passo a passo do programa a partir da linha que você considerar problemática.

Então, lançamos o depurador, colocamos o DebugBreak no lugar certo e estamos verificando a execução do programa agora. Qual o próximo passo? Como isso pode nos ajudar a entender o que está acontecendo com o programa?

Primeiramente, olhe para a parte esquerda da janela do depurador. Ela mostra o nome da função e o número da linha onde você está localizado agora. Depois, olhe para o lado direito da janela. Ele está vazio, mas você pode inserir o nome de qualquer variável no campo Expressão. Insira o nome da variável para ver o valor atual no campo Value (valor).

A variável também pode ser selecionada e adicionada utilizando as teclas de atalho [Shift+F9] ou a partir do menu de contexto como mostrado a seguir:

Fig. 3. Adicionando a observação da variável durante a depuração

Fig. 3. Adicionando a observação da variável durante a depuração.

Assim, você pode rastrear a linha do código na qual você está localizado no momento e visualizar os valores das variáveis importantes. Analisando tudo isso, você será finalmente capaz de entender se o programa está operando corretamente.

Não é necessário se preocupar se a variável que você está interessado é definida localmente enquanto você ainda não tenha alcançado a função na qual qual ela é definida. Enquanto você estiver fora do escopo da variável, ela terá o valor de "Identificador desconhecido". Isso significa que a variável não está definida. Isso não causará erro no depurador. Após alcançar o escopo da variável, você verá seu valor e tipo.

Fig. 4. Processo de depuração - visualizando os valores da variável

Fig. 4. Processo de depuração. Visualizando os valores da variável.

Esses são os principais recursos do depurador. A seção do examinador mostra o que não pode ser feito no depurador.


3. Perfilador

O Perfilador de código é uma adição importante ao depurador. De fato, esse é o último estágio da depuração do programa consistindo em sua otimização.

O perfilador é ativado do menu do MetaEditor 5 ao clicar no botão "Iniciar a criação de perfil". Ao invés da análise passo a passo do programa oferecida pelo depurador, o perfilador executa o programa. Se um programa é um indicador ou um Consultor Especialista, o perfilador funcionará até que o programa seja descarregado. A descarga deve ser realizada pela remoção de um indicador ou de um Consultor Especialista do gráfico, assim como ao clicar no "Parar a criação de perfil".

A criação de perfil nos fornece estatísticas importantes: quantas vezes cada função foi ativada, quanto tempo foi gasto em sua execução. Talvez, você fique um pouco confuso com as estatísticas em termos percentuais. É necessário entender que as estatísticas não consideram as funções agrupadas. Portanto, a soma de todos os valores percentuais excederão consideravelmente 100%.

Mas, apesar desse fato, o perfilador ainda continua uma poderosa ferramenta para a otimização de programas permitindo aos usuários visualizarem qual função deve ser otimizada para velocidade e onde você pode poupar alguma memória.

Fig. 5. Resultados da operação do perfilador

Fig. 5. Resultados da operação do perfilador.


4. Interatividade

De qualquer modo, eu considero as funções de exibição de mensagem - Print e Comment as principais ferramentas de depuração. Primeiro, elas são muito fáceis de usar. Segundo, os programadores mudando para o MQL5 a partir da versão anterior da linguagem já as conhecem.

A função "Imprimir" envia o parâmetro passado para o arquivo de registro e a aba de ferramentas do Consultor como uma cadeia de texto. O tempo de envio e o nome do programa que ativou a função são exibidos à esquerda do texto. Durante a depuração, a função é usada para definir quais valores estão contidos nas variáveis.

Além dos valores das variáveis, às vezes, é necessário saber a sequência das ativações daquelas funções. "__FUNCTION__" e "__FUNCSIG__" macros podem ser usadas para isso. O primeiro macro retorna uma cadeia com o nome da função a partir da qual ela é ativada, enquanto o segundo exibe adicionalmente a lista dos parâmetros da função ativada.

A seguir você pode ver o uso dos macros:

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

Eu prefiro usar o macro "__FUNCSIG__" , pois ele mostra a diferença entre as Funções de sobrecarga (possui o mesmo nome, mas diferentes parâmetros).

Normalmente é necessário ignorar algumas ativações ou mesmo focar na ativação de uma função em particular. Para este fim, a função Imprimir pode ser protegida por uma condição. Por exemplo, imprimir pode ser ativada apenas após a 1013ª iteração:

//+------------------------------------------------------------------+
//| 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
  }

O mesmo pode ser feito com a função Comentar que mostra os comentários no canto superior esquerdo do gráfico. Essa é uma grande vantagem, pois você não precisa mudar para nenhum outro lugar durante a depuração. No entanto, ao usar a função, cada novo comentário apaga o anterior. Isso pode ser visto como uma desvantagem (apesar de ser conveniente às vezes).

Para eliminar essa inconveniência, o método de escrita adicional de uma nova cadeia para a variável pode ser adotado. Primeiro, a variável do tipo string (cadeia) é definida (na maioria dos casos, globalmente) e inicializada pelo valor vazio. Assim, cada nova cadeia de texto é colocada no início com o caractere de alimentação da linha adicionado, enquanto o valor anterior da variável é adicionado ao final.

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
  }

Aqui trazemos outra oportunidade de visualizar os conteúdos do programa em detalhes - imprimindo para o arquivo. As funções Imprimir e Comentar podem não ser sempre adequadas para grandes volumes de dados ou impressão de alta velocidade. A primeira pode, às vezes, não ter tempo suficiente para mostrar as mudanças (pois as ativações podem acontecer antes da exibição causando confusão), a segunda - porque opera ainda mais devagar. Além disso, o comentário não pode ser relido e verificado em detalhes.

Imprimir para o arquivo é o método mais conveniente de saída dos dados quando você precisa verificar a sequência das ativações ou registrar uma grande quantidade de dados. No entanto, não se deve esquecer que a impressão não é usada a cada iteração, mas ao final do arquivo, enquanto o arquivamento dos dados para a variável de cadeia é utilizado a cada iteração de acordo com o princípio descrito acima (a única diferença é que os novos dados são adicionalmente escritos ao final da variável).

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());
  }

A função WriteFile é ativada no OnDeinit. Portanto, todas as mudanças que aconteceram no programa estão escritas no arquivo.

Nota: se seu registro é muito extenso, seria prudente armazená-lo em várias variáveis. A melhor maneira de fazer isso é colocar os conteúdos da variável de texto para a célula do arranjo do tipo cadeia e zerar a variável (preparação para o próximo estágio do trabalho).

Isso deve ser feito após cada 1-2 milhões de cadeias (entradas não recorrentes). Primeiro, você evitará a perda de dados causada pelo excesso da variável (a propósito, eu não fui capaz de fazer isso apesar de todos os meus esforços, pois os desenvolvedores trabalharam duro no tipo de cadeia). Segundo, e mais importante - você será capaz de exibir os dados em vários arquivos ao invés de abrir um arquivo enorme no editor.

Para não acompanhar constantemente a quantidade de cadeias salvas, você pode usar a separação das funções para trabalhar com arquivos em três partes. A primeira parte é a abertura do arquivo, a segunda é a escrita para o arquivo a cada iteração e a terceira é o fechamento do arquivo.

//--- 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);

Mas esse método deve ser usado com cautela. Se a execução de seus programas falhar (por exemplo, devido à divisão por zero), você deve receber um arquivo aberto incontrolável que pode interromper o trabalho de seu sistema operacional.

Eu também não recomendo usar o ciclo abre-escreve-fecha a cada iteração. Minha experiência pessoal diz que seu disco rígido estragará em alguns meses nesse caso.


5. Examinador

Ao depurar os Consultores Especialistas, você normalmente precisa verificar a ativação de alguma condição especial. Mas o depurador mencionado acima lança um Consultor Especialista apenas no modo de tempo real e você deve esperar por um longo tempo enquanto essas condições são finalmente ativadas.

De fato, as condições de negociação específicas devem ocorrer raramente. No entanto, sabemos que elas acontecem, mas seria um absurdo esperar por elas durante meses. Então o que podemos fazer?

O Testador de estratégia pode ajudar nesse caso. As mesmas funções Imprimir e Comentar são usadas para a depuração. A função Comentar sempre leva o primeiro lugar para avaliar a situação, enquanto a função Imprimir é usada para uma análise mais detalhada. O examinador armazena os dados exibidos nos registros do examinador (diretórios separados para cada agente examinador).

Para lançar um Consultor Especialista no intervalo certo, eu localizo o tempo (onde as falhas ocorrem, em minha opinião), configuro a data necessária no examinador e o executo no modo de visualização em todos os pontos.

Eu também gostaria de mencionar que peguei emprestado esse método de depuração do MetaTrader 4 onde esse é era quase o único jeito de depurar um programa durante sua execução.

Fig. 6. Depuração utilizando o Testador de Estratégia

Fig. 6. Depuração utilizando o Testador de Estratégia.


6. Depurando no OOP

A programação orientada ao objeto que apareceu no MQL5 influenciou os processo de depuração. Ao depurar os procedimentos, você pode navegar facilmente no programa utilizando apenas os nomes das funções. No entanto, no OOP, é normalmente necessário conhecer o objeto a partir do qual os métodos diferentes são ativados. É sobretudo importante quando os objetos são desenvolvidos verticalmente (utilizando a herança). Modelos (recentemente introduzida no MQL5) pode ajudar nesse caso.

A função modelo permite receber o tipo de ponteiro como um valor do tipo de cadeia.

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

Eu utilizo essa propriedade para depurar da seguinte maneira:

//+------------------------------------------------------------------+
//| 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) {  }
  };

A classe base contém a variável para armazenar seu tipo (a variável é inicializada em cada construtor de objeto). A classe derivada também utiliza o valor dessa variável para armazenar seu tipo. Assim, quando o macro é ativado, eu só adiciono a variável m_typename recebendo não apenas o nome da função ativada, mas também o tipo do objeto que ativou essa função.

O próprio ponteiro deve ser derivado para o reconhecimento mais preciso dos objetos, permitindo aos usuários diferenciar os objetos pelos números. Dentro do objeto, isso é feito assim:

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

Enquanto do lado de fora, será o seguinte:

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

Além disso, a variável para armazenar o nome do objeto pode ser utilizada dentro de cada classe. Nesse caso, é possível passar o nome do objeto como um parâmetro do construtor ao criar um objeto. Isso permitirá que você não apenas divida os objetos através de seus números, mas também entenda o que cada objeto significa (pois você irá nomeá-los). Esse método pode ser realizado similarmente para preencher a variável m_typename.


7. Rastreamento

Todos os métodos mencionados acima se complementam e são muito importantes para a depuração. No entanto, existe outro método que não é tão popular - o rastreamento.

Esse método é raramente utilizado devido à sua complexidade. Quando você ficar preso e não entender o que está acontecendo, o rastreamento pode ajudar você.

Esse método permite que você entenda a estrutura da aplicação - a sequência e os objetos das ativações. Utilizando o rastreamento, você entenderá o que está errado com o programa. Além disso, o método fornece uma visão geral do projeto.

O rastreamento é realizado da seguinte forma: Crie dois macros:

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

Estes são os macros de abertura zx e de fechamento xz consequentemente. Vamos colocá-los para operar os corpos que serão rastreados:

//+------------------------------------------------------------------+
//| 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 a função contém a saída de acordo com as condições, devemos configurar o fechamento xz na área protegida antes de cada retorno. Isso evitará a interrupção da estrutura de rastreamento.

Note que o macro mencionado acima é utilizado apenas para simplificar o exemplo. É melhor usar a impressão para o arquivo para o rastreamento. Ademais, eu utilizo um truque ao imprimir para o arquivo. Para ver a estrutura do rastreamento inteira, eu encapsulo os nomes das funções na seguinte construção sintática:

if() {...}

O arquivo resultante é configurado com a extensão ".mqh", que permite ser aberto no MetaEditor e utilizar o modelador [Ctrl+,] para mostrar a estrutura do rastreamento.

O código de rastreamento completo está disponível abaixo:

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());
  }

Para começar o rastreamento de um determinado local, os macros devem ser complementados pelas condições:

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";

Nesse caso, você será capaz de habilitar ou desabilitar o rastreamento após configurar o valor "verdadeiro" ou "falso" para "rastrear" a variável após um certo evento ou em um certo lugar.

Se o rastreamento já não é solicitado, embora ele possa ser necessário mais adiante ou não há tempo suficiente para limpar a fonte no momento, ele pode ser desabilitado através da mudança dos valores dos macros para os vazios:

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

O arquivo com o Consultor Especialista padrão contendo as mudanças para o rastreamento está anexado abaixo. Os resultados do rastreamento podem ser vistos no diretório "Files" após lançar o Consultor Especialista no gráfico (o arquivo tracing.mqh é criado). Aqui está a passagem do texto do arquivo resultante:

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

Note que a estrutura das ativações agrupadas não podem ser claramente definidas no arquivo recém-criado inicialmente, mas você será capaz de ver sua estrutura completa após utilizar o modelador de código. Abaixo está o texto do arquivo resultante após o uso do modelador:

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

Esse é apenas o meu truque e não um exemplo de como o rastreamento deve ser realizado. Todos são livres para efetuar o rastreamento à sua própria maneira. O aspecto importante é que o rastreamento revela a estrutura das ativações da função.


Observação importante sobre a depuração

Se você implementar mudanças ao seu código durante a depuração, utilize o encapsulamento das ativações das funções diretas do MQL5. É assim que é feito:

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

Isso permitirá que você limpe facilmente o código quando a depuração estiver completa:

  • remova a ativação da função "DebugPrint",
  • e então compilar
  • e apagar as ativações dessa função nas linhas onde o MetaEditor avisa sobre os erros de compilação.

O mesmo acontece com as variáveis utilizadas na depuração. Portanto, tente utilizar variáveis e funções declaradas globalmente. Isso vai lhe poupar a procura por construções perdidas nos caminhos da sua aplicação.


Conclusão

A depuração é uma parte importante do trabalho do programador. Um indivíduo que não é capaz de realizar a depuração do programa não pode ser chamado de programador. Mas a depuração principal é sempre realizada na sua cabeça. Este artigo apenas mostra alguns métodos utilizados na depuração. Mas sem entender os princípios da operação da aplicação esses métodos não serão úteis.

Desejo a você uma depuração bem sucedida!

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/654

Arquivos anexados |
Indicador para o gráfico de Ponto e Figura Indicador para o gráfico de Ponto e Figura
Existem vários tipos de gráficos que fornecem informações sobre a situação do mercado atual. Muitos deles, como o Gráfico de Ponto e Figura, são o legado de um passado remoto. O artigo descreve um exemplo do gráfico de Ponto e Figura usando um indicador de tempo real.
Guia prático do MQL5: Reduzindo o efeito de sobreajuste e lidando com a falta de cotações Guia prático do MQL5: Reduzindo o efeito de sobreajuste e lidando com a falta de cotações
Seja qual for a estratégia de negociação que você usa, sempre haverá uma questão de quais parâmetros escolher para garantir lucros futuros. Este artigo fornece um exemplo de um Expert Advisor com a possibilidade de otimizar vários parâmetros símbolos ao mesmo tempo. Esse método destina-se a reduzir o efeito dos parâmetros de sobreajuste e a lidar com situações em que os dados a partir de um único símbolo não são suficientes para o estudo.
Rede em nuvem do MQL5: Você ainda está calculando? Rede em nuvem do MQL5: Você ainda está calculando?
Logo fará um ano e meio desde que a Rede em nuvem do MQL5 foi inaugurada. Esse evento inovador nos conduziu para uma era de negócios algorítmicos - agora com poucos cliques, negociadores podem ter centenas e milhares de núcleos de computação a sua disposição para a otimização de suas estratégias de negócios.
Guia prático do MQL5: Registrando o histórico de negociações em um arquivo e criando gráficos de saldo para cada símbolo no Excel Guia prático do MQL5: Registrando o histórico de negociações em um arquivo e criando gráficos de saldo para cada símbolo no Excel
Ao me comunicar em vários fóruns, utilizei frequentemente exemplos de meus resultados de teste exibidos como capturas de tela de gráficos do Microsoft Excel. Por muitas vezes me foi pedido para explicar como tais gráficos podem ser criados. Agora, enfim, eu tenho algum tempo para explicar tudo nesse artigo.