MQL5 프로그램 디버깅
소개
이 글은 주로 언어를 이미 배웠지만 아직 프로그램 개발을 완전히 마스터하지 못한 프로그래머를 대상으로 합니다. 모든 개발자가 프로그램을 디버깅할 때 다루는 주요 문제를 강조합니다. 그렇다면 디버깅이란 무엇입니까?
디버깅은 프로그램 실행 오류를 감지하고 제거하기 위한 프로그램 개발 단계입니다. 디버깅 프로세스 동안 개발자는 가능한 문제를 감지하기 위해 애플리케이션을 분석합니다. 변수와 프로그램 실행(어떤 함수가 호출되고 언제 호출되는지)을 관찰하여 분석용 데이터를 받습니다.
두 가지 보완적인 디버깅 기술이 있습니다.
- 디버거 사용 - 개발된 프로그램의 단계별 실행을 보여주는 유틸리티.
- 화면, 저널 또는 파일에서 변수의 상태 및 함수 호출을 대화식으로 표시합니다.
변수, 구조 등을 포함하여 MQL5를 알고 있다고 가정해 보겠습니다. 그러나 아직 자체적으로 프로그램을 개발하지 않았습니다. 가장 먼저 수행할 작업은 편집입니다. 사실 이것은 디버깅의 첫 번째 단계입니다.
1. 편집
컴파일은 높은 수준의 프로그래밍 언어에서 낮은 수준의 프로그래밍 언어로 소스 코드를 번역하는 것입니다.
MetaEditor 컴파일러는 프로그램을 기본 코드가 아닌 바이트 코드로 변환합니다(자세한 내용은 링크 참조). 이를 통해 암호화된 프로그램을 개발할 수 있습니다. 게다가, 바이트코드는 32비트와 64비트 운영 체제 모두에서 시작할 수 있습니다.
그러나 디버깅의 첫 번째 단계인 컴파일로 돌아가 보겠습니다. F7(또는 컴파일 버튼)을 누르면 MetaEditor 5가 코드를 작성할 때 만든 모든 오류를 보고합니다. "도구 상자" 창의 "오류" 탭에는 감지된 오류 및 해당 위치에 대한 설명이 포함되어 있습니다. 커서로 설명 줄을 강조 표시하고 Enter 키를 눌러 오류로 바로 이동합니다.
컴파일러는 두 가지 유형의 오류만 표시합니다.
- 구문 오류(빨간색으로 표시) - 소스 코드는 제거될 때까지 컴파일할 수 없습니다.
- 경고(노란색으로 표시됨) - 코드가 준수되지만 이러한 실수를 수정하는 것이 좋습니다.
구문 오류는 종종 부주의로 인해 발생합니다. 예: "," 및 ";" 변수를 선언할 때 쉽게 혼동될 수 있습니다.
int a; b; // incorrect declaration
컴파일러는 이러한 선언의 경우 오류를 반환합니다. 올바른 선언은 다음과 같습니다.
int a, b; // correct declaration
또는:
int a; int b; // correct declaration
경고도 무시해서는 안 됩니다(많은 개발자가 경고에 대해 너무 부주의합니다). MetaEditor 5가 컴파일 중에 경고를 반환하면 프로그램이 생성되지만 계획한 대로 작동한다는 보장은 없습니다.
경고는 프로그래머의 일반적인 오타를 체계화하려는 MQL5 개발자의 주요 노력을 숨기고 있는 빙산의 일각일 뿐입니다.
두 변수를 비교한다고 가정합니다.
if(a==b) { } // if a is equal to b, then ...
그러나 오타나 건망증으로 인해 "==" 대신 "="를 사용합니다. 이 경우 컴파일러는 코드를 다음과 같이 해석합니다.
if(a=b) { } // assign b to a, if a is true, then ... (unlike MQL4, it is applicable in MQL5)
보시다시피, 이 오타는 프로그램 작동을 극적으로 변경할 수 있습니다. 따라서 컴파일러는 이 줄에 대한 경고를 표시합니다.
요약하자면: 컴파일은 디버깅의 첫 번째 단계입니다. 컴파일러 경고를 무시해서는 안됩니다.
그림 1. 컴파일 중 데이터 디버깅.
2. 디버거
두 번째 디버깅 단계는 디버거(F5 단축키)를 사용하는 것입니다. 디버거는 에뮬레이션 모드에서 프로그램을 실행하여 단계별로 실행합니다. 디버거는 MetaEditor 4에 없는 MetaEditor 5의 새로운 기능입니다. 그렇기 때문에 프로그래머가 MQL4에서 MQL5로 전환하여 사용한 경험이 없는 것입니다.
디버거 인터페이스에는 3개의 기본 버튼과 3개의 보조 버튼이 있습니다.
- 시작 [F5] - 디버깅을 시작합니다.
- 일시 중지 [중단] - 디버깅을 일시 중지합니다.
- 중지 [Shift+F5] - 디버깅을 중지합니다.
- [F11] 실행 - 사용자는 이 줄에서 호출된 함수 내부로 이동합니다.
- [F10] 건너뛰기 - 디버거가 이 문자열에서 호출된 함수의 본문을 무시하고 다음 줄로 이동합니다.
- 나가기 [Shift+F11] - 사용자가 현재 있는 기능의 본문을 종료합니다.
그것이 디버거의 인터페이스입니다. 그러나 우리는 그것을 어떻게 사용해야합니까? 프로그램 디버깅은 프로그래머가 특별한 DebugBreak() 디버깅 기능을 설정한 줄에서 시작하거나 F9 버튼을 누르거나 도구 모음에서 특수 버튼을 클릭하여 설정할 수 있는 중단점에서 시작할 수 있습니다.
그림 2. 중단점 설정
중단점이 없으면 디버거는 단순히 프로그램을 실행하고 디버깅이 성공했다고 보고하지만 아무 것도 표시되지 않습니다. DebugBreak을 사용하면 관심이 없는 일부 코드를 건너뛰고 번거롭다고 생각되는 줄부터 프로그램의 단계별 검사를 시작할 수 있습니다.
따라서 디버거를 시작하고 DebugBreak을 올바른 위치에 배치했으며 지금 프로그램 실행을 검사하고 있습니다. 향후 계획은 무엇입니까? 프로그램에서 무슨 일이 일어나고 있는지 이해하는 데 어떻게 도움이 됩니까?
먼저 디버거 창의 왼쪽 부분을 보십시오. 현재 위치하는 라인의 번호와 함수명을 표시합니다. 둘째, 창의 오른쪽을 보십시오. 비어 있지만 표현식 필드에 변수 이름을 입력할 수 있습니다. 값 필드에 현재 값을 보려면 변수 이름을 입력하세요.
변수는 [Shift+F9] 단축키를 사용하거나 아래와 같이 컨텍스트 메뉴에서 선택하고 추가할 수도 있습니다.
그림 3. 디버깅할 때 변수 감시를 추가합니다.
따라서 현재 위치하는 코드 라인을 추적하고 중요한 변수의 값을 볼 수 있습니다. 이 모든 것을 분석하면 결국 프로그램이 올바르게 작동하는지 이해할 수 있습니다.
관심 있는 변수가 선언된 함수에 아직 도달하지 않은 상태에서 로컬로 선언된 것에 대해 걱정할 필요가 없습니다. 변수 범위 밖에 있는 동안에는 "알 수 없는 식별자" 값을 갖게 됩니다. 변수가 선언되지 않았음을 의미합니다. 디버거의 오류가 발생하지 않습니다. 변수의 범위에 도달하면 해당 값과 유형이 표시됩니다.
그림 4. 디버깅 프로세스. 변수 값 보기.
다음은 주요 디버거 기능입니다. 테스터 섹션은 디버거에서 수행할 수 없는 작업을 보여줍니다.
3. 프로파일러
코드 프로파일러는 디버거에 중요한 추가 기능입니다. 실제로 이것은 최적화로 구성된 프로그램 디버깅의 마지막 단계입니다.
프로파일러는 "프로파일링 시작" 버튼을 클릭하여 MetaEditor 5 메뉴에서 호출됩니다. 디버거에서 제공하는 단계별 프로그램 분석 대신 프로파일러가 프로그램을 실행합니다. 프로그램이 지표 또는 Expert Advisor인 경우 프로파일러는 프로그램이 언로드될 때까지 작동합니다. 언로드는 차트에서 지표 또는 Expert Advisor를 제거하고 "프로파일링 중지"를 클릭하여 수행할 수 있습니다.
프로파일링은 각 함수가 호출된 횟수, 실행에 소요된 시간 등 중요한 통계를 제공합니다. 아마도 백분율 용어의 통계에 약간 혼란스러워 할 것입니다. 통계는 중첩 함수를 고려하지 않는다는 점을 이해해야 합니다. 따라서 모든 백분율 값의 합은 100%를 크게 초과합니다.
그러나 그 사실에도 불구하고 프로파일러는 사용자가 속도를 위해 최적화해야 하는 기능과 메모리를 절약할 수 있는 포지션을 볼 수 있도록 하는 프로그램 최적화를 위한 강력한 도구로 여전히 남아 있습니다.
그림 5. 프로파일러 작업 결과.
4. 상호 작용
어쨌든 메시지 표시 기능인 프린트 와 댓글이 주요 디버깅 도구라고 생각합니다. 첫째, 사용하기가 매우 쉽습니다. 둘째, 이전 버전의 언어에서 MQL5로 전환하는 프로그래머는 이미 이를 알고 있습니다.
"프린트" 기능은 전달된 매개변수를 텍스트 문자열로 로그 파일 및 Expert 도구 탭에 보냅니다. 송신 시간과 기능을 호출한 프로그램의 이름이 텍스트 왼쪽에 표시됩니다. 디버깅하는 동안 함수는 변수에 포함된 값을 정의하는 데 사용됩니다.
변수 값 외에도 이러한 함수의 호출 순서를 알아야 하는 경우가 있습니다. "__FUNCTION__" 및 "__FUNCSIG__" 매크로를 사용할 수 있습니다. 첫 번째 매크로는 호출된 함수의 이름이 포함된 문자열을 반환하고 두 번째 매크로는 호출된 함수의 매개변수 목록을 추가로 표시합니다.
아래에서 매크로 사용을 볼 수 있습니다.
//+------------------------------------------------------------------+ //| Example of displaying data for debugging | //+------------------------------------------------------------------+ void myfunc(int a) { Print(__FUNCSIG__); // display data for debugging //--- here is some code of the function itself }
오버로드된 함수(이름은 같지만 매개변수가 다름) 간의 차이점을 보여주기 때문에 "__FUNCSIG__" 매크로를 사용하는 것을 선호합니다.
일부 호출을 건너뛰거나 특정 함수 호출에 집중해야 하는 경우가 많습니다. 이를 위해 Print 기능은 조건에 의해 보호될 수 있습니다. 예를 들어, print는 1013번째 반복 후에만 호출될 수 있습니다.
//+------------------------------------------------------------------+ //| 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 }
차트의 왼쪽 상단 모서리에 주석을 표시하는 주석 기능도 마찬가지입니다. 디버깅하는 동안 아무데도 전환할 필요가 없기 때문에 이것은 큰 장점입니다. 그러나 기능을 사용할 때마다 새 주석은 이전 주석을 삭제합니다. 그것은 단점으로 볼 수 있습니다(때로는 편리하지만).
이러한 단점을 없애기 위해 변수에 새로운 문자열을 추가로 쓰는 방법을 적용할 수 있습니다. 먼저 string 유형 변수가 선언되고(대부분의 경우 전역적으로) 빈 값으로 초기화됩니다. 그런 다음 각 새 텍스트 문자열은 추가된 라인 바꿈 문자와 함께 시작 부분에 배치되고 변수의 이전 값은 끝에 추가됩니다.
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 }
여기에서 프로그램의 내용을 자세히 볼 수 있는 또 다른 기회가 있습니다. 즉, 파일로 프린트하는 것입니다. 프린트 및 주석 기능은 대용량 데이터 또는 고속 프린트에 항상 적합하지 않을 수 있습니다. 전자는 때때로 변경 사항을 표시할 시간이 충분하지 않을 수 있습니다(통화가 디스플레이보다 먼저 실행되어 혼동을 일으킬 수 있음). 후자는 더 느리게 작동하기 때문입니다. 또한 댓글을 다시 읽고 자세히 검토할 수 없습니다.
파일로 출력하는 것은 호출 순서를 확인하거나 많은 양의 데이터를 기록해야 할 때 가장 편리한 데이터 출력 방법입니다. 단, 매 반복마다 프린트가 사용되지 않고 파일의 끝부분에 프린트가 사용되는 반면, 위에서 설명한 원칙에 따라 각 반복마다 string 변수에 저장하는 데이터가 사용된다는 점에 유의해야 합니다(유일한 차이점은 변수 끝에 새로운 데이터가 추가로 기록됨).
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()); }
WriteFile 함수는 OnDeinit에서 호출됩니다. 따라서 프로그램에서 발생한 모든 변경 사항이 파일에 기록됩니다.
참고: 로그가 너무 크면 여러 변수에 저장하는 것이 좋습니다. 가장 좋은 방법은 텍스트 변수의 내용을 string형 배열 셀에 넣고 com 변수를 0으로 만드는 것입니다(작업의 다음 단계를 위한 준비).
1-2백만 개의 문자열(반복되지 않는 항목)마다 수행해야 합니다. 첫째, 변수 오버플로로 인한 데이터 손실을 방지할 수 있습니다. 두 번째로 가장 중요한 것은 편집기에서 거대한 파일을 여는 대신 여러 파일로 데이터를 표시할 수 있다는 것입니다.
저장된 문자열의 양을 지속적으로 추적하지 않기 위해 파일 작업을 위한 기능을 세 부분으로 분리할 수 있습니다. 첫 번째 부분은 파일을 여는 것이고, 두 번째 부분은 각 반복에서 파일에 쓰는 것이고, 세 번째 부분은 파일을 닫는 것입니다.
//--- 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);
그러나 이 방법은 주의해서 사용해야 합니다. 프로그램 실행이 실패하면(예: 0 분할로 인해) 운영 체제 작업을 방해할 수 있는 관리할 수 없는 열린 파일을 받을 수 있습니다.
또한 각 반복에서 전체 열기-쓰기-닫기 루프를 사용하지 않는 것이 좋습니다. 내 개인적인 경험에 따르면 이 경우 하드 드라이브가 몇 달 안에 죽을 것입니다.
5. 시험 장치
Expert Advisors를 디버깅할 때 특정 조건의 활성화를 확인해야 하는 경우가 많습니다. 그러나 위에서 언급한 디버거는 실시간 모드에서만 Expert Advisor를 실행하며 이러한 조건이 최종적으로 활성화되는 동안 꽤 많은 시간을 기다릴 수 있습니다.
실제로 특정 거래 조건이 드물게 발생할 수 있습니다. 그러나 우리는 그들이 일어난다는 것을 알고 있지만 몇 달 동안 그들을 기다리는 것은 터무니없는 일입니다. 그래서 우리가 뭘 할 수 있지?
이 경우 전략 테스터가 도움이 될 수 있습니다. 동일한 프린트 및 주석 기능이 디버깅에 사용됩니다. 주석은 항상 상황을 먼저 판단하고 프린트 기능은 보다 자세한 분석을 위해 사용됩니다. 테스터는 표시된 데이터를 테스터 로그(각 테스터 에이전트에 대한 별도의 디렉토리)에 저장합니다.
적절한 간격으로 Expert Advisor를 시작하기 위해 시간(제 생각에 오류가 발생한 위치)을 현지화하고 테스터에서 필요한 날짜를 설정하고 모든 틱에서 시각화 모드로 시작합니다.
또한 이 디버깅 방법은 실행 중에 프로그램을 디버그하는 거의 유일한 방법이었던 MetaTrader 4에서 빌려왔다는 점을 언급하고 싶습니다.
그림 6. 전략 테스터를 사용한 디버깅.
6. OOP에서 디버깅
MQL5에 등장한 객체 지향 프로그래밍은 디버깅 프로세스에 영향을 미쳤습니다. 절차를 디버깅할 때 함수 이름만 사용하여 프로그램에서 쉽게 탐색할 수 있습니다. 그러나 OOP에서는 다른 메소드가 호출된 개체를 알아야 하는 경우가 많습니다. 개체가 수직으로 디자인될 때(상속을 사용하여) 특히 중요합니다. 이러한 경우에 템플릿(최근 MQL5에 도입됨)이 도움이 될 수 있습니다.
템플릿 함수를 사용하면 포인터 유형을 문자열 유형 값으로 수신할 수 있습니다.
template<typename T> string GetTypeName(const T &t) { return(typename(T)); }
다음과 같은 방법으로 디버깅할 때 이 속성을 사용합니다.
//+------------------------------------------------------------------+ //| 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) { } };
기본 클래스에는 해당 유형을 저장하기 위한 변수가 포함됩니다(변수는 각 개체의 생성자에서 초기화됨). 파생 클래스는 또한 해당 유형을 저장하기 위해 이 변수의 값을 사용합니다. 따라서 매크로가 호출될 때 호출된 함수의 이름뿐만 아니라 이 함수를 호출한 개체의 유형을 받는 m_typename 변수를 추가하기만 하면 됩니다.
포인터 자체는 사용자가 숫자로 개체를 구별할 수 있도록 개체를 보다 정확하게 인식하기 위해 파생될 수 있습니다. 개체 내부에서 이것은 다음과 같이 수행됩니다.
Print((string)this); // print pointer number inside the class
외부에서는 다음과 같이 보입니다.
Print((string)GetPointer(pointer)); // print pointer number outside the class
또한 각 클래스 내에서 개체 이름을 저장하는 변수를 사용할 수 있습니다. 이 경우 개체 생성 시 개체 이름을 생성자의 매개변수로 전달할 수 있습니다. 이렇게 하면 개체를 숫자로 나눌 수 있을 뿐만 아니라 각 개체가 무엇을 의미하는지 이해할 수 있습니다(이름을 지정할 때). 이 방법은 m_typename 변수를 채우는 것과 유사하게 구현할 수 있습니다.
7. 트레이싱
위에서 언급한 모든 방법은 서로를 보완하며 디버깅에 매우 중요합니다. 그러나 그다지 인기가 없는 또 다른 방법인 추적이 있습니다.
이 방법은 복잡성 때문에 거의 사용되지 않습니다. 막혀서 무슨 일이 일어나고 있는지 이해하지 못할 때 추적이 도움이 될 수 있습니다.
이 방법을 사용하면 응용 프로그램의 구조(순서 및 호출 개체)를 이해할 수 있습니다. 추적을 사용하면 프로그램의 문제점을 이해할 수 있습니다. 게다가 이 방법은 프로젝트의 개요를 제공합니다.
추적은 다음과 같은 방식으로 수행됩니다. 두 개의 매크로를 만듭니다.
//--- opening substitution #define zx Print(__FUNCSIG__+"{"); //--- closing substitution #define xz Print("};");
zx를 열고 이에 따라 xz 매크로를 닫습니다. 추적할 함수 본문에 배치해 보겠습니다.
//+------------------------------------------------------------------+ //| 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; }
함수가 조건에 따라 exit를 포함하는 경우 각 반환 전에 보호 영역에 닫는 xz를 설정해야 합니다. 이렇게 하면 추적 구조가 중단되는 것을 방지할 수 있습니다.
위에서 언급한 매크로는 예제를 단순화하기 위해 사용되었습니다. 추적을 위해 파일로 프린트를 사용하는 것이 좋습니다. 게다가 파일로 프린트할 때 한 가지 트릭을 사용합니다. 전체 추적 구조를 보기 위해 다음 구문 구조로 함수 이름을 래핑합니다.
if() {...}
결과 파일은 MetaEditor에서 열고 스타일러 [Ctrl+,]를 사용하여 추적 구조를 표시할 수 있는 ".mqh" 확장자로 설정됩니다.
전체 추적 코드는 다음과 같습니다.
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()); }
특정 위치에서 추적을 시작하려면 매크로에 다음 조건을 추가해야 합니다.
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";
이 경우 특정 이벤트 또는 특정 위치에서 "추적" 변수에 "true" 또는 "false" 값을 설정한 후 추적을 활성화 또는 비활성화할 수 있습니다.
나중에 필요할 수 있지만 추적이 이미 필요하지 않거나 현재 소스를 지울 시간이 충분하지 않은 경우 매크로 값을 빈 값으로 변경하여 비활성화할 수 있습니다.
//--- substitute empty values #define zx #define xz
추적을 위한 변경 사항이 포함된 표준 Expert Advisor가 포함된 파일이 아래에 첨부되어 있습니다. 추적 결과는 차트에서 Expert Advisor를 실행한 후 Files 디렉토리에서 볼 수 있습니다(tracing.mqh 파일이 생성됨). 다음은 결과 파일 텍스트의 구절입니다.
if(int OnInit()){ }; if(void OnTick()){ if(void CheckForOpen()){ }; }; if(void OnTick()){ if(void CheckForOpen()){ }; }; if(void OnTick()){ if(void CheckForOpen()){ }; }; //--- ...
중첩 호출의 구조는 처음에 새로 생성된 파일에서 명확하게 정의할 수 없지만 코드 스타일러를 사용하면 전체 구조를 볼 수 있습니다. 다음은 스타일러를 사용한 결과 파일의 텍스트입니다.
if(int OnInit()) { }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ if(void OnTick()) { if(void CheckForOpen()) { }; }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ if(void OnTick()) { if(void CheckForOpen()) { }; }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ if(void OnTick()) { if(void CheckForOpen()) { }; }; //--- ...
이것은 내 속임수일 뿐이며 추적을 수행하는 방법에 대한 예는 아닙니다. 누구나 자신의 방식으로 추적을 수행할 수 있습니다. 가장 중요한 것은 추적이 함수 호출의 구조를 드러낸다는 것입니다.
디버깅에 대한 중요 참고 사항
디버깅 중에 코드에 대한 변경 사항을 구현하는 경우 직접 MQL5 함수 호출 래핑을 사용하세요. 다음은 수행 방법입니다.
//+------------------------------------------------------------------+ //| Example of wrapping a standard function in a shell function | //+------------------------------------------------------------------+ void DebugPrint(string text) { Print(text); }
이렇게 하면 디버깅이 완료될 때 코드를 쉽게 지울 수 있습니다.
- "DebugPrint" 함수 호출 제거,
- 그런 다음 컴파일
- MetaEditor가 컴파일 오류에 대해 경고하는 줄에서 이 함수의 호출을 삭제합니다.
디버깅에 사용되는 변수도 마찬가지입니다. 따라서 전역적으로 선언된 변수와 함수를 사용해 보십시오. 그러면 응용 프로그램의 깊이에서 손실된 구성을 검색하지 않아도 됩니다.
결론
디버깅은 프로그래머의 작업에서 중요한 부분입니다. 프로그램 디버깅을 할 수 없는 사람은 프로그래머라고 할 수 없습니다. 그러나 주요 디버깅은 항상 머리에서 수행됩니다. 이 문서에서는 디버깅에 사용되는 몇 가지 방법만 보여줍니다. 그러나 응용 프로그램의 작동 원리를 이해하지 못하면 이러한 방법은 아무 소용이 없습니다.
성공적인 디버깅을 기원합니다!
MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/654