コーディングスタイルについて

 

この話題を持ち出したのは、昔、MQL4で一から書いたコーディングと再コーディングの経験がちゃんとあるので、その経験を共有したいからです。

私の同僚、あなたが何かのアルゴリズムを実装したプログラムを素早く書く能力を疑うわけではありません。しかし、何らかの理由で企画を放棄し、1ヵ月後に戻ってきたときに、すぐに理解できなかった場合は、作家として失格であることは、すでに確認しています。以下では、私自身がコーディングのスタイルに求めていることをお伝えします。この要件を満たすことで、さらなる改良が容易になります。

0.すぐに戦場に駆けつけず、プログラムを書いてください。古典が推奨するように、プログラムの構造を考えるのに数時間かけることです。そうすれば、腰を据えて、素早く、わかりやすく、簡潔にプログラムを書くことができます。この数時間の積み重ねが、執筆のスピードやさらなるデバッグのスピードに何倍にもなって返ってくるのです。

1.関数の長さは、20行を大幅に超えないようにしてください。実装できない場合は、ロジックやコード構造を十分に考えていないところがあります。さらに、コードのデバッグに最も時間がかかるのは、最も長い関数と、その関数が呼び出す関数との関係であることが多い。

例えば、今の私のコードは629行で、27の関数が含まれています。それに加えて、関数の呼び出し構造の説明(2〜6行)と各関数の前の簡単なコメント、そして関数間の4〜5行の空白区切りがあることです。また、ブロックブラケット(中括弧)を惜しげもなく、つまり2つのブラケットごとに1行を割いています。関数の前のコメントをすべて削除し、関数間のセパレータを減らすと、27個の関数で約400行、つまり関数の平均長は約15行になります。

もちろん、このルールにも例外はありますが、それは最も単純な関数や出力関数に適用されるものです。一般的に、1つの関数は機能的に異なる3〜5個のアクションを実行する必要があると言われています。そうでないと、混乱します。また、私は通常、関数内の機能的に異なるアクションの間に空白行を入れます。

4行しかない関数(これは最低限で、関数本体に1行、宣言行に1行、中括弧に2行)~10行の関数が結構あるんです。実際にコードが遅くなるのは、このせいでは全くなく、手が曲がるからなので、コードの速度劣化の心配はないです。

2.アクション、変数、関数の意味を説明するコメントをケチらないでください。この場合、関数の長さの20行制限はそのまま維持されます。壊したら、機能を書き換える。コメントは、1行でも複数行でも使用できます。

3.私の機能は構造化されています。トップ(ゼロ)コールレベル機能、1st、2ndなどがあります。次のレベルの呼び出しの各関数は、前のレベルの関数から直接呼び出されます。例えば、こんな機能です。

// open
// .pairsToOpen
// .combineAndVerify( )
// Собирает из двух валют символ и выполняет все проверки, нужные для его открытия.
// Возвращает валидность пары для открытия.
// Последний аргумент - [...]
bool
combineAndVerify( string quoted, string base, double& fp1 )

- は第3レベルの関数である。これです。

open()は第1レベルの関数です。

pairsToOpen()は2番目の関数(open()から呼び出される)、そして

combineAndVerify() - 3番目 (pairsToOpen()関数によって呼び出される).


次のレベルの各関数のコードは、前の関数のコードよりさらに左側にインデントされています。これにより、プログラム全体の構造が見やすくなります。

このルールにも例外はあるが(より高い構造レベルの2つの関数から呼び出される関数がある)、これは一般的ではない。これは通常、プログラムの複数の異なる部分で同じアクションが実行されているため、コードが最適化されていないことを示します。
しかし、どこからでも呼び出せるユニバーサルな機能も存在する。これらは出力機能で、特別なカテゴリーに入れました。

3.グローバル変数:少ない方が良いが、こちらもやり過ぎない方が良い。これらの変数をすべて正式な関数パラメータに詰め込むこともできますが、そうするとその呼び出しがあまりにも煩雑になり、意味も不明瞭になります。

4.計算の動作とその出力(ファイル、画面、SMS)を分離する。出力する関数はすべて個別に設計し、その関数の呼び出しを呼び出し側の関数本体に貼り付けています。

このルールは、コードの明瞭性を高めるだけでなく、別の副次的効果もあります。そうすれば、非常に簡単にコードからすべての出力を切り離すことができ、コードの実行時間を大幅に短縮できます:出力は、しばしばプログラムの中で最も遅い動作となるのです。

5.変数名:まあ、わかりやすいですね。人それぞれのスタイルがありますが、やはり変数の意味を説明しやすいように作るのが望ましいですね。

まずはこれで十分だと思います。お好みで、さらに追加していただいても結構です。
 
 
Mathemat >> :
まずはこれで十分だと思います。必要であれば、他のものを加えてもよい。

問題はこれだ。最も賢明なプログラム構築の方法とは?

1.START関数でできることをすべて書いてください。

2) それとも、すべてのアクションをユーザー定義関数として記述し、必要に応じてSTART関数から呼び出してもよいですか?

//---------------------------------------------

例えば、同じトロールでも

 

2番目の方がいい。ということを書いています。また、取引 機能は別の関数として記述する必要があります。

 

私もほぼ同じです。

除く。

1.関数の行数です。

2.機能の数です。

計算の速さを優先しています。そのため、関数の数が少なく、呼び出す回数が少ないほど、プログラムの実行速度は速くなります。

機能を捨てられるのであれば、使いたい。

一度だけ失敗したことがあります。メタクオーツはネストされたブロックの数に制限を課していた。

710行のインターフェイス描画機能を得た。51のパラメータを持つ。その数、21配列。ということで、Metaquotesはこんな感じになっていました...。:-)))

一般的には、プログラムのさまざまな部分から呼び出される場合にのみ必要な機能で、あまり頻繁には必要ないのではないかと思います。スピード重視でブロックごとにコードを繰り返すのがいい。

 
Zhunko >> :

その結果、710行のインターフェース描画機能を実現しました。51のパラメータを持つ。その数、21配列。

すごい。しかし、出力機能については、すでに述べたように、例外的な存在である。実行速度に関しては、関数を使わずに直接正しいブロックを書く代わりに関数を呼び出すことのコストは、それほど大きくないと思います - 特にループの中で関数を呼び出す場合は。Roshは、ダイレクトコードとファンクションコールコードの違いをどこかに示した。

 
Zhunko писал(а)>>

計算の速さを優先しています。そのため、関数の数が少なく、呼び出す回数が少ないほど、プログラムの実行速度は速くなります。

機能を排除する機会があれば、それを利用する。

私もそう思います。関数が3回未満しか呼ばれない場合は、ボディに挿入した方が良い。私はそれを身をもって知っています。他の人のプログラムを編集することもよくあります。機能だけだと、いつ何が起こるかわからなくなるので、2つ、3つとウィンドウを開く必要があります。

 
Mathemat >> :

すごい。しかし、出力機能については、すでに述べたように、例外的な存在である。実行速度に関しては、関数を使わずに必要なブロックを直接書く代わりに関数を呼び出すことのコストは、それほど大きくないと思います--特にループの中で関数を呼び出す場合は。どこかでRoshは、直接的なコードと関数呼び出しによるコードの違いを示していた。

Alexeiさん、そうかもしれませんね、最近チェックしてませんが...!

当時はMT4のMetakvotのメモリマネージャに問題があったのでしょう。そこで、インデックスを計算するための関数をすべて削除したところ、非常に驚きの結果が!...です。計算速度が5倍、メモリ消費量が3倍に!!!!

 
Zhunko писал(а)>>

アレクセイ、あなたは正しいかもしれない、最近チェックしていない、しかし!......!?

当時はMT4でMetakvotのメモリマネージャに問題があったのでしょう。そこで、指標を計算するための機能をすべて削除したところ、非常に驚きの結果が!...。計算速度が5倍、メモリ消費量が3倍に!!!!

関数内で宣言された配列はすべて静的です。つまり、これらの配列は一度だけ(関数の最初の呼び出し時に)作成され、メモリに保存される。 そのため、配列はグローバルにするようにしています。どっちがいいんだろう。

 
機能の大きさについて。一画面に収まるような機能を心がけています。全体を見渡せるように
 

はい、ヴァディム、インパクトはあります。調べてみることにしました。以下はその結果である。

1.単純な和算サイクル(5億回繰り返し)。


int start()
{
double sum = 0;
double d;
int st = GetTickCount();
for( int i = 0; i < 500000000; i ++ )
{
add( sum );


// sum += 3.14159265;

}
int timeTotal = GetTickCount() - st;
Print( "Time = " + timeTotal );
return(0);
}
//+------------------------------------------------------------------+


double add( double sum )
{
return( sum + 3.14159265 );
}//+------------------------------------------------------------------+


計算時間(秒): 4.42 - add()を呼び出さない 場合、36.7。


2.より複雑な計算を行うループ(同じ5億回の反復計算)。


int start()
{
double sum = 0;
double d;
int st = GetTickCount();
for( int i = 0; i < 500000000; i ++ )
{
add( i, sum, d );


// d = MathTan( i ) + MathLog( i );
// sum += MathSin( 3.14159265 );
}
int timeTotal = GetTickCount() - st;
Print( "Time = " + timeTotal );
return(0);
}//+------------------------------------------------------------------+


double add( int i, double sum, double& d )
{
d = MathTan( i ) + MathLog( i );
return( sum + MathSin( 3.14159265 ) );
}//+------------------------------------------------------------------+


計算時間(秒):add()なし 100.6、add()あり 142.1。


ここでは、ループ内で直接計算を行うブロックをコメントし、比較のために関数化しています。このように、どのような場合であっても、大きな違いがあるのです。

結論はどうなったのでしょうか?非常に単純なものを関数にすると、関数呼び出しのコストが非常に大きな役割を果たすことさえある。つまり、関数体の計算コストよりもずっと高い可能性があるのです。計算が複雑になれば、機能の有無による差は大きくなる。

したがって、多かれ少なかれ重大な計算を伴うブロックのみを関数に設計するのがよいでしょう。コーディングの際には、この点を考慮するようにします。しかし、いずれにせよ、ループの反復回数が多い場合のみ、時間差が大きくなります。ここでの関数呼び出しのコストは、約10^(-7)秒です。