Пособие по практике программирования

сани для мотобуксировщика


Выражения


Итак, имена надо подбирать так, чтобы максимально облегчить жизнь читателю; точно так же и выражения следует писать так, чтобы их смысл был предельно ясен. Пишите самый ясный код, какой только возможен. Вставляйте пробелы вокруг операторов для логической группировки выражений; вообще, форматируйте выражения так, чтобы сделать их наиболее удобочитаемыми. Идея тривиальна, но очень полезна: совсем как мысль о том, что чем аккуратнее убран ваш рабочий стол, тем проще на нем найти необходимую вещь. Правда, в отличие от рабочего стола в вашей программе могут копаться и посторонние — и им тоже должно быть удобно.

Форматируйте код, подчеркивая его структуру. Форматирование кода логично выбранными отступами — самый простой способ добиться того, чтобы структура программы стала самоочевидна. Приведенный фрагмент плохо отформатирован:

 ? for(n++;n<100;field[n++]='\(O');
? *i = \О'; return( '\n);

Проведя простейшее форматирование, мы несколько улучшим его:

Еще лучше — перенести оператор присваивания в тело цикла и выделить инкремент (приращение) счетчика в отдельный оператор, тогда цикл примет более традиционный вид и, следовательно, можно будет быстрее догадаться о его смысле.

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

 if (!(block_id < actblks) !(blocked >= unblocks))


Здесь каждая проверка используется с отрицанием, однако необходимости в этом нет никакой. Достаточно изменить знаки сравнения, и можно обойтись без отрицаний:

 , if ((blocked >= actblks) || (blockj.d < unblocks))

Теперь код читается вполне естественно.

Используйте скобки для устранения неясностей. Скобки подчеркивают смысловую группировку кода, и их можно использовать для упрощения понимания его структуры даже тогда, когда с точки зрения грамматики в их применении нет необходимости. Так, в предыдущем примере внутренние скобки не нужны, однако их применение как минимум ничему не мешает. Бывалый программист мог бы и опустить их, поскольку операторы сравнения (< <= == ! = >= >) имеют более низкий приоритет, чем логические операторы (&& и | |).

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

 while ((с = getchar()) != EOF)
...

Побитовые операции & и | имеют более низкий приоритет, чем "обычные" операции сравнения типа ==, так что, несмотря на, казалось бы, очевидный порядок выполнения, выражение

if (x&MASK == BITS)

на самом деле выполнится как

 ? if (х & (MASK==BITS))
?

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

if ((x&MASK) == BITS)

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

 ? lеар_уеаг = у % 4 == 0 && у % 100 ! = 0 | | у % 400 == 0; 

но с ними фрагмент станет гораздо проще для понимания:

lеар_уеаг = ((у%4 == 0) && (у%100 != 0)) | (у%400 ==0);

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

Разбивайте сложные выражения. Языки С, C++ и Java имеют богатый и разнообразный синтаксис, поэтому многие увлекаются втискиванием всего подряд в одну конструкцию. Приведенное выражение весьма компактно, однако в нем содержится слишком много операторов:

 *х += (*xp=(2*k < (n-m) ? c [k+1] : d[k--]));

Если разбить это выражение на несколько, оно станет гораздо более удобочитаемым:

if (2*k < n-m)
*хр = c[k+1j;
else
*xp = d[k--];
*x += *xp;

Будьте проще. Безудержная энергия программистов иногда направляется на то, чтобы написать наиболее краткий код или достичь результата самым хитрым и красивым способом. Иногда, впрочем, эти таланты тратятся впустую: настоящая задача в том, чтобы написать код попроще, а не похлеще. Вот, к примеру, что делает это замысловатое выражение?

? subkey = subkey » (bitoff - ((bitoff » 3) « 3));

Самое внутреннее выражение сдвигает bitoff на три бита вправо. Результат сдвигается обратно влево, при этом три сдвинутых бита замещаются нулями. Затем результат вычитается из первоначального значения, оставляя три младших бита bitoff, которые используются для сдвига subkey вправо.

Таким образом, приведенная конструкция эквивалентна

subkey = subkey » (bitoff & 0x7);

Над первой версией приходится гадать некоторое время, чтобы понять, что там происходит; со второй же все просто и ясно. Опытные программисты записали бы ее еще короче:

subkey »= bitoff & 0x7;

Некоторые конструкции кажутся специально созданными для того, чтобы сбить читателя с толку. Например, применение оператора ? : может привести к появлению абсолютно загадочного кода:

 ? child=(!LC&&!RC)?0:(!LC?RC:LC);

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

f (LC == 0 && RC == 0)
child = 0; else if (LC == 0)
child = RC; else
child = LC;

Операция ? : хороша только для коротких и простых выражений-, где она может заменить четыре строки if-else одной, как в следующем примере:

 max = (а > b) ? а : b;

или даже в таком случае:

 printf("The list has %d item%s\n", n, n==1 ? ""
: "s");

однако подобная замена все же не является распространенной.


Ясность и краткость — не одно и то же. Часто более ясный код будет и более коротким (как в примере со сдвигом битов), однако он может быть и, наоборот, более длинным (как в примере с if-else). Главным критерием выбора должна служить простота восприятия.

Будьте осторожны с побочными эффектами. Операции типа ++ имеют побочные эффекты: они не только возвращают значение, но еще и изменяют операнд. Эти побочные эффекты иногда могут быть весьма удобны, но они же могут вызвать трудности из-за того, что получение значения и обновление переменной могут происходить не тогда, когда ожидается. В С и C++ порядок выполнения этих побочных эффектов вообще не определен, поэтому нижеприведенное множественное присваивание, скорее всего, будет выдавать неправильный ответ:

 ? str[i++] = str[i++] = ' ';

Смысл выражения — записать пробел в следующие две позиции строки str. Однако в зависимости от того, когда будет обновляться i, позиция в st r может быть пропущена, и в результате переменная 1 будет увеличена только на 1. Разбейте выражение на два:

str[i++] = ' '; 
str[i++] =''.-"';

Даже если в вы ражении содержится только один инкремент, результат может быть неоднозначным:

? array[i++] = i;

Если изначально i имеет значение 3, то элемент массива может принять значение 3 либо 4.

Побочные эффекты есть не только у инкремента и декремента; ввод-вывод — еще один потенциальный источник возникновения закулисных действий. В следующем примере осуществляется попытка прочитать два взаимосвязанных числа из стандартного ввода:

scanf ("' &yr, &profit[yr]);

Выражение неверно, поскольку одна его часть изменяет уг, а другая использует ее. Значение prof it[yr] будет правильным только в том случае, если новое значение уг будет таким же, как и старое. Вы, наверное, думаете, что все дело в порядке вычисления аргументов, однако в действительности проблема здесь в том, что все аргументы scanf вычисляются до того, как эта операция вызывается на выполнение, так что &profit[yr] всегда будет вычисляться с использованием старого значения у г. Проблемы подобного рода могут возникнуть в любом языке программирования. Для исправления достаточно опять же разбить выражение на две части:

 scanf("%d", &yr);
scanf("%d", &profit[yr]);

Будьте осторожны с любым выражением, вызывающим побочные эффекты.

Упражнение 1-4

Улучшите каждый из приведенных фрагментов:

Упражнение 1-5

Найдите ошибку в приведенном фрагменте:

Упражнение 1-6

Перечислите все возможные варианты, которые выдаст приведенное выражение в зависимости от порядка вычислений:

? n=1
? printf(."%d %d\n", n++, n++);

Попробуйте пропустить это выражение через разные компиляторы и посмотрите, что получается на практике.

 

Назад Начало Вперед