Diskussion zum Artikel "Die Verwendung der Behauptung (assertions) bei der Entwicklung der Programme in MQL5"

 

Neuer Artikel Die Verwendung der Behauptung (assertions) bei der Entwicklung der Programme in MQL5 :

In diesem Artikel wird Behauptung (assertions) im Rahmen der Sprache MQL5 betrachtet. Es werden zwei Beispiele für die Realisierung des Behauptungsmechanismus geben, sowie allgemeine Empfehlungen für die Verwendung der Behauptungen.

Eine voll funktionsfähige Makro assert wird nach folgender Art und Weise gebaut. Zunächst wird ein eingehender Ausdruck condition überprüft. Wenn es falsch ist, dann wird eine Nachricht fullMessage gebildet und angezeigt. Die Nachricht fullMessage wird aus den folgenden Elementen aufgebaut:

  1. Der Text des Ausdrucks,der zur Überprüfung gegeben wurde (#condition).
  2. Der Dateiname mit einem Quellcode, aus dem die Makro (__FILE__) aufgerufen wurde.
  3. Die Signatur der Funktion oder Methode, aus der die Makro (__FUNCSIG__) aufgerufen wurde.
  4. Die Zeilennummer in einer Datei mit dem Quellcode, in dem ein Makro-Aufruf platziert wird (__LINE__).
  5. Die Nachricht, die an eine Makro übertragen wurde, wenn es nicht leer ist (message).

Nach der Lieferung der Meldung(Alert) wird in dem zweiten Makrotyp versucht, einen Wert zu einem nicht existierenden Array-Element zuzuweisen, dies führt zu einem Fehler bei der Ausführungszeit und bewirkt so, dass das Programm direkt abstürzt.

Dieses Verfahren, das Programm zu beenden hat Nebenwirkungen für die Indikatoren, die in ihren Unterfenster arbeiten: ihre Unterfenster bleiben im Terminal, und deshalb müssen sie manuell geschlossen werden. Außerdem können Artefakte in Form von nicht entfernten grafischen Objekten, globale Variablen des Terminals, Dateien, etc. sein, die während der Arbeit des Programms erstellt wurden, bis das abgestürzt ist. Wenn dieses Verhalten vollständig inakzeptabel ist, dann sollte die erste Makro verwendet werden.

Autor: Sergey Eremin

 

1. Warum Makros? Sie sind unbequem, nicht alle Bedingungen können ihnen zugeführt werden, und es ist extrem schwierig, sie zu debuggen, wenn etwas schief geht. Es war einfacher, triviale Prozeduren zu implementieren.

2. Ein zu "schmutziger Trick" mit dem Array. Könnte man es nicht durch Null dividieren?

 
Andrey Shpilev:

1. Warum Makros? Sie sind unbequem, nicht alle Bedingungen können ihnen zugeführt werden, und es ist extrem schwierig, sie zu debuggen, wenn etwas schief geht. Es war einfacher, triviale Prozeduren zu implementieren.

2. Ein zu "schmutziger Trick" mit dem Array. Könnte man es nicht durch Null dividieren?

1. Um nicht unsubstantiiert zu sein, zeigen Sie mir ein Beispiel für eine Bedingung, die nicht in mein Makro eingespeist werden kann (ich bin nicht sarkastisch, es ist wirklich wichtig für mich, über alle Feinheiten Bescheid zu wissen, da ich dieses Makro ständig benutze). Erläutern Sie also bitte, worin die Schwierigkeiten bei der Fehlersuche bestehen?

Und im Allgemeinen, ja, man kann es mit einer Prozedur machen, ich habe nur zwei mögliche Beispiele gezeigt. Aber um fair zu sein, ich kenne keinen eleganten Weg, um all diese Daten in einer Prozedur zu erhalten:

  1. Der Text des Ausdrucks, der an die Prüfung übergeben wird(#condition).
  2. Der Name der Quellcode-Datei, aus der das Makro aufgerufen wurde(__FILE__).
  3. Die Signatur der Funktion oder Methode, aus der das Makro aufgerufen wurde(__FUNCSIG__).
  4. Zeilennummer in der Quellcodedatei, in der sich der Makroaufruf befindet(__LINE__).

Ich wäre Ihnen sehr dankbar (und ich bin wahrscheinlich nicht der Einzige), wenn Sie mir Ihre Variante in Form einer Prozedur zeigen könnten, die all dies implementieren würde (natürlich "out of the box" und auf der Maschine, und nicht durch manuelle Übergabe all dieser Parameter). Im Prinzip kann 2...4 als Eingabeparameter übergeben werden , und es wird mehr oder weniger universell sein (in dem Sinne, dass es immer das Gleiche sein wird, was übergeben wird, man wird nicht manuell etwas einstellen müssen), aber wie bekommt man Element. 1 in der Prozedur zu bekommen, habe ich überhaupt keine Ideen

Plus alle die gleichen in der Regel, wie in der gleichen C + +, Anweisungen sind auf Makros geschrieben, auf die gleiche Weise und ich ging den gleichen Weg. Der einzige Schwachpunkt, den ich sehe: wenn ein Eingabeparameter oder eine Variable mit dem Namen x in der Prozedur/Funktion deklariert wird, in der wir ein solches Makro verwenden, erhalten wir eine Warnung. Die Lösung ist einfach: im Makro benennen Sie das Array etwas eindeutiger, zum Beispiel assertionFailedArray.


2. Ich sehe den Unterschied nicht. Ein Ausführungsfehler ist ein Ausführungsfehler, er führt zum Absturz des Programms und es wird nicht weiter ausgeführt. Aber ich sage Ihnen, warum ich diesen Weg gegangen bin: Zuerst war es Division durch Null, aber als ich ein solches Makro getestet habe, wurde die Codeausführung aus irgendeinem Grund nicht unterbrochen, wenn es in Methoden aufgerufen wurde. Wenn es in OnTick, OnInit, etc. aufgerufen wurde, dann ja, wurde die Ausführung angehalten. Wenn es innerhalb einer Methode einer beliebigen Klasse aufgerufen wurde, dann nicht. Ob es sich um einen MQL5-Fehler handelte, habe ich nicht untersucht, ich habe einfach einen anderen Ausführungsfehler aufgerufen :).

Ich werde versuchen zu sehen, was mit der Division durch Null in Methoden falsch ist.

 
Ich weiß nicht, warum (schließlich geht es um die Fehlersuche), ich lasse diesen Code einfach hier stehen:
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict
//+------------------------------------------------------------------+
//||
//+------------------------------------------------------------------+
struct CFormatOutEol       { uchar dummy; };
struct CFormatOutFmtDigits { int digits;  };  
struct CFormatOutFmtSpace  { bool space;  };  
//+------------------------------------------------------------------+
//||
//+------------------------------------------------------------------+
class CFormatOut
  {
   string            m_line;
   string            m_dbl_fmt;
   bool              m_auto_space;

public:

   //---- Konstruktor
                     CFormatOut(int dbl_fmt_digits=4,bool auto_space=false):m_dbl_fmt("%."+(string)dbl_fmt_digits+"f") { }

   //--- Ausgabedaten
   CFormatOut *operator<<(double x) { auto_space(); m_line+=StringFormat(m_dbl_fmt,x); return(GetPointer(this)); }
   CFormatOut *operator<<(string s) { auto_space(); m_line+=s;                         return(GetPointer(this)); }
   CFormatOut *operator<<(long   l) { auto_space(); m_line+=(string)l;                 return(GetPointer(this)); }

   //--- Ausgabe am Ende der Zeile (echte Ausgabe/Aufruf der Druckfunktion)
   CFormatOut *operator<<(CFormatOutEol &eol) { Print(m_line); m_line=NULL; return(GetPointer(this)); }

   //--- Ausgabeformat für reelle Zahlen ändern
   CFormatOut *operator<<(CFormatOutFmtDigits &fmt) { m_dbl_fmt="%."+(string)fmt.digits+"f"; return(GetPointer(this)); }
   
   //--- Einfügen/Entfernen der automatischen Leerzeicheneinfügung
   CFormatOut *operator<<(CFormatOutFmtSpace  &fmt) { m_auto_space=fmt.space; return(GetPointer(this)); }

protected:
   void              auto_space() { if(m_line!=NULL && m_auto_space) m_line+=" "; }
  };

CFormatOut           OUT;
//--- Spezielles Objekt zum Einfügen von EndOfLine in die Ausgabe
CFormatOutEol        EOL;
//--- Einstellung der Ziffern für die Zahlenausgabe
CFormatOutFmtDigits  DBL_FMT_DIGITS(int digits) { CFormatOutFmtDigits fmt; fmt.digits=digits; return(fmt); }
//--- Ein/Ausschalten des Einfügens von Leerzeichen zwischen den Ausgaben
CFormatOutFmtSpace   AUTO_SPACE(bool enable)    { CFormatOutFmtSpace  fmt; fmt.space =enable; return(fmt); }
//--- Shorty-Funktion zur Konvertierung von Enums in String
template<typename T> string EN(T enum_value)    { return(EnumToString(enum_value)); }
Verwendung:
OUT << AUTO_SPACE(true) << M_PI << "Test" << DBL_FMT_DIGITS(6) << M_PI << EN(PERIOD_M1) << EOL;
Ergebnis:
2015.09.01 18:04:49.060    Test EURUSD,H1: 3.1416 Test 3.141593 PERIOD_M1

ACHTUNG:
Die Parameter des Ausdrucks OUT << ... in umgekehrter Reihenfolge, von rechts nach links, ist ein Nebeneffekt möglich!
 
Ilyas:
Ich weiß nicht, warum (wir reden hier über Debugging), also lasse ich diesen Code einfach hier stehen:

Wenn Sie in Ihrem Code angeben können, wohin die Ausgabe erfolgen soll (in das Protokoll über Print, in den Alert, in eine Datei usw.), könnte dies noch nützlicher sein, wie mir scheint. Zumal es gar nicht so schwierig ist, dies zu tun.


P.S. Darf ich den Artikel kritisieren/lobend erwähnen? :)

 
Sergey Eremin:

Wenn Sie in Ihrem Code angeben können, wohin die Ausgabe erfolgen soll (in das Protokoll über Print, in den Alert, in eine Datei usw.), scheint mir dies noch nützlicher zu sein. Zumal es gar nicht so schwierig ist, dies zu tun.


P.S. Darf ich den Artikel kritisieren/lobend erwähnen? :)

Der Artikel bespricht nur DEBUG ASSERT, es ist gut, es zur Hand zu haben. Das Thema ist abgedeckt.

Aber, IMHO! Für Benutzer (die das Material des Artikels im großen Stil nutzen) brauchen Sie nicht nur DEBUG ASSERT, sondern auch einen Logger.

Ein guter Logger sollte einen Logging-Level haben:
  1. FATAL - ошибка, дальнейшее выполнение программы невозможно
  2. ERR   - ошибка, выполнение программы можно продолжить
  3. ATT   - предупреждение
  4. MSG   - сообщение
Der Logging-Level kann durch einen Parameter des MQL-Programms gesteuert werden.
Wenn das Programm debuggt, wird DebugBreak im Logger aufgerufen - man kann anhalten und sich die Umgebung (den Zustand) des MQL-Programms ansehen.
Wenn das Programm beim Endbenutzer läuft, werden die Logger-Meldungen in einer Datei gespeichert (print/alert).
Standardmäßig gibt der Logger nur ERR- und FATAL-Fehler aus. Der Benutzer kann die Logging-Ebene bei der Ausführung des Programms jederzeit ändern, um alle Meldungen des Programms (ATT und MSG) zu sehen.
Bei richtiger Verwendung kann der Logger zur Identifizierung/Findung eines Fehlers im Programm verwendet werden.
 
Hier sind deine Knochen, mach etwas Fleisch darauf:
#property script_show_inputs

enum EnLogLevel
  {
   __LOG_LEVEL_FATAL,   // nur schwerwiegende Fehler
   __LOG_LEVEL_ERR,     // nur Fehler
   __LOG_LEVEL_ATT,     // Warnungen und Fehler
   __LOG_LEVEL_MSG,     // alle Meldungen
  };

input EnLogLevel LogLevel=__LOG_LEVEL_MSG;   // Logger-Ebene
//+------------------------------------------------------------------+
//||
//+------------------------------------------------------------------+
#define __LOG_OUT(params) ExtTrueLogger.Out params
#define __LOG(level,params) do{ if(level<=LogLevel) __LOG_OUT(params); }while(0)
#define  LOG_MSG(msg)    __LOG(__LOG_LEVEL_MSG,(__FUNCSIG__,__FILE__,__LINE__,msg))
#define  LOG_ATT(msg)    __LOG(__LOG_LEVEL_ATT,(__FUNCSIG__,__FILE__,__LINE__,msg))
#define  LOG_ERR(msg)    __LOG(__LOG_LEVEL_ERR,(__FUNCSIG__,__FILE__,__LINE__,msg))
#define  LOG_FATAL(msg)  __LOG(__LOG_LEVEL_FATAL,(__FUNCSIG__,__FILE__,__LINE__,msg))
//+------------------------------------------------------------------+
//||
//+------------------------------------------------------------------+
class CTrueLogger
  {
public:
   void              Out(string func,string file,int line,string msg)
     {
      Print(func," ",func," ",file," ",line," ",msg);
     }
  } ExtTrueLogger;
//+------------------------------------------------------------------+
//| Skript-Programmstartfunktion|
//+------------------------------------------------------------------+
void OnStart()
  {
   LOG_MSG("Hello MSG world!");
   LOG_ATT("Hello ATT world!");
   LOG_ERR("Hello ERR world!");
   LOG_FATAL("Hello FATAL world!");
  }
 
Ilyas:
Der Artikel bespricht nur DEBUG ASSERT, es ist gut, es zur Hand zu haben. Das Thema wird behandelt.

Aber, IMHO! Für Benutzer (die das Material des Artikels in großem Umfang nutzen) brauchen Sie nicht nur DEBUG ASSERT, sondern auch einen Logger.

Ein guter Logger muss einen Logging-Level haben:
  1. FATAL - ошибка, дальнейшее выполнение программы невозможно
  2. ERR   - ошибка, выполнение программы можно продолжить
  3. ATT   - предупреждение
  4. MSG   - сообщение
Der Logging-Level kann durch einen Parameter des MQL-Programms gesteuert werden.
Wenn das Programm debuggt, wird DebugBreak im Logger aufgerufen - Sie können anhalten und die Umgebung (den Zustand) des MQL-Programms betrachten.
Wenn das Programm beim Endbenutzer läuft, werden die Logger-Meldungen in einer Datei gespeichert (print/alert).
Standardmäßig erzeugt der Logger nur ERR- und FATAL-Fehler, und der Benutzer kann die Logging-Ebene bei der Ausführung des Programms jederzeit ändern, um alle Meldungen des Programms (ATT und MSG) zu sehen.
Bei richtiger Verwendung kann das Log verwendet werden, um Fehler im Programm zu identifizieren/zu finden.

Gerade im nächsten Artikel (wenn Rashid zustimmt) plane ich die Verarbeitung von "erwarteten" Fehlern bereits in Release-Versionen von Software (als logische Fortsetzung nach der Freigabe), die die Offenlegung des Protokollierungsproblems beinhalten wird.

Vielen Dank für diese beiden Kommentare, wenn es Ihnen nichts ausmacht, werde ich sie für diesen Artikel verwenden.

 
Sergey Eremin:

Im nächsten Artikel (wenn Rashid zustimmt) werde ich die "erwarteten" Fehler in den Release-Versionen der Software behandeln (als logische Fortsetzung nach den Genehmigungen), die auch die Frage der Protokollierung abdecken werden.

Vielen Dank für diese beiden Kommentare, wenn es Ihnen nichts ausmacht, werde ich sie für diesen Artikel verwenden.

Natürlich werde ich auf den Artikel warten, ich werde den Entwurf abonnieren und bei seiner Entwicklung helfen, viel Glück.
 
Sergey Eremin:

Interessantes Thema.

Kurz bevor ich diesen Artikel gelesen habe, habe ich selbst über Möglichkeiten nachgedacht, wie man eine mögliche Codeschleife in einem der Blöcke anhand von Vorbedingungen erkennen und die Programmausführung sofort unterbrechen kann.

Opps.

Gleichzeitig habe ich die Datei assert.mqh heruntergeladen und dort eine Zeile eingefügt:

#define  TEST_TEXT "Line: ",__LINE__,", ",__FUNCTION__,", "

Und dann sieht es im Code so aus:

  Print(TEST_TEXT,"a = ",a);

Das heißt, dass und einfach bei der Konstruktion des Codes, um die Ausgabe von Informationen mit der Erwartung, dass bis zum Ende der Arbeit an den Code dann diese Ausgabe von "Arbeits"-Informationen kann leicht entfernt werden (wie viele, nehme ich an, wahrscheinlich tat und tut mit der Ausgabe von Informationen in den Stadien der Code-Konstruktion) gelten.

 
Dina Paches:

Interessantes Thema.

Kurz bevor ich diesen Artikel gelesen habe, habe ich selbst über Möglichkeiten nachgedacht, wie man eine mögliche Codeschleife in einem der Blöcke anhand von Vorbedingungen erkennen und die Programmausführung sofort unterbrechen kann.

Opps.

Gleichzeitig habe ich die Datei assert.mqh heruntergeladen und dort eine Zeile eingefügt:

Und dann sieht es im Code so aus:

Das heißt, dass und einfach bei der Konstruktion des Codes, um die Ausgabe von Informationen mit der Erwartung, dass bis zum Ende der Arbeit an den Code dann diese Ausgabe von "Arbeits"-Informationen kann leicht entfernt werden (wie viele, ich glaube, wahrscheinlich tat und tut mit der Ausgabe von Informationen in den Stadien der Code-Konstruktion) gelten.

Vielen Dank für das Feedback!

Damit TEST_TEXT wirklich leicht durch bedingte Kompilierung entfernt werden kann, würde ich in Erwägung ziehen, Print in das Makro aufzunehmen. In der aktuellen Version ist es meiner Meinung nach einfach, TEST_TEXT zu entfernen, aber nicht die Prints selbst.