記事"MQL5 でのアサーション"についてのディスカッション

 

新しい記事 MQL5 でのアサーション はパブリッシュされました:

この記事では、MQL5でのアサーションの利用について扱います。アサーションメカニズムとアサーションを実装するための一般的なガイダンスを2つの例として提供します。

ここでは、文字通り「0が1より大きいか確認すること」を意味するアサーションを行います。このアサーションは、明らかに偽で、エラーメッセージが表示されます。:

図1。アサーションの例

図1。アサーションの例


作者: Sergey Eremin

 

1.なぜマクロなのか?マクロは不便だし、すべての条件を入力できるわけではない。些細なプロシージャを実装する方が簡単だった。

2.配列の "汚いトリック"。ゼロで割ることはできないのか?

 
Andrey Shpilev:

1.なぜマクロなのか?マクロは不便だし、すべての条件を入力できるわけではない。些細なプロシージャを実装する方が簡単だった。

2.配列の "汚いトリック"。ゼロで割ることはできないのか?

1.根拠のない話にならないように、私のマクロに与えられない条件の例を示してください(皮肉ではなく、このマクロをいつも使っている私にとって、微妙な点をすべて知ることは本当に重要なのです)。では、デバッグの難しさを教えてください。

一般的には、はい、プロシージャを使えばできます。しかし、公正を期すために、私はプロシージャでこのデータをすべて取得する優雅な方法を知りません:

  1. チェックに渡される式のテキスト(#condition)。
  2. マクロが呼び出されたソースコードのファイル名(__FILE__)。
  3. マクロが呼び出された関数またはメソッドのシグネチャ(__FUNCSIG__)。
  4. マクロが呼び出されたソースコードファイルの行番号(__LINE__)。

これらすべてを実装するプロシージャ(もちろん、"out of the box "であり、マシン上であり、手動でこれらすべてをパラメータとして渡すのではない)の形であなたのバリアントを示していただければ、非常にありがたい(そしておそらく私だけではない)。原理的には、2...4は入力パラメータとして渡すことが でき、それは多かれ少なかれ普遍的なものになります(常に同じものが渡されるという意味で、手動で何かを設定する必要はありません)。1をプロシージャーで取得する方法については、まったくアイデアがない。

さらに、すべて同じC++のように、通常、ステートメントは、同じ方法で、マクロに書かれており、私は同じ道を行きました。唯一の弱点は、そのようなマクロを使用するプロシージャ/関数内でxという 入力パラメータまたは変数が宣言されている場合、警告が表示されることです。解決策は簡単で、マクロの中で配列にもっとユニークな名前、例えばassertionFailedArrayを つけることです。


2.違いがわかりません。実行エラーは実行エラーであり、プログラムをクラッシュさせ、それ以上実行されません。しかし、なぜこのような方法をとったかについてお答えします。最初はゼロ除算 だったのですが、このようなマクロをテストしていたところ、なぜかメソッド内で呼び出してもコードの実行が中断されませんでした。OnTickやOnInitなどで呼び出された場合は、そう、実行が止まってしまうのだ。任意のクラスのメソッド内で呼び出された場合は、そうではなかった。MQL5のエラーなのかどうか、私はそれを調べようとはしなかった。)

メソッド内でのゼロによる除算の何が問題なのか、試してみるつもりだ。

 
なぜかわからないが(結局デバッグの話)、このコードをここに残しておこう:
#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:

   //---- コンストラクタ
                     CFormatOut(int dbl_fmt_digits=4,bool auto_space=false):m_dbl_fmt("%."+(string)dbl_fmt_digits+"f") { }

   //--- 出力データ
   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)); }

   //--- 行末を出力する (実出力/Print関数を呼び出す)
   CFormatOut *operator<<(CFormatOutEol &eol) { Print(m_line); m_line=NULL; return(GetPointer(this)); }

   //--- 実数の出力形式を変更する
   CFormatOut *operator<<(CFormatOutFmtDigits &fmt) { m_dbl_fmt="%."+(string)fmt.digits+"f"; return(GetPointer(this)); }
   
   //--- 自動スペース挿入の追加と削除
   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;
//--- 出力にEndOfLineを挿入するための特別なオブジェクト。
CFormatOutEol        EOL;
//--- 出力する数字の桁数を設定する
CFormatOutFmtDigits  DBL_FMT_DIGITS(int digits) { CFormatOutFmtDigits fmt; fmt.digits=digits; return(fmt); }
//--- 出力と出力の間にスペースを挿入する/しない
CFormatOutFmtSpace   AUTO_SPACE(bool enable)    { CFormatOutFmtSpace  fmt; fmt.space =enable; return(fmt); }
//--- 列挙型を文字列に変換する簡単な関数
template<typename T> string EN(T enum_value)    { return(EnumToString(enum_value)); }
使い方
OUT << AUTO_SPACE(true) << M_PI << "Test" << DBL_FMT_DIGITS(6) << M_PI << EN(PERIOD_M1) << EOL;
結果
2015.09.01 18:04:49.060    Test EURUSD,H1: 3.1416 Test 3.141593 PERIOD_M1

注意:
OUT << ...という式のパラメータは、右から左へ、逆の順序で指定します。を右から左へ逆順に実行すると、副作用が発生する可能性があります!
 
Ilyas:
なぜかわからないので(デバッグの話なので)、このコードをここに残しておきます:

もしコードの中で出力先(Printで ログへ、alertでアラートへ、ファイルへ、など)を指定できるのであれば、さらに便利だと思います。特に、それを実行するのは難しいことではありませんから。


P.S.この記事を批判/賞賛してもいいですか?:)

 
Sergey Eremin:

もしコード内で出力先(Printでログに出力、アラートに出力、ファイルに出力など)を指定できるのであれば、さらに便利だと思います。特に、それを実行するのは難しいことではありませんから。


P.S.この記事を批判/賞賛してもいいですか?:)

この記事ではDEBUG ASSERTだけを取り上げています。

しかし、IMHO!

良いロガーはロギングレベルを持つべきです:
  1. FATAL - ошибка, дальнейшее выполнение программы невозможно
  2. ERR   - ошибка, выполнение программы можно продолжить
  3. ATT   - предупреждение
  4. MSG   - сообщение

プログラムがデバッグ されているとき、DebugBreakがロガーで呼び出されます- 停止してMQLプログラムの環境(状態)を見ることができます。
プログラムがエンドユーザーで実行されているとき、ロガーメッセージはファイル(print/alert)に保存されます。
デフォルトでは、ロガーはERRとFATALエラーのみを出力し、ユーザーはプログラムを実行しているときに、プログラムのすべてのメッセージ(ATTとMSG)を見るために、常にロギング・レベルを変更することができます。
ログを正しく使用すると、プログラムのエラーを特定/検索するために使用できます。
 
これが骨だ、肉を付けろ:
#property script_show_inputs

enum EnLogLevel
  {
   __LOG_LEVEL_FATAL,   // 致命的なエラーのみ
   __LOG_LEVEL_ERR,     // エラーのみ
   __LOG_LEVEL_ATT,     // 警告とエラー
   __LOG_LEVEL_MSG,     // すべてのメッセージ
  };

input EnLogLevel LogLevel=__LOG_LEVEL_MSG;   // ロガーレベル
//+------------------------------------------------------------------+
//||
//+------------------------------------------------------------------+
#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;
//+------------------------------------------------------------------+
//| スクリプト番組開始機能|
//+------------------------------------------------------------------+
void OnStart()
  {
   LOG_MSG("Hello MSG world!");
   LOG_ATT("Hello ATT world!");
   LOG_ERR("Hello ERR world!");
   LOG_FATAL("Hello FATAL world!");
  }
 
Ilyas:
この記事ではDEBUG ASSERTについてしか触れていない。 しかし、IMHO! 良いロガーはロギングレベルを持たなければならない:




  1. FATAL - ошибка, дальнейшее выполнение программы невозможно
  2. ERR   - ошибка, выполнение программы можно продолжить
  3. ATT   - предупреждение
  4. MSG   - сообщение
ロギング・レベルはMQLプログラムのパラメータで制御できます。
プログラムがデバッグ されているとき、DebugBreakがロガーで呼び出されます- 停止してMQLプログラムの環境(状態)を見ることができます。
プログラムがエンドユーザーで実行されているとき、ロガー・メッセージはファイル(print/alert)に保存されます。
デフォルトでは、ロガーはERRとFATALエラーのみを生成し、ユーザーはプログラムを実行するときにロギング・レベルを常に変更して、プログラムのすべてのメッセージ(ATTとMSG)を見ることができます。
正しく使用すれば、ログを使用してプログラムのエラーを特定/検索できます。

ちょうど次の記事で(Rashidが承認した場合)ソフトウェアのリリースバージョンですでに「予想される」エラーの処理を計画しており(承認後の論理的な継続として)、これにはログの問題の開示が含まれます。

もし差し支えなければ、この記事に使わせていただきます。

 
Sergey Eremin:

次の記事では(ラシードが承認すれば)、ソフトウェアのリリース・バージョンにおける「予想される」エラーを取り上げようと思っています(承認後の論理的な続きとして)。

もし差し支えなければ、この記事に使わせていただきます。

もちろん、私はその記事を待ちます。ドラフトを購読し、その開発に協力します。
 
Sergey Eremin:

興味深い話題だ。

この記事を読む少し前に、私は、ブロックの1つにコードループの可能性がある場合に、前提条件によって検出し、プログラムの実行を 即座に中断する方法について自分なりに考えていた。

おっと。

同時に、assert.mqh ファイルをダウンロードし、そこに1行追加した:

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

そして、コードではこうなっている:

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

つまり、単純に、コードを構築するときに、コードの作業の終わりまでに、この「作業」情報の出力が簡単に削除できることを期待して、情報の出力を 適用するのである(多くの人が、おそらくコード構築の段階で情報の出力を行い、行っているように)。

 
Dina Paches:

興味深い話題だ。

この記事を読む少し前に、私は、ブロックの1つにコードループの可能性がある場合に、前提条件によって検出し、プログラムの実行を 即座に中断する方法について自分なりに考えていた。

おっと。

同時に、assert.mqh ファイルをダウンロードし、そこに1行追加した:

そして、コードではこうなっている:

つまり、単純に、コードを構築するときに、そのコードでの作業の終わりまでに、この「作業中の」情報の出力が簡単に削除できることを期待して、情報の出力を適用するということだ(多くの人が、おそらくコード構築の段階で情報の出力を行い、そうしていると思う)。

フィードバックをありがとう!

TEST_TEXTを条件付きコンパイルで本当に簡単に削除できるようにするには、マクロの中にPrintを 入れることを考えます。現在のバージョンでは、TEST_TEXTを削除するのは簡単ですが、Print自体は削除できないと思います。