Исходный код
long gr, ri, r1, r2, y, z, z_rev;
for (i = 2; i < qr_count; i++) {
step = 0;
ri = r[i];
gr = - mpz_fdiv_ui(lowM, pm[i][step]);
r1 = gr + ri;
r2 = gr + pm[i][step] - ri;
if (r1 < 0) {
r1 += pm[i][step];
}
if (r2 < 0) {
r2 += pm[i][step];
}
while (r1 < (long) Ml2) {
sieve[r1][i] += 1;
r1 += pm[i][step];
}
while (r2 < (long) Ml2) {
sieve[r2][i] += 1;
r2 += pm[i][step];
}
for (step = 1; step < degr[i]; step++) {
gr = - mpz_fdiv_ui(lowM, pm[i][step]);
y = ri*ri;
y -= Npm[i][step];
y = y/( (long) pm[i][step-1]);
if (y < 0) { y += pm[i][step]; }
z_rev = (ri<<1) % QR[i];
//?IOOA?EOO ?OI?AOEO IA UAAEEIE?AIEA
if (z_rev == 0) { y = 0; }
else {
z = 1;
while ((z_rev*z % QR[i]) != 1) { z++;}
y *= z*pm[i][step-1];
y = y % pm[i][step];
}
ri -= y;
if (ri < 0) { ri += pm[i][step]; }
r1 = gr+ri;
r2 = gr+pm[i][step]-ri;
if (r1 < 0) { r1 += pm[i][step]; }
if (r2 < 0) { r2 += pm[i][step]; }
while (r1 < (long) Ml2) {
sieve[r1][i] += 1;
r1 += pm[i][step];
}
while (r2 < (long) Ml2) {
sieve[r2][i] += 1;
r2 +=pm[i][step];
}
}
}
Что не так в этом коде
Чрезмерная сложность кода
Ничего не могу сказать о корректности и предназначении данного кода (какие-то математические расчёты), но что могу сказать сразу и однозначно — этот код переусложнён.
Что делает его переусложнённым?
Даже в этом фрагменте (а это именно фрагмент) чрезмерное количество переменных и операций.
Входные переменные:
qr_count
— числоr
— одномерный массивlowM
— видимо, числоpm
— двумерный массивMl2
— числоdegr
— одномерный массивNpm
— двумерный массивQR
— одномерный массив
Рабочие (временные) переменные:
- gr, ri, r1, r2, y, z, z_rev, i, step — целые числа
Выходные переменные
sieve
— двумерный массив
Итого, если я не сбился со счёта, тут 18 переменных, а операции я уже даже считать не буду.
А ещё отдельными объектами для мозга являются промежуточные значения переменных, под которые мы новые переменные не отводим. В этом примере это переменная y
:
y = ri*ri;
y -= Npm[i][step];
y = y/( (long) pm[i][step-1]);
if (y < 0) { y += pm[i][step]; }
z_rev = (ri<<1) % QR[i];
if (z_rev == 0) { y = 0; }
else {
z = 1;
while ((z_rev*z % QR[i]) != 1) { z++;}
y *= z*pm[i][step-1];
y = y % pm[i][step];
}
Чтобы уменьшить сложность исходного фрагмента кода, буду применять метод рефакторинга под названием «извлечение метода» [Фаулер].
Глядя на наш пример кода, я вижу в нём группу строк со своим локальным набором входных и промежуточных переменных (сразу превращаю её в функцию):
function step_sieve(long ri, long i, long step) {
gr = - mpz_fdiv_ui(lowM, pm[i][step]);
r1 = gr + ri;
r2 = gr + pm[i][step] - ri;
if (r1 < 0) {
r1 += pm[i][step];
}
if (r2 < 0) {
r2 += pm[i][step];
}
while (r1 < (long) Ml2) {
sieve[r1][i] += 1;
r1 += pm[i][step];
}
while (r2 < (long) Ml2) {
sieve[r2][i] += 1;
r2 += pm[i][step];
}
}
Нетрудно заметить, что этот же фрагмент кода повторяется и внутри вложенного цикла.
Внутри кода новой функции также вижу самостоятельный набор операций, причём тоже повторяющийся дважды. Проведу выделение функции и здесь:
function step_sieve(long ri, long i, long step) {
gr = - mpz_fdiv_ui(lowM, pm[i][step]);
r1 = gr + ri;
fill_sieve(r1, i, step);
r2 = gr + pm[i][step] - ri;
fill_sieve(r2, i, step);
}
function fill_sieve(long r_, long i, long step) {
if (r_ < 0) {
r_ += pm[i][step];
}
while (r_ < (long) Ml2) {
sieve[r_][i] += 1;
r_ += pm[i][step];
}
}
В исходном коде вижу код, который занимается вычислением значения переменной y
и никак не затрагивает переменные вне этого фрагмента. Сделаю из него сразу две функции:
function calculate_y(long ri, long i, long step) {
long y;
y = ri*ri;
y -= Npm[i][step];
y = y/( (long) pm[i][step-1]);
if (y < 0) { y += pm[i][step]; }
return y;
}
function correct_y(long y, long i, long step) {
long z_rev, z;
z_rev = (ri<<1) % QR[i];
if (z_rev == 0) { y = 0; }
else {
z = 1;
while ((z_rev*z % QR[i]) != 1) { z++; }
y *= z*pm[i][step-1];
y = y % pm[i][step];
}
return y;
}
С учетом всех проведённых рефакторингов «выделение функции» исходный фрагмент принимает следующий вид:
for (i = 2; i < qr_count; i++) {
step = 0;
ri = r[i];
step_sieve(ri, i, step);
for (step = 1; step < degr[i]; step++) {
y = calculate_y(ri, i, step);
y = correct_y(y, i, step);
ri -= y;
if (ri < 0) { ri += pm[i][step]; }
step_sieve(ri, i, step);
}
}
Для окончательного блеска сделаю ещё кое-что:
for (long i = 2; i < qr_count; i++) {
long ri = r[i];
step_sieve(ri, i, 0);
for (long step = 1; step < degr[i]; step++) {
ri = decrease_ri(ri, i, step);
step_sieve(ri, i, step);
}
}
function decrease_ri(long ri, long i, long step) {
long y = calculate_y(ri, i, step);
y = correct_y(y, i, step);
ri -= y;
if (ri < 0) { ri += pm[i][step]; }
return ri;
}
Безусловно, суммарно по количеству строк отрефакторенный фрагмент превосходит исходный, однако же для мозга теперь нет необходимости исследовать и понимать весь фрагмент целиком.
Низкая степень понятности операций
Другой проблемный аспект исходного примера кода — низкая степень понятности каждого отдельного его фрагмента из-за невыразительных имён переменных. Чтобы исправить этот аспект, все переменные в примере должны иметь существенно более длинные названия.
Не исключаю, что имеющиеся названия были присвоены переменным исходя из символьной записи формул, которыми описывался исходный математический алгоритм. Тем не менее, у каждой величины, участвующей в расчётах, имеется вполне человеческое название, которым следует также именовать и переменные в коде.
Теория
- Длина функций
- Названия переменных
- Выделение функции
- Ребусы в коде
- Принцип единственного уровня
- Принцип единственного знания
- Принцип единственной реализации
Литература
- Фаулер М. Рефакторинг: улучшение существующего кода. — СПб: Символ-Плюс, 2016
- Макконнелл С. Совершенный код. Мастер класс. — М.: «Русская редакция», 2017.
- Закон Миллера
- Цикломатическая сложность
- Когнитивная сложность