Пример рефакторинга жёстко обфусцированного кода для вычисления непонятно чего


Исходный код

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]);
}

Всё, сдаюсь. Формул за всеми вычислениями я так и не увидел, а наобум выбранные названия переменных вообще не добавляют ясности.

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

Теория