Исходный код
int main(void)
{
int a=10000,b=0,c=2800,d,e=0,f[2801],g;FILE *fo;
if (!(fo=fopen("C:\\out","w"))) return1;
for (;b-c;) f[b++]=a/5;
for(;d=0,g=c*2;c-=14,fprintf(fo,"%.4d",e+d/a),e=d%a)
for(b=c;d+=f[b]*a, f[b]=d%--g,d/=g--,--b;d*=b);
}
Что не так в исходном коде
Вот замечательный пример того, насколько всё может быть непонятно даже в коротком простом коде. Возможно даже, этот код участвовал в каком-нибудь конкрусе непонятного кода, поэтому специально был написан настолько непонятным.
Печально то, что мы часто пишем подобные вещи на автомате и не замечаем этого. А страдают от этого наши коллеги, которым достанется такое наследство, и даже мы сами в будущем.
Разберу причины непонятности.
Форматирование
Код написан очень плотно, с минимумом «лишних» пробелов.
Нет названия у данных
Есть названия у переменных, а вот данные, заключенные в переменных, никак не называются.
Не выражена структура операций
Здесь есть пара операций, которые легко увидеть в плотном потоке:
- инициализация массива
f
, - вывод неких данных в файл, а вот смысл остальных действий скрыт во мгле.
За хаотичным потоком арифметических операторов не видна формула, лежащая в основе вычислений. Исправление малейшей ошибки в алгоритме станет адом.
Нет названия у операций
Что делают внешний и внутренний циклы? Какие из выполняемых арифметических операций связаны между собой, а какие независимы друг от друга. Очевидно, что некоторые связки операторов сложения, вычитания, присвивания, инкремента совместно достигают некие второстепенные цели по пути к одной конечной цели. Что это за цели: конечная и промежуточные?
Не выражен порядок операций
У любого цикла в любом языке программирования есть два аспекта:
- внешний (организационный, административный): как организуются итерации, сколько их должно быть,
- внутренний (содержательный, смысловой): что, собственно, происходит на каждой итерации.
Циклы for
в языке си — это такие синтаксические конструкции, которые помогают красиво отделить организационные операции цикла от содержательных. Организационные размещаются в круглых скобках вверху цикла, содержательные заключаются в фигурные и следуют за организационными.
В этом коротком фрагменте кода в двух циклах эта система нарушена: оба вида операций насильно перемещены в место, предназначенное только для организационных операций. А в третьем цикле организационные операции, наоборот, вынесены за круглые скобки.
Из-за этого очень трудно понять, какая операция следует за какой.
Как отрефакторить исходный код
Нетрудно догадаться, что избавиться от названных недостатков можно:
- раздав переменных говорящие имена
- извлекая фрагменты кода в отдельные функции с говорящими названиями
- упорядочив операторы в циклах в соответствии с типом (организационные и содержательные)
#define VALUE_COUNT 2800
#define VALUE_STEP 14
#define TOP_VALUE 10000
int main(void)
{
calculate_values(values, VALUE_COUNT, TOP_VALUE);
save_values(values, VALUE_COUNT / VALUE_STEP, "C:\\out");
}
void calculate_values(int[] output_values)
{
int root = 0;
int temp_values[VALUE_COUNT];
initialize_values(temp_values, VALUE_COUNT);
for (int i = 0; i < VALUE_COUNT; i++) {
int limit = VALUE_COUNT - i * VALUE_STEP;
int base = calculate_base(limit, temp_values);
output_values[i] = root + base / TOP_VALUE;
root = base % TOP_VALUE;
}
}
int calculate_base(int limit, int[] temp_values)
{
int base = 0;
for (int i = limit; --i; ) {
int t = base + temp_values[j] * TOP_VALUE;
int max_value = i * 2 - 1;
temp_values[i] = t % max_value;
base = t / max_value * i;
}
return base;
}
void initialize_values(int[] temp_values)
{
for (int i = 0; i < VALUE_COUNT; i++)
temp_values[i] = TOP_VALUE / 5;
}
void save_values(int[] values, int value_count, char *output_file)
{
FILE* fo = fopen(output_file, "w");
if (!fo) return;
for (int i = 0; i < value_count; i++)
fprintf(fo, "%.4d", values[i]);
}
Всё, сдаюсь. Формул за всеми вычислениями я так и не увидел, а наобум выбранные названия переменных вообще не добавляют ясности.
Максимум, что удалось сделать — сгруппировать и изолировать операции так, чтобы стало ясно, какие из них работают совместно, а какие относятся к вычислению не связанных вещей.
Теория
- Название переменной
- Название функции
- Рефакторинг «извлечение функции»
- Принцип единственного знания
- Алгоритмы работают с данными, а не с переменными
- Исполнение алгоритмов в уме