About coding style

 

I bring this topic up because I have a decent experience of coding and recoding a long time ago written from scratch in MQL4 and I want to share my experience.

My colleague, I don't doubt your ability to quickly write a program that implements some algorithm. But I've already made sure that if you have abandoned the project for whatever reason, returned to it a month later, and could not immediately understand it, you are not a good writer. In what follows I will tell you about my own requirements to the style of coding. Observance of these requirements simplifies further modifications.

0. Do not rush into action right away and write the program. Do as the classics recommend: spend a few hours on thinking about the structure of the program. Then you can sit down and write a program quickly, clearly and concisely. These few hours will pay off in speed of writing and further debugging many times over.

1. The length of functions should not significantly exceed 20 lines. If you cannot implement it, there are some places where you have not thought out the logic and code structure well enough. Moreover, it is on the longest functions and their relation to the functions they call that most time is often spent on code debugging.

For example, my code now is 629 lines long and contains 27 functions. That's along with a description of the function call structure (2-6 lines) and a brief comment before each function, as well as 4-5 line blank separators between functions. In addition, I do not put block brackets (curly brackets) sparingly, i.e. for each of the two brackets I dedicate a line. If I remove all comments before functions, and reduce the number of separators between functions, then 27 functions will take about 400 lines, i.e. the average length of my functions is about 15 lines.

There are, of course, exceptions to this rule - but that applies to the simplest functions or output functions. Generally speaking, a function should perform no more than 3-5 functionally different actions. Otherwise, it will be confusing. I also usually put a blank line between the function's functionally different actions inside the function.

I have quite a few functions that are only 4 lines long (that's a minimum, with one line in the body of the function, one per declaration line and two per curly braces) to 10 lines. I'm not worried about code speed degradation because of that, as the code actually slows down not because of that at all, but because of crooked hands.

2. Don't be stingy with comments explaining meanings of actions, variables and functions. The 20-line limit for the function length remains intact in this case. If you break it, rewrite the function. You can use both single-line and multiline comments.

3. My functions are structured. There are top (zero) call level functions, 1st, 2nd, etc. Each function of the next level of call is directly called by the function of the previous level. For example, a function like this:

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

- is a third level function. Here:

open() is a first-level function,

pairsToOpen() is the second function (it is called by open()), and

combineAndVerify() - third (it is called by pairsToOpen() function).


The code of each function on the next level is indented further to the left than the code of the previous one. This makes it easier to see the structure of the whole program.

There are exceptions to this rule too (there are functions called by two functions of a higher structural level), but this is not common. This usually indicates that the code is not optimal, because the same action is performed in several different parts of the program.
There are, however, universal functions which can be called from anywhere. These are the output functions, and I put them in a special category.

3. Global variables: it's better to have fewer of them, but it's better not to overdo it here, either. You can stuff all these variables into formal function parameters, but then their calls will be too cumbersome to write and obscure in meaning.

4. Separate the actions of calculations and their output (in a file, the screen or SMS). I design all the output functions separately, and then paste the calls of such functions into the body of the calling function.

This rule, in addition to improving code clarity and clarity, has another side effect: if you do so, you can very easily cut off all the output from the code and significantly reduce code execution time: output is often the slowest action in a program.

5. Variable names: well, that's clear. Everyone has his own style, but it's still desirable to make them in such a way that they easily explain the meaning of variables.

I think that's enough to start with. If you want, you may add more.
 
Mathemat >> :
I think that's enough to start with. If you want, you may add something else.

The question is this. What is the most sensible way to build a programme?

1. Describe everything that can be done in the START function ?

2) Or may I describe all actions as user-defined functions, and then call them from the START function as required?

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

For example, the same trawl.

 

The second is better. This is what I am writing about. The trading functions should also be written as separate functions.

 

It's almost the same for me.

Except:

1. The number of lines in the function.

2. The number of functions.

I prioritise the speed of calculations. Therefore, the fewer functions and the less you call them, the faster the program runs.

If I can get rid of a function, I will use it.

I have only once failed to do so. The meta-quotes imposed a limit on the number of nested blocks.

I got an interface rendering function of 710 lines. It has 51 parameters. There are 21 arrays of them. So, this is what Metacquotes has managed to get to... :-)))

In general, I think the function is necessary only if it is called from different parts of the program and not very often. I prefer to repeat the code in each block for the sake of speed.

 
Zhunko >> :

The result is an interface drawing function of 710 lines. It has 51 parameters. There are 21 arrays of them.

Wow. But the output functions, I've already noted, represent an exception. As for execution speed, I think the cost of calling a function instead of directly writing the right block without a function is not that great - especially if the function is called in a loop. Rosh showed somewhere the difference between direct code and function call code.

 
Zhunko писал(а) >>

I prioritise the speed of computation. Therefore, the fewer functions and the less you call them, the faster the program runs.

If there is an opportunity to get rid of a function, I take advantage of it.

I agree. If a function is called less than three times, you'd better insert it into the body. I know it firsthand. I often have to edit other people's programs. If all you have is functions, you have to open two or three windows to make sure you do not get confused what happens when.

 
Mathemat >> :

Wow. But the output functions, I've already noted, represent an exception. As for execution speed, I think the cost of calling a function instead of directly writing the required block without a function is not that great - especially if the function is called in a loop. Somewhere Rosh showed the difference between direct code and code with function call.

Alexei, you may be right, I haven't checked lately, but...!

There must have been some problems in those days with MT4's memory manager for Metakvot. So, having removed all functions used to calculate indexes, I was very surprised with the result!... Calculation speed increased by 5 times and memory consumption decreased by 3 times!!!!

 
Zhunko писал(а) >>

Alexey, you may be right, haven't checked lately, BUT!...!

There must have been some problems in those days with the memory manager at MT4 for Metakvot. So, having removed all the functions used to calculate indices, I was very surprised by the result!... Calculation speed increased by 5 times and memory consumption decreased by 3 times!!!!

All arrays declared in functions are static. This means that these arrays are created only once (during the first call of the function), and they are stored in memory. Therefore, I try to make arrays global. Which is not good.

 
On the size of the function. I try to make the function fit on one screen. So that you can see the whole thing.
 

Yes, Vadim, the impact is there. I decided to check it out. Here are the results:

1. Simple summing cycle (500 million iterations):


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


Computation time in seconds: 4.42 - without calling add(), 36.7 with it.


2. A loop with more complex calculations (the same 500 million iterations):


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


Calculation time in seconds: 100.6 without add(), 142.1 with add().


Here are commented blocks with direct calculations in the loop which we make into a function for comparison. As we can see, there is a difference in any case, but it is very different.

What are the conclusions? If we form something very simple into a function, function call costs play a significant role, even very significant. In other words, it may be much more than the cost of calculations in the function body. If the calculations are more complex, the difference between the function's presence and its absence is greatly reduced.

Therefore it is better to design only blocks with more or less serious calculations into functions. I will try to take this into account when coding. But in any case the time difference is significant only when the loop has a lot of iterations: the cost of function call here is about 10^(-7) seconds.

Reason: