Über den Kodierungsstil

 

Ich bringe dieses Thema auf, weil ich eine anständige Erfahrung der Codierung und Umkodierung vor langer Zeit von Grund auf in MQL4 geschrieben und ich möchte meine Erfahrung zu teilen.

Mein Kollege, ich zweifle nicht an Ihrer Fähigkeit, schnell ein Programm zu schreiben, das einen Algorithmus umsetzt. Aber ich habe mich bereits vergewissert, dass Sie kein guter Autor sind, wenn Sie das Projekt aus irgendeinem Grund aufgeben, einen Monat später wieder daran arbeiten und es nicht sofort verstehen. Im Folgenden werde ich Ihnen von meinen eigenen Anforderungen an den Kodierungsstil berichten. Die Einhaltung dieser Anforderungen vereinfacht weitere Änderungen.

0. Stürzen Sie sich nicht gleich ins Getümmel und schreiben Sie das Programm. Tun Sie, was die Klassiker empfehlen: Nehmen Sie sich ein paar Stunden Zeit, um über die Struktur des Programms nachzudenken. Dann können Sie sich hinsetzen und ein Programm schnell, klar und präzise schreiben. Diese wenigen Stunden werden sich durch die Geschwindigkeit des Schreibens und die weitere Fehlersuche um ein Vielfaches auszahlen.

1. Die Länge der Funktionen sollte 20 Zeilen nicht wesentlich überschreiten. Wenn Sie es nicht umsetzen können, haben Sie die Logik und die Codestruktur an einigen Stellen nicht gut genug durchdacht. Außerdem wird für die längsten Funktionen und ihre Beziehung zu den Funktionen, die sie aufrufen, oft die meiste Zeit für das Debuggen von Code aufgewendet.

Zum Beispiel ist mein Code jetzt 629 Zeilen lang und enthält 27 Funktionen. Dazu kommt eine Beschreibung der Struktur der Funktionsaufrufe (2-6 Zeilen) und ein kurzer Kommentar vor jeder Funktion sowie 4-5 Zeilen Leerzeichen zwischen den Funktionen. Außerdem gehe ich mit geschweiften Klammern nicht sparsam um, d. h. für jeweils zwei Klammern verwende ich eine Zeile. Wenn ich alle Kommentare vor den Funktionen entferne und die Anzahl der Trennzeichen zwischen den Funktionen reduziere, dann werden 27 Funktionen etwa 400 Zeilen benötigen, d.h. die durchschnittliche Länge meiner Funktionen beträgt etwa 15 Zeilen.

Natürlich gibt es Ausnahmen von dieser Regel - aber das gilt für die einfachsten Funktionen oder Ausgabefunktionen. Im Allgemeinen sollte eine Funktion nicht mehr als 3-5 funktionell unterschiedliche Aktionen ausführen. Andernfalls wird es verwirrend sein. Außerdem setze ich in der Regel eine Leerzeile zwischen die funktionell unterschiedlichen Aktionen der Funktion.

Ich habe eine ganze Reihe von Funktionen, die nur 4 Zeilen lang sind (das ist das Minimum, mit einer Zeile im Körper der Funktion, einer pro Deklarationszeile und zwei pro geschweiften Klammern) bis zu 10 Zeilen. Ich mache mir keine Sorgen, dass der Code dadurch langsamer wird, denn der Code wird nicht dadurch langsamer, sondern durch die krummen Hände.

2. Sparen Sie nicht mit Kommentaren, die die Bedeutung von Aktionen, Variablen und Funktionen erklären. Die 20-Zeilen-Grenze für die Funktionslänge bleibt in diesem Fall erhalten. Wenn Sie sie brechen, schreiben Sie die Funktion neu. Sie können sowohl einzeilige als auch mehrzeilige Kommentare verwenden.

3. Meine Funktionen sind strukturiert. Es gibt Funktionen der obersten (nullten) Aufrufebene, der ersten, zweiten usw. Jede Funktion der nächsten Aufrufebene wird direkt von der Funktion der vorherigen Ebene aufgerufen. Zum Beispiel, eine Funktion wie diese:

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

- ist eine Funktion der dritten Ebene. Hier:

open() ist eine Funktion der ersten Ebene,

pairsToOpen() ist die zweite (sie wird von open() aufgerufen), und

combineAndVerify() - dritte (wird von der Funktion pairsToOpen() aufgerufen).


Der Code jeder Funktion auf der nächsten Ebene ist weiter nach links eingerückt als der Code der vorherigen Funktion. Dies erleichtert den Überblick über die Struktur des gesamten Programms.

Es gibt auch Ausnahmen von dieser Regel (es gibt Funktionen, die von zwei Funktionen einer höheren Strukturebene aufgerufen werden), aber dies ist nicht üblich. Dies deutet in der Regel darauf hin, dass der Code nicht optimal ist, weil dieselbe Aktion in mehreren verschiedenen Teilen des Programms ausgeführt wird.
Es gibt jedoch universelle Funktionen, die von überall aus aufgerufen werden können. Dies sind die Ausgabefunktionen, die ich in eine spezielle Kategorie aufgenommen habe.

3. Globale Variablen: Es ist besser, weniger davon zu haben, aber man sollte es auch hier nicht übertreiben. Man kann all diese Variablen in formale Funktionsparameter packen, aber dann sind ihre Aufrufe zu umständlich zu schreiben und in ihrer Bedeutung unklar.

4. Trennen Sie die Aktionen von Berechnungen und deren Ausgabe (in einer Datei, auf dem Bildschirm oder per SMS). Ich entwerfe alle Ausgabefunktionen separat und füge dann die Aufrufe dieser Funktionen in den Körper der aufrufenden Funktion ein.

Diese Regel verbessert nicht nur die Klarheit und Übersichtlichkeit des Codes, sondern hat auch noch einen weiteren Nebeneffekt: Wenn Sie dies tun, können Sie sehr leicht die gesamte Ausgabe aus dem Code herausschneiden und die Ausführungszeit des Codes erheblich reduzieren: Die Ausgabe ist oft die langsamste Aktion in einem Programm.

5. Variable Namen: Nun, das ist klar. Jeder hat seinen eigenen Stil, aber es ist dennoch wünschenswert, sie so zu gestalten, dass sie die Bedeutung der Variablen leicht erklären.

Ich denke, das ist für den Anfang genug. Wenn Sie möchten, können Sie mehr hinzufügen.
 
Mathemat >> :
Ich denke, das ist für den Anfang genug. Wenn Sie möchten, können Sie noch etwas hinzufügen.

Die Frage ist folgende. Was ist der sinnvollste Weg, ein Programm zu erstellen?

1) Beschreiben Sie, was in der Funktion START alles möglich ist.

2) Oder kann ich alle Aktionen als benutzerdefinierte Funktionen beschreiben und sie dann bei Bedarf von der START-Funktion aus aufrufen?

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

Zum Beispiel, das gleiche Schleppnetz.

 

Die zweite ist besser. Genau darüber schreibe ich hier. Die Handelsfunktionen sollten ebenfalls als separate Funktionen geschrieben werden.

 

Bei mir ist es fast dasselbe.

Außer:

1. Die Anzahl der Zeilen in der Funktion.

2. Die Anzahl der Funktionen.

Ich lege Wert auf die Geschwindigkeit der Berechnungen. Je weniger Funktionen und je weniger Sie diese aufrufen, desto schneller läuft das Programm.

Wenn ich eine Funktion loswerden kann, werde ich sie verwenden.

Ich habe das nur einmal nicht getan. Durch die Meta-Anführungszeichen wurde die Anzahl der verschachtelten Blöcke begrenzt.

Ich habe eine Schnittstellen-Rendering-Funktion von 710 Zeilen. Sie hat 51 Parameter. Es gibt 21 Arrays davon. Das ist es also, was Metacquotes erreicht hat... :-)))

Im Allgemeinen denke ich, dass die Funktion nur dann notwendig ist, wenn sie von verschiedenen Teilen des Programms aus aufgerufen wird und nicht sehr oft. Ich ziehe es vor, den Code in jedem Block zu wiederholen, um die Geschwindigkeit zu erhöhen.

 
Zhunko >> :

Das Ergebnis ist eine Schnittstellen-Zeichenfunktion mit 710 Zeilen. Sie hat 51 Parameter. Es gibt 21 Arrays davon.

Wow. Aber die Ausgabefunktionen stellen, wie ich bereits erwähnt habe, eine Ausnahme dar. Was die Ausführungsgeschwindigkeit betrifft, so denke ich, dass die Kosten für den Aufruf einer Funktion anstelle des direkten Schreibens des erforderlichen Blocks ohne Funktion nicht so hoch sind - insbesondere, wenn die Funktion in einer Schleife aufgerufen wird. Rosh zeigte irgendwo den Unterschied zwischen direktem Code und Funktionsaufrufcode.

 
Zhunko писал(а) >>

Ich lege Wert auf die Geschwindigkeit der Berechnungen. Je weniger Funktionen und je weniger Sie diese aufrufen, desto schneller läuft das Programm.

Wenn es eine Möglichkeit gibt, eine Funktion loszuwerden, nutze ich sie.

Ich stimme zu. Wenn eine Funktion weniger als dreimal aufgerufen wird, sollten Sie sie besser in den Textkörper einfügen. Ich weiß es aus erster Hand. Ich muss oft die Programme anderer Leute bearbeiten. Wenn Sie nur Funktionen haben, müssen Sie zwei oder drei Fenster öffnen, damit Sie nicht durcheinander kommen, was wann passiert.

 
Mathemat >> :

Wow. Aber die Ausgabefunktionen stellen, wie ich bereits erwähnt habe, eine Ausnahme dar. Was die Ausführungsgeschwindigkeit betrifft, so denke ich, dass die Kosten für den Aufruf einer Funktion anstelle des direkten Schreibens des erforderlichen Blocks ohne Funktion nicht so hoch sind - insbesondere, wenn die Funktion in einer Schleife aufgerufen wird. Irgendwo zeigte Rosh den Unterschied zwischen direktem Code und Code mit Funktionsaufruf.

Alexej, vielleicht hast du recht, ich habe in letzter Zeit nicht nachgesehen, aber...!

Damals muss es Probleme mit dem Speichermanager von MT4 gegeben haben. Nachdem ich also alle Funktionen zur Berechnung der Indizes entfernt hatte, war ich von dem Ergebnis sehr überrascht!... Die Berechnungsgeschwindigkeit wurde um das 5-fache erhöht und der Speicherverbrauch um das 3-fache verringert!!!!

 
Zhunko писал(а) >>

Alexey, du könntest Recht haben, ich habe in letzter Zeit nicht nachgesehen, ABER!...!

Damals muss es Probleme mit dem Speichermanager bei MT4 für Metakvot gegeben haben. Nachdem ich also alle Funktionen zur Berechnung der Indizes entfernt hatte, war ich von dem Ergebnis sehr überrascht!... Die Berechnungsgeschwindigkeit wurde um das 5-fache erhöht und der Speicherverbrauch um das 3-fache verringert!!!!

Alle in Funktionen deklarierten Arrays sind statisch. Das bedeutet, dass diese Arrays nur einmal erstellt werden (beim ersten Aufruf der Funktion) und im Speicher abgelegt werden. Deshalb versuche ich, Arrays global zu machen. Das ist nicht gut.

 
Über die Größe der Funktion. Ich versuche, die Funktion auf einen Bildschirm zu bringen. Damit Sie das Ganze sehen können.
 

Ja, Vadim, die Auswirkungen sind da. Ich beschloss, das zu überprüfen. Hier sind die Ergebnisse:

1. Einfacher Summierungszyklus (500 Millionen Iterationen):


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


Berechnungszeit in Sekunden: 4,42 - ohne Aufruf von add(), 36,7 mit.


2. Eine Schleife mit komplexeren Berechnungen (die gleichen 500 Millionen Iterationen):


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


Berechnungszeit in Sekunden: 100,6 ohne add(), 142,1 mit add().


Hier sind kommentierte Blöcke mit direkten Berechnungen in der Schleife, die wir zum Vergleich zu einer Funktion machen. Wie wir sehen, gibt es in jedem Fall einen Unterschied, aber er ist sehr unterschiedlich.

Was sind die Schlussfolgerungen? Wenn wir etwas sehr Einfaches zu einer Funktion formen, spielen die Kosten für den Funktionsaufruf eine wichtige Rolle, sogar eine sehr wichtige. Mit anderen Worten, es kann viel mehr sein als die Kosten der Berechnungen im Funktionskörper. Wenn die Berechnungen komplexer sind, ist der Unterschied zwischen dem Vorhandensein und dem Fehlen der Funktion sehr gering.

Daher ist es besser, nur Blöcke mit mehr oder weniger schwerwiegenden Berechnungen zu Funktionen zu machen. Ich werde versuchen, dies bei der Kodierung zu berücksichtigen. In jedem Fall ist der Zeitunterschied nur dann signifikant, wenn die Schleife sehr viele Iterationen hat: Die Kosten für einen Funktionsaufruf betragen hier etwa 10^(-7) Sekunden.