Sobre el estilo de codificación

 

Traigo este tema porque tengo una experiencia decente de codificación y recodificación hace tiempo escrita desde cero en MQL4 y quiero compartir mi experiencia.

Colega, no dudo de tu capacidad para escribir rápidamente un programa que implemente algún algoritmo. Pero ya he asegurado que si abandonaste el proyecto por cualquier motivo, volviste a él un mes después y no pudiste entenderlo inmediatamente, no eres un buen escritor. En lo que sigue les hablaré de mis propias exigencias al estilo de codificación. El cumplimiento de estos requisitos simplifica las modificaciones posteriores.

0. No se apresure a entrar en acción de inmediato y escriba el programa. Haz lo que recomiendan los clásicos: dedica unas horas a pensar en la estructura del programa. Así podrá sentarse y escribir un programa de forma rápida, clara y concisa. Estas pocas horas se verán recompensadas por la velocidad de escritura y la depuración posterior muchas veces.

1. La longitud de las funciones no debe superar significativamente las 20 líneas. Si no puedes implementarlo, es que no has pensado bien la lógica y la estructura del código. Además, es en las funciones más largas y su relación con las funciones a las que llaman donde se suele invertir más tiempo en la depuración del código.

Por ejemplo, mi código tiene ahora 629 líneas y contiene 27 funciones. Eso junto con una descripción de la estructura de la llamada a la función (2-6 líneas) y un breve comentario antes de cada función, así como separadores de 4-5 líneas en blanco entre las funciones. Además, no pongo paréntesis de bloque (corchetes) con moderación, es decir, por cada dos paréntesis dedico una línea. Si elimino todos los comentarios antes de las funciones y reduzco el número de separadores entre ellas, 27 funciones ocuparán unas 400 líneas, es decir, la longitud media de mis funciones es de unas 15 líneas.

Hay, por supuesto, excepciones a esta regla - pero eso se aplica a las funciones más simples o a las funciones de salida. En general, una función no debe realizar más de 3-5 acciones funcionalmente diferentes. De lo contrario, será confuso. También suelo poner una línea en blanco entre las diferentes acciones de la función dentro de la misma.

Tengo bastantes funciones que tienen desde 4 líneas (eso es un mínimo, con una línea en el cuerpo de la función, una por línea de declaración y dos por llaves) hasta 10 líneas. No me preocupa la degradación de la velocidad del código debido a esto, ya que el código realmente se ralentiza no por esto en absoluto, sino por las manos torcidas.

2. No seas tacaño con los comentarios que explican el significado de las acciones, variables y funciones. El límite de 20 líneas para la longitud de la función permanece intacto en este caso. Si lo rompes, reescribe la función. Puede utilizar comentarios de una o varias líneas.

3. Mis funciones están estructuradas. Hay funciones de nivel de llamada superior (cero), 1ª, 2ª, etc. Cada función del siguiente nivel de llamada es llamada directamente por la función del nivel anterior. Por ejemplo, una función como ésta:

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

- es una función de tercer nivel. Aquí:

open() es una función de primer nivel,

pairsToOpen() es la segunda función (es llamada por open()), y

combineAndVerify() - tercero (es llamado por la función pairsToOpen()).


El código de cada función del siguiente nivel tiene una sangría más a la izquierda que el código de la función anterior. De este modo, es más fácil ver la estructura de todo el programa.

También hay excepciones a esta regla (hay funciones llamadas por dos funciones de un nivel estructural superior), pero no es lo habitual. Esto suele indicar que el código no es óptimo, porque la misma acción se realiza en varias partes diferentes del programa.
Sin embargo, hay funciones universales que pueden ser llamadas desde cualquier lugar. Estas son las funciones de salida, y las pongo en una categoría especial.

3. Variables globales: es mejor tener menos, pero tampoco hay que exagerar. Puedes meter todas estas variables en parámetros de funciones formales, pero entonces sus llamadas serán demasiado engorrosas de escribir y de significado oscuro.

4. Separar las acciones de los cálculos y su salida (en un archivo, en la pantalla o en un SMS). Diseño todas las funciones de salida por separado, y luego pego las llamadas de dichas funciones en el cuerpo de la función de llamada.

Esta regla, además de mejorar la claridad del código, tiene otro efecto secundario: si lo haces, puedes cortar muy fácilmente toda la salida del código y reducir significativamente el tiempo de ejecución del mismo: la salida suele ser la acción más lenta de un programa.

5. Nombres de las variables: bueno, eso está claro. Cada uno tiene su propio estilo, pero aún así es deseable hacerlos de tal manera que expliquen fácilmente el significado de las variables.

Creo que es suficiente para empezar. Si quieres, puedes añadir más.
 
Mathemat >> :
Creo que es suficiente para empezar. Si quieres, puedes añadir algo más.

La cuestión es la siguiente. ¿Cuál es la forma más sensata de construir un programa?

1. Describa todo lo que se puede hacer en la función START ?

2) ¿O puedo describir todas las acciones como funciones definidas por el usuario y luego llamarlas desde la función START según sea necesario?

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

Por ejemplo, la misma red de arrastre.

 

El segundo es mejor. Esto es lo que estoy escribiendo. Las funciones comerciales también deben escribirse como funciones separadas.

 

A mí me pasa más o menos lo mismo.

Excepto:

1. El número de líneas de la función.

2. El número de funciones.

Doy prioridad a la velocidad de los cálculos. Por lo tanto, cuantas menos funciones y menos se llamen, más rápido se ejecutará el programa.

Si puedo librarme de una función, la utilizaré.

Sólo he fallado una vez. Las metacomillas imponen un límite al número de bloques anidados.

Tengo una función de representación de la interfaz de 710 líneas. Tiene 51 parámetros. Hay 21 matrices de ellos. Esto es lo que ha conseguido Metacquotes... :-)))

En general, creo que la función es necesaria sólo si se llama desde diferentes partes del programa y no muy a menudo. Prefiero repetir el código en cada bloque por razones de velocidad.

 
Zhunko >> :

El resultado es una función de dibujo de interfaz de 710 líneas. Tiene 51 parámetros. Hay 21 matrices de ellos.

Vaya. Pero las funciones de salida, ya lo he señalado, representan una excepción. En cuanto a la velocidad de ejecución, creo que el coste de llamar a una función en lugar de escribir directamente el bloque correcto sin una función no es tan grande - especialmente si la función se llama en un bucle. Rosh mostró en alguna parte la diferencia entre el código directo y el código de llamada a función.

 
Zhunko писал(а) >>

Doy prioridad a la velocidad de cálculo. Por lo tanto, cuantas menos funciones y menos se llamen, más rápido se ejecutará el programa.

Si hay una oportunidad de deshacerse de una función, la aprovecho.

Estoy de acuerdo. Si una función es llamada menos de tres veces, es mejor que la inserte en el cuerpo. Lo sé de primera mano. A menudo tengo que editar los programas de otras personas. Si todo lo que tienes son funciones, tienes que abrir dos o tres ventanas para asegurarte de que no te confundes con lo que pasa.

 
Mathemat >> :

Vaya. Pero las funciones de salida, ya lo he señalado, representan una excepción. En cuanto a la velocidad de ejecución, creo que el coste de llamar a una función en lugar de escribir directamente el bloque requerido sin una función no es tan grande - especialmente si la función se llama en un bucle. En algún lugar Rosh mostró la diferencia entre el código directo y el código con llamada a función.

Alexei, puede que tengas razón, no lo he comprobado últimamente, pero...

Debe haber habido algunos problemas en esos días con el gestor de memoria de MT4 para Metakvot. Así que, tras eliminar todas las funciones utilizadas para calcular los índices, me sorprendió mucho el resultado... ¡¡¡¡La velocidad de cálculo se ha multiplicado por 5 y el consumo de memoria se ha reducido por 3 veces!!!!

 
Zhunko писал(а) >>

Alexey, puede que tengas razón, no lo he comprobado últimamente, PERO...

Debe haber habido algunos problemas en esos días con el gestor de memoria de Metacvot en MT4. Así que, tras eliminar todas las funciones utilizadas para calcular los índices, me sorprendió mucho el resultado... ¡¡¡¡La velocidad de cálculo se ha multiplicado por 5 y el consumo de memoria se ha reducido por 3 veces!!!!

Todas las matrices declaradas en funciones son estáticas. Esto significa que estas matrices se crean sólo una vez (durante la primera llamada de la función), y se almacenan en la memoria. Por lo tanto, intento que las matrices sean globales. Lo cual no es bueno.

 
Sobre el tamaño de la función. Intento que la función quepa en una pantalla. Para que puedas ver el conjunto.
 

Sí, Vadim, el impacto está ahí. Decidí comprobarlo. Aquí están los resultados:

1. Ciclo de suma simple (500 millones de iteraciones):


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


Tiempo de cálculo en segundos: 4,42 - sin llamar a add(), 36,7 con ella.


2. Un bucle con cálculos más complejos (los mismos 500 millones de iteraciones):


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


Tiempo de cálculo en segundos: 100,6 sin add(), 142,1 con add().


Aquí hay bloques comentados con cálculos directos en el bucle que convertimos en una función para comparar. Como podemos ver, hay una diferencia en cualquier caso, pero es muy diferente.

¿Cuáles son las conclusiones? Si formamos algo muy simple en una función, los costes de las llamadas a la función juegan un papel importante, incluso muy significativo. En otras palabras, puede ser mucho más que el coste de los cálculos en el cuerpo de la función. Si los cálculos son más complejos, la diferencia entre la presencia de la función y su ausencia se reduce considerablemente.

Por lo tanto, es mejor diseñar sólo bloques con cálculos más o menos serios en funciones. Intentaré tenerlo en cuenta a la hora de codificar. Pero, en cualquier caso, la diferencia de tiempo sólo es significativa cuando el bucle tiene muchas iteraciones: el coste de la llamada a la función es aquí de unos 10^(-7) segundos.

Razón de la queja: