О стиле кодирования

 

Тему поднимаю потому, что имею приличный опыт кодирования и перекодирования давно написанного "с нуля" на МQL4 и хочу поделиться своим опытом.

Коллега, я не сомневаюсь в твоих способностях быстро написать программу, реализующую тот или иной алгоритм. Но я уже убедился в том, что если ты по какой-то причине забросил проект, вернулся к нему через месяц и не смог сразу разобраться в нем, то ты пишешь плохо. Дальше я изложу те требования к стилю оформления кода, которые выработал самостоятельно. Соблюдение этих требований, на мой взгляд, существенно упрощает дальнейшую его модификацию.

0. Не спеши кидаться в бой сразу и писать программу. Делай так, как рекомендуют классики: потрать несколько часов на обдумывание структуры программы. Потом ты сможешь сесть и быстро написать программу, ясную и четкую. Эти несколько часов окупятся в скорости написания и дальнейшей отладки многократно.

1. Длина функций не должна существенно превышать 20 строк. Если ты не можешь это реализовать, то где-то есть места, в которых ты недостаточно хорошо продумал логику и структуру кода. Более того, именно на самые длинные функции и взаимосвязь их с вызываемыми ими часто тратится максимальное время при отладке кода.

Например, вот сейчас длина моего кода - 629 строк, в нем 27 функций. Это вместе с описанием структуры вызова функции (2-6 строк) и кратким комментом перед каждой функцией, а также пустыми разделителями в 4-5 строк между функциями. Кроме того, блочные скобки (фигурные) я ставлю не скупясь, т.е. на каждую из двух скобок я отвожу по строке. Если убрать все комменты перед функциями и уменьшить количество разделителей между функциями, то на 27 функций останется порядка 400 строк, т.е. средняя длина функции у меня - около 15 строк.

Есть, конечно, исключения из этого правила - но это касается самых простых функций или функций вывода. Вообще говоря, функция должна выполнять не более 3-5 функционально различных действий. Иначе она будет запутанной. Между функционально различными действиями внутри функции я тоже обычно ставлю пустую строку.

У меня немало функций длиной всего от 4 (это минимум, при одной строке в теле функции, одной на строку декларации и двух на фигурные скобки) до 10 строк. Я не волнуюсь о снижении скорости кода из-за этого, т.к. код в действительности замедляется совсем не из-за этого, а из-за кривых рук.

2. Не скупись на комменты, поясняющие значения действий, переменных и смысл функций. Ограничение в 20 строк на длину функции при этом остается незыблемым. Если оно все же нарушается, перепиши функцию. Комменты можно ставить и однострочные, и многострочные.

3. Функции у меня структурированы. Есть функции высшего (нулевого) уровня вызова, 1-го, 2-го и т.п. Каждая функция следующего уровня вызова напрямую вызывается функцией предыдущего уровня. Например, вот такая функция:

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

- это ф-ция третьего уровня. Здесь:

open() - функция первого уровня,

pairsToOpen() - второго (она вызывается функцией open()), и

combineAndVerify() - третьего (она вызывается функцией pairsToOpen()).


Код каждой функции следующего уровня у меня имеет больший отступ от левого края, чем код функции предыдущего. Так лучше видна структура всей программы.

Из этого правила тоже бывают исключения (бывают функции, вызываемые двумя ф-циями более высокого структурного уровня), но это нечасто. Обычно это свидетельствует о неоптимальности кода, т.к. одно и то же действие выполняется в нескольких разных частях программы.
Однако есть универсальные функции, к-рые могут вызываться отовсюду. Это функции вывода, я их выделяю в особую категорию.

3. Глобальные переменные: лучше, чтобы их было поменьше, но и здесь лучше не перестараться. Можно запихнуть все такие переменные в формальные параметры функций, но тогда вызовы их будут слишком громоздкими по написанию и темными по смыслу.

4. Разделяйте действия по вычислениям и их выводу (в файл, на экран или SMS). Все функции вывода я оформляю отдельно и затем вставляю вызовы таких функций в тело вызывающей функции.

Это правило, помимо улучшения ясности и четкости кода, имеет еще один побочный эффект: если ты так и делаешь, ты можешь очень легко отрубить из кода весь вывод и существенно сократить время выполнения кода: вывод - это часто самое медленное действие в программе.

5. Имена переменных: ну тут все ясно. У каждого свой стиль, но все же желательно делать их такими, чтобы они легко поясняли смысл переменных.

Думаю, для начала достаточно. Желающие могут добавить что-нибудь еще.
 
 
Mathemat >>:
Думаю, для начала достаточно. Желающие могут добавить что-нибудь еще.

Вопрос такой. Как более целесообразно строить программу?

1. Описать все, что можно  в функции СТАРТ ?

2. Либо расписать все действия как  пользовательские функции, а потом по мере надобности вызывать их из функции СТАРТ ?

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

Например, тот же трал.

 

Второе лучше. Я об этом и пишу. Торговые функции тоже желательно писать отдельными функциями.

 

У меня почти всё также.

За исключением:

1. Количества строк в функции.

2. Количества функций.

У меня в приоритете скорость вычислений. По этому, чем меньше функций и меньше их вызываешь, тем быстрее работает программа.

Если есть возможность избавиться от функции, пользуюсь этой возможностью.

Только один раз не получилось это сделать. Метаквоты наложили ограничение на количество вложенных блоков.

Получилась функция отрисовки интерфейса из 710 строк. В ней 51 параметр. Из них 21 массив. Вот до чего Метаквоты довели... :-)))

Вообще, считаю, что функция нужна только, если она вызывается из разных частей программы и не очень часто. Для скорости предпочитаю повторить код в каждом блоке.

 
Zhunko >>:

Получилась функция отрисовки интерфейса из 710 строк. В ней 51 параметр. Из них 21 массив.

Ого. Но функции вывода, я уже отметил, представляют исключение. Что касается скорости выполнения, то, мне кажется, издержки вызова функции вместо прямого написания нужного блока без функции не настолько велики - особенно если функция вызывается в цикле. Где-то Rosh показывал разницу межу прямым кодом и кодом с вызовом функции.

 
Zhunko писал(а) >>

У меня в приоритете скорость вычислений. По этому, чем меньше функций и меньше их вызываешь, тем быстрее работает программа.

Если есть возможность избавиться от функции, пользуюсь этой возможностью.

Согласен. Если функция вызывается менее трех раз, лучше ее вставить в тело. Знаком непонаслышке. Часто приходится править чужие программы. Если все из функций, приходится открывать два, а то и три окна, чтобы не запутаться, что когда происходит.

 
Mathemat >>:

Ого. Но функции вывода, я уже отметил, представляют исключение. Что касается скорости выполнения, то, мне кажется, издержки вызова функции вместо прямого написания нужного блока без функции не настолько велики - особенно если функция вызывается в цикле. Где-то Rosh показывал разницу межу прямым кодом и кодом с вызовом функции.

Алексей, может ты и прав, не поверял в последенее время, НО!...

Наверно, в те времена были какие-то проблемы у Метаквотов с диспетчером памяти у МТ4. Так вот, убрав все функции, применявшиеся для вычисления индексов, я был очень удивлён результатом!... Скорость вычислений увеличилась в 5 раз, а потребление памяти уменьшилось в 3 раза!!!

 
Zhunko писал(а) >>

Алексей, может ты и прав, не поверял в последенее время, НО!...

Наверно, в те времена были какие-то проблемы у Метаквотов с диспетчером памяти у МТ4. Так вот, убрав все функции, применявшиеся для вычисления индексов, я был очень удивлён результатом!... Скорость вычислений увеличилась в 5 раз, а потребление памяти уменьшилось в 3 раза!!!

Все массивы, объявляемые в функциях статичны. А это значит что эти массивы создаются только один раз (при первом вызове функции) и хранятся в памяти. Потому стараюсь массивы делать глобальными. Что не есть хорошо.

 
По размеру функции. Стараюсь что бы функция помещалась на один экран. Что бы можно было видеть ее всю.
 

Да, Вадим, влияние есть. Решил это проверить. Вот результаты:

1. Цикл простого суммирования (500 млн. итераций):


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. Цикл с более сложными вычислениями (те же 500 млн. итераций):


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


Время вычисления в секундах: 100.6 - без вызова функции add(), 142.1 с ее вызовом.


Здесь закомментированы блоки с прямыми вычислениями в цикле, которые мы для сравнения оформляем в функцию. Как видим, разница в любом случае есть, но она очень разная.

Какие выводы? Если мы оформляем в функцию нечто очень простое, то затраты на вызов функции играют существенную роль, даже очень. Т.е. они могут быть намного больше затрат на вычисления в теле функции. Если же вычисления посложнее, то разница между присутствием функции и ее отсутствием круто уменьшается.

Следовательно, в функции лучше оформлять только блоки с более-менее серьезными вычислениями. Постараюсь это учитывать при кодировании. Но в любом случае разница во времени существенна только тогда, когда в цикле очень много итераций: стоимость вызова функции здесь получилась порядка 10^(-7) секунды.

Причина обращения: