Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 [ 26 ] 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76

ограничения компьютерной системы. Если вы производите умножение перед делением, то при этом рискуете выйти за пределы разрядности при сохранении результата; если вы сначала делите, то рискуете случайно округлить результат до нуля; и так далее. Численным методам для компьютеров посвящены целые книги, и вам нужно прочитать хотя бы одну из них, если в ваших программах много математики.

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

long x;

x &= 0xffff; очистить все, кроме младших 16-ти бит 32-битного типа long.

Компьютер имел 16-битовый тип int и 32-битовый тип long. Константа 0xffff типа int с арифметическим значением -1 . Компилятор Си при трансляции &= обнаруживал разные типы операндов и поэтому преобразовывал int в long. -1 в типе long представляется как 0xffffffff, поэтому логическая операция И не имела эффекта. Это как раз тот способ, которым и должен работать данный язык программирования. Я просто об этом не подумал.

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

x&=(long)0xffff; Единственным методом решения этой проблемы является:

x &= 0xffffUL;

или равноценный ему.

66.1. Рассчитывайте на невозможное

Оператор switch всегда должен иметь предложение с ключевым словом default для ситуации по умолчанию, особенно если эта ситуация не должна возникать:

f( int i) переменная i должна иметь значение 1 или 2.

switch(i)

case 1: сделать нечто(); break;

case 2: сделать нечто другое(); break; default:

fprintf(stderr, "Внутренняя ошибка в f(): неверное



значение i (%d)", i );

exit(-1);

То же самое относится к блокам if /else, работающим в манере, схожей с оператором switch.

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

f( int i) переменная i должна быть положительной

while ( -- i >= 0 )

сделать нечто();

Конструкция while(--i) менее надежна, так как она дает ужасный сбой в случае, если i сначала равно 0 .

66.2. Всегда проверяйте коды возврата ошибки

Это должно быть очевидно, но комитет ISO/ANSI по Си++ потребовал, чтобы оператор new вызывал исключение, если он не смог выделить память, потому что было установлено, что удивительное множество ошибок во время выполнения в реальных программах вызвано тем, что люди не потрудились проверить, не возвратил ли new значение NULL.

Мне также довелось видеть множество программ, в которых люди не позаботились посмотреть, сработала ли функция fopen(), перед тем как начать пользоваться указателем FILE.

67. Избегайте явно временных переменных

Большинство переменных, используемых лишь один раз, попадают в эту категорию. Например, вместо:

int x = *p++;

f(x);

должно быть:

f( *p++ );

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



fgets( buf, sizeof (buf) / sizeof(*buf), stdin );

Я использовал sizeof () в вызове fgets(), поэтому изменения размера массива автоматически отражаются в программе. Добавление дополнительного идентификатора для хранения размера добавит излишнюю сложность.

69. Не делайте предположений о размерах

Классической проблемой является код, исходящий из того, что тип int имеет размер 32 бита. Следующий фрагмент не работает, если у вас 32-битный указатель и 1 6-битный тип int (что может быть при архитектуре Intel 80x86):

double a[1000], *p = a; ...

dist from start of array in bytes = (int)p-(int)a; Более трудно уловима такая проблема в Си (но не в Си++):

f( Coefficient of lift * (0.5 * RHO * square(v)) ); передать функции f() образующуюся подъемную силу

double lift = Coefficient of lift * (0.5 * RHO * square(v));

f( lift );

Это правило не запрещает ни одно из подобных применений, а является, скорее, вырожденным случаем того, что упомянуто мной вначале.

68. Не нужно магических чисел

В основном тексте вашей программы не должно быть чисел в явном виде. Используйте перечислитель или константу для того, чтобы дать числу символическое имя. (Я уже объяснял, почему для этого не очень хорошо применять #define). Тут есть два преимущества:

• Символическое имя делает величину самодокументируемой, устраняя необходимость в комментарии.

• Если число используется более чем в одном месте, то менять нужно лишь одно место - определение константы.

Я иногда делаю исключение из этого правила для локальных переменных. Например, в следующем фрагменте используется магическое число (1 28):

char buf[128]



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 [ 26 ] 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76