Человеку удобно читать программу последовательно сверху-вниз без
необходимости частых переходов, особенно, возвратов назад. Языки
высокого уровня проектируются так, чтобы программист мог написать
программу в таком стиле. Часто для этого в язык вводится избыточная
конструкция. Например, оператор цикла for
в языке C
специально сделан так, чтобы собрать в одной строке действия,
ключевые для понимания логики работы цикла (инициализацию, условие и
инкремент), но он не является необходимым. Действительно, любой цикл
можно реализовать используя только операторы if
и goto
:
int k = 0; // Инициализация
loop_start:
if(k < 100) { // Проверка условия
printf("%d", k);
if((k % 3) == 0) {
printf(" делится на три!");
}
printf("\n");
k += 2; // Изменение управляющей переменной цикла
goto loop_start; // Переход к следующей итерации
}
for(int k = 0; k < 100; k += 2) {
printf("%d", k);
if((k % 3) == 0) {
printf(" делится на три!");
}
printf("\n");
}
Использование в программе оператора goto
в явном виде без
необходимости – общепризнанное зло. При чтении такой
программы приходится постояноо перепрыгивать взглядом между
строчками. Однако некоторые студенты умудряются составить цикл for
таким образом, что его невозможно понять без прочтения всего тела
цикла. Рассмотрим пример функции, которая заменяет элементы массива на
произведения двух последующих, если они существуют.
void replace_by_products(double *data, int length) {
double temp; // Начальное значение не присвоено; запомнили, что в переменной "мусор".
for(int k = 0; k < length; k++) {
if(k > 1) {
data[k-2] = temp * data[k]; // Что!? Умножаем элемент массива на "мусор"?
}
temp = data[k]; // ... Нет, оказывается, все нормально. Переменная получает корректное значение,
// которое сохраняется до следующей итерации цикла. А первую итерацию, когда значение
// переменной temp было еще неопределено, мы пропустили с помощью if.
}
}
Правильное форматирование кода в плане отступов и пробелов не делает
приведенный выше фрагмент хорошей программой. Для понимания ее
корректности нужно прочитать цикл до самого конца, заметить, что
значение переменной "передается" между итерациями и подумать о том,
чему будет равно значение temp на итерации k. Психологическая
сложность состоит в том, что в операторе присваивания temp =
data[k];
стоит переменная k
, но в том месте, где
переменная используется
data[k-2] = temp * data[k];
, значение k
уже изменилось. Получается. что читателю нужно помнить, что в момент
использования temp ее значение фактически соответствует data[k-1]
,
а не data[k]
как это написано ниже.
Конечно, приведенный пример очень простой и в нем легко разобраться,
но, как было показано в начале этой странице, при некотором старании
можно и однострочную функцию сделать абсолютно непонятной. Нужно
стремиться к тому, чтобы все переменные, которые используются на
очередной итерации цикла, получали свои значения в начале итерации. В
приведенном примере от переменной temp можно было вообще отказаться,
если заменить data[k-2] = temp * data[k];
на
data[k-2] = data[k-1] * data[k];
.
Не пишите лишних else
и не дублируйте
действия в обеих ветках условных операторов.
int do_something(double value) {
if(value < 0) {
return -1;
} else {
if(value > 1.5) {
// что-то делаем
printf("value=%f\n", value); // ... и печатаем value
} else {
// делаем что-то другое
printf("value=%f\n", value); // ... и печатаем value
}
}
return 0;
}
int do_something(double value) {
if(value < 0) {
return -1;
}
// else не нужен - return заканчивает выполнение функции.
if(value > 1.5) {
// что-то делаем
} else {
// делаем что-то другое
}
// Печать выносим из условия. Это эквивалентное преобразование.
printf("value=%f\n", value);
return 0;
}