Sobre o estilo de codificação

 

Eu levanto este tópico porque tenho uma experiência decente de codificação e recodificação há muito tempo escrita do zero na MQL4 e quero compartilhar minha experiência.

Meu colega, não duvido de sua capacidade de escrever rapidamente um programa implementando um algoritmo. Mas já me certifiquei de que se você abandonou o projeto por qualquer razão, voltou a ele um mês depois, e não pôde entendê-lo imediatamente, você não é um bom escritor. No que se segue, falarei sobre minhas próprias exigências ao estilo de codificação. A observância destes requisitos simplifica outras modificações.

0. Não se apresse a agir imediatamente e escreva o programa. Faça como os clássicos recomendam: passe algumas horas pensando sobre a estrutura do programa. Então você pode sentar-se e escrever um programa de forma rápida, clara e concisa. Essas poucas horas vão compensar a velocidade da escrita e a depuração muitas vezes mais.

1. O comprimento das funções não deve exceder significativamente 20 linhas. Se não for possível implementá-lo, há alguns lugares onde não se pensou suficientemente bem na lógica e na estrutura do código. Além disso, é sobre as funções mais longas e sua relação com as funções que eles chamam que a maior parte do tempo é gasto em depuração de código.

Por exemplo, meu código agora é de 629 linhas e contém 27 funções. Isso junto com uma descrição da estrutura de chamada de função (2-6 linhas) e um breve comentário antes de cada função, assim como 4-5 separadores de linha em branco entre as funções. Além disso, eu não coloco parênteses de bloco (parênteses encaracolados) com moderação, ou seja, para cada um dos dois parênteses eu aloco uma linha. Se eu remover todos os comentários antes das funções, e reduzir o número de separadores entre funções, então 27 funções tomarão cerca de 400 linhas, ou seja, o comprimento médio das minhas funções é de cerca de 15 linhas.

Existem, é claro, exceções a esta regra - mas isso se aplica às funções mais simples ou funções de saída. De modo geral, uma função não deve realizar mais do que 3-5 ações funcionalmente diferentes. Caso contrário, será confuso. Eu também costumo colocar uma linha em branco entre as ações funcionalmente diferentes da função.

Tenho algumas funções que têm apenas 4 linhas (isso é um mínimo, com uma linha no corpo da função, uma por linha de declaração e duas por aparelho de atadura) a 10 linhas. Não estou preocupado com a degradação da velocidade do código por causa disso, pois o código na verdade abranda não por causa disso, mas por causa das mãos tortas.

2. Não seja mesquinho com comentários que explicam os significados das ações, variáveis e funções. Neste caso, o limite de 20 linhas para o comprimento da função permanece intacto. Se você quebrá-la, reescreva a função. Você pode usar tanto os comentários de uma linha como os comentários de várias linhas.

3. Minhas funções são estruturadas. Há funções de nível de chamada superior (zero), 1º, 2º, etc. Cada função do próximo nível de chamada é chamada diretamente pela função do nível anterior. Por exemplo, uma função como esta:

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

- é uma função de terceiro nível. Aqui:

aberto() é uma função de primeiro nível,

parToOpen() é a segunda função (é chamada por open()), e

combineAndVerify() - terceiro (é chamada pela função de paresToOpen()).


O código de cada função no nível seguinte é recuado mais à esquerda do que o código da função anterior. Isto facilita a visualização da estrutura de todo o programa.

Há exceções a esta regra também (há funções chamadas por duas funções de nível estrutural superior), mas isto não é comum. Isto geralmente indica que o código não é ótimo, porque a mesma ação é realizada em várias partes diferentes do programa.
Existem, no entanto, funções universais que podem ser chamadas de qualquer lugar. Estas são as funções de saída, e eu as coloco em uma categoria especial.

3. Variáveis globais: é melhor ter menos delas, mas também é melhor não exagerar aqui. Você pode inserir todas estas variáveis em parâmetros formais de funções, mas então suas chamadas serão muito incômodas para escrever e obscuras no significado.

4. Separar as ações dos cálculos e sua saída (em um arquivo, na tela ou SMS). Eu desenho todas as funções de saída separadamente, e depois colo as chamadas de tais funções no corpo da função de chamada.

Esta regra, além de melhorar a clareza e clareza do código, tem outro efeito colateral: se você fizer isso, você pode muito facilmente cortar toda a saída do código e reduzir significativamente o tempo de execução do código: a saída é muitas vezes a ação mais lenta em um programa.

5. Nomes variáveis: bem, isso é claro. Cada um tem seu próprio estilo, mas ainda assim é desejável fazê-los de tal forma que eles expliquem facilmente o significado das variáveis.

Acho que isso é o suficiente para começar. Se você quiser, pode acrescentar mais.
 
Mathemat >> :
Acho que isso é o suficiente para começar. Se você quiser, pode acrescentar algo mais.

A questão é a seguinte. Qual é a maneira mais sensata de construir um programa?

1. descrever tudo o que pode ser feito na função START ?

2) Ou posso descrever todas as ações como funções definidas pelo usuário, e então chamá-las da função INICIAR, conforme necessário?

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

Por exemplo, a mesma rede de arrasto.

 

A segunda é melhor. É sobre isso que estou escrevendo. As funções comerciais também devem ser escritas como funções separadas.

 

Para mim é praticamente a mesma coisa.

Exceto:

1. O número de linhas na função.

2. O número de funções.

Eu dou prioridade à velocidade dos cálculos. Portanto, quanto menos funções e quanto menos você as chamar, mais rápido o programa será executado.

Se eu puder me livrar de uma função, eu a usarei.

Só uma vez não o fiz. As metaquotas impunham um limite para o número de blocos aninhados.

Eu tenho uma função de renderização de interface de 710 linhas. Ela tem 51 parâmetros. Há 21 matrizes delas. Então, foi a isto que a Metacquotes conseguiu chegar... :-)))

Em geral, acho que a função só é necessária se for chamada de diferentes partes do programa e não com muita freqüência. Prefiro repetir o código em cada bloco por uma questão de velocidade.

 
Zhunko >> :

O resultado é uma função de desenho de interface de 710 linhas. Ela tem 51 parâmetros. Há 21 matrizes delas.

Uau. Mas as funções de saída, eu já notei, representam uma exceção. Quanto à velocidade de execução, acho que o custo de chamar uma função em vez de escrever diretamente o bloco necessário sem uma função não é tão grande assim - especialmente se a função for chamada em loop. Rosh mostrou em algum lugar a diferença entre código direto e código de chamada de função.

 
Zhunko писал(а) >>

Eu dou prioridade à velocidade do cálculo. Portanto, quanto menos funções e quanto menos você as chamar, mais rápido o programa será executado.

Se há uma oportunidade de se livrar de uma função, eu aproveito para aproveitá-la.

Eu concordo. Se uma função é chamada menos de três vezes, é melhor inseri-la no corpo. Eu o conheço em primeira mão. Muitas vezes tenho que editar os programas de outras pessoas. Se tudo o que você tem são funções, você tem que abrir duas ou três janelas para ter certeza de não confundir o que acontece quando.

 
Mathemat >> :

Uau. Mas as funções de saída, eu já notei, representam uma exceção. Quanto à velocidade de execução, acho que o custo de chamar uma função em vez de escrever diretamente o bloco necessário sem uma função não é tão grande - especialmente se a função for chamada em loop. Em algum lugar, Rosh mostrou a diferença entre código direto e código com chamada de função.

Alexei, você pode estar certo, eu não tenho verificado ultimamente, mas...!

Deve ter havido alguns problemas naqueles dias com o gerente de memória da MT4 para a Metakvot. Então, tendo retirado todas as funções utilizadas para calcular índices, fiquei muito surpreso com o resultado! A velocidade de cálculo aumentou 5 vezes e o consumo de memória diminuiu 3 vezes!!!!

 
Zhunko писал(а) >>

Alexey, você pode estar certo, não checou ultimamente, MAS!...!

Deve ter havido alguns problemas naqueles dias com o gerente de memória do MT4 da Metakvot. Então, tendo retirado todas as funções utilizadas para calcular índices, fiquei muito surpreso com o resultado! A velocidade de cálculo aumentou 5 vezes e o consumo de memória diminuiu 3 vezes!!!!

Todas as matrizes declaradas em funções são estáticas. Isto significa que estas matrizes são criadas apenas uma vez (durante a primeira chamada da função), e são armazenadas na memória. Portanto, eu tento tornar os arrays globais. O que não é bom.

 
Sobre o tamanho da função. Eu tento fazer com que a função caiba em uma tela. Para que você possa ver a coisa toda.
 

Sim, Vadim, o impacto está lá. Decidi verificar. Aqui estão os resultados:

1. Ciclo de soma simples (500 milhões de iterações):


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


Tempo de cálculo em segundos: 4,42 - sem chamar add(), 36,7 com ele.


2. Um loop com cálculos mais complexos (os mesmos 500 milhões de iterações):


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


Tempo de cálculo em segundos: 100,6 sem add(), 142,1 com add().


Aqui são comentados blocos com cálculos diretos no laço que tornamos uma função para comparação. Como podemos ver, há uma diferença em qualquer caso, mas é muito diferente.

Quais são as conclusões? Se formamos algo muito simples em uma função, os custos de chamada de função desempenham um papel significativo, mesmo muito significativo. Em outras palavras, pode ser muito mais do que o custo dos cálculos no corpo da função. Se os cálculos são mais complexos, a diferença entre a presença da função e sua ausência é muito reduzida.

Portanto, é melhor projetar somente blocos com cálculos mais ou menos sérios em funções. Vou tentar levar isto em consideração ao codificar. Mas em qualquer caso a diferença de tempo é significativa apenas quando o laço tem muitas iterações: o custo da chamada de função aqui é de cerca de 10^(-7) segundos.

Razão: