Приветствую Вас ГостьВторник, 14.05.2024, 08:36

Каталог статей


Стив Макконнелл "Совершенный код". Использование типов данных

Числа в общем

Избегайте «магических чисел» Магические числа — это обычные числа, такие как 100 или 47524, которые появляются в программе без объяснений. Если вы программируете на языке, поддерживающем именованные константы, используйте их вместо магических чисел. Если вы не можете применить именованные константы, применяйте глобальные переменные, когда это возможно.

for i = 0 to 99 do ...
можно предположить, что 99 определяет максимальное число элементов. А вот выражение:
for i = 0 to MAX_ENTRIES-1 do ...
не оставляет на этот счет сомнений.

Ошибки деления на ноль Каждый раз, когда вы пользуетесь символом деления (/ в большинстве языков), думайте о том, может ли в знаменателе оказаться 0. Если такая возможность существует, напишите код, предупреждающий появление ошибки деления на 0.

Избегайте сравнений разных типов Если x — число с плавающей запятой, а i — целое, проверка: 

if ( i = x ) then ...

 почти гарантированно не сработает.

Целые числа

Проверяйте целочисленность операций деления Когда используются целые числа, выражение 7/10 не равно 0,7. Оно обычно равно 0 или минус бесконечности, или ближайшему целому, или…

В реальном мире 10 * (7/10) = (10*7) / 10 = 7. Но не в мире целочисленной арифметики. 10 * (7/10) равно 0, потому что целочисленное деление (7/10) равно 0. Простейший способ исправить положение — преобразовать его так, чтобы операции деления выполнялись последними: (10*7) / 10.

Проверяйте переполнение целых чисел При выполнении умножения или сложения необходимо принимать во внимание наибольшие возможные значения целых чисел. Для целого числа без знака это обычно 232 –1, а иногда и 216 –1, или 65 535. Проблема возникает, когда вы умножаете два числа, в результате чего получается число большее, чем максимально возможное целое.

Интервалы значений некоторых целых типов
Целый тип             Интервал
8-битный со знаком    От –128 до 127
8-битный без знака    От 0 до 255
16-битный со знаком   От –32 768 до 32 767
16-битный без знака   От 0 до 65 535
32-битный со знаком   От –2 147 483,648 до 2 147 483 647
32-битный без знака   От 0 до 4 294 967 295
64-битный со знаком   От –9 223 372 036 854 775 808 до 9 223 372 036 854 775 807
64-битный без знака   От 0 до 18 446 744 073 709 551 615

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

int termA = 1000000;
int termB = 1000000;
int product = termA * termB / 1000000;
System.out.println( “( “ + termA + “ * “ + termB + “ ) / 1000000 = “ + product );

Вы можете подумать, что значение Product вычисляется как (100 000*100 000) / 100 000 и поэтому равно 100 000. Но программе приходится вычислять промежуточное значение 100 000*100 000 до того, как будет выполнено деление на 100 000, а это значит, что нужно хранить такое большое число, как 1 000 000 000 000. Угадайте, что получится? Вот результат:
( 1000000 * 1000000 ) / 1000000 = -727

Числа с плавающей запятой

Главная особенность применения чисел с плавающей запятой в том, что многие дробные десятичные числа не могут быть точно представлены с помощью нулей и единиц, используемых в  цифровом компьютере. В бесконечных десятичных дробях, таких как 1/3 или 1/7, обычно сохраняется только 7 или 15 цифр после запятой.

Избегайте сложения и вычитания слишком разных по размеру чисел. Для 32#битной переменной с плавающей запятой сумма 1 000 000,00 + 0,1, вероятно, будет равна 1 000 000,00, так как в 32 битах недостаточно значимых цифр, чтобы охватить интервал между 1 000 000 и 0,1. Аналогично 5 000 000,02 – 5 000 000,01, вероятно, равно 0,0.

Избегайте сравнений на равенство Числа с плавающей запятой, которые должны быть равны, на самом деле равны не всегда. Главная проблема в том, что два разных способа получить одно и то же число не всегда приводят к одинаковому результату. Так, если 10 раз сложить 0,1, то 1,0 получается только в редких случаях. Следующий пример содержит две переменных (nominal и sum), которые должны быть равны, но это не так.

// Переменная nominal — 64-битное вещественное число.
double nominal = 1.0;
double sum = 0.0;
for ( int i = 0; i < 10; i++ ) {
   //sum вычисляется как 10*0,1. Она должна быть равна 1,0.
   sum += 0.1;
}
Здесь неправильное сравнение.
if ( nominal == sum ) {
   System.out.println( “Numbers are the same.” );
}
else {
   System.out.println( “Numbers are different.” );
}

Как вы, наверное, догадались, программа выводит: Numbers are different.
Вывод каждого значения sum в цикле for выглядит так:
0.1
0.2
0.30000000000000004
0.4
0.5
0.6
0.7
0.7999999999999999
0.8999999999999999
0.9999999999999999

Таким образом, хорошей идеей будет найти альтернативу операции сравнения на равенство для чисел с плавающей запятой. Один эффективный подход состоит в том, чтобы определить приемлемый интервал точности, а затем использовать логические функции для выяснения, достаточно ли близки сравниваемые значения. Для этого обычно пишется функция Equals(), которая возвращает true, если значения попадают в этот интервал, и false — в противном случае.

Предупреждайте ошибки округления Проблемы с ошибками округления сходны с проблемами слишком разных по размеру чисел. У них одинаковые причины и похожие методики решения. Кроме того, далее перечислены способы решения проблем округления.
- Измените тип переменной на тип с большей точностью. Если вы используете числа с одинарной точностью, замените их числами с двойной точностью и т. д.
- Используйте двоично-десятичные переменные (binary coded decimal, BCD). BCD-числа обычно работают медленнее и требуют больше памяти для хранения, но предотвращают множество ошибок округления. Это особенно важно, если используемые переменные представляют собой доллары и центы или другие величины, которые должны точно балансироваться.
- Измените тип с плавающей запятой на целые значения. Это такая самодельная замена BCD-переменных. Возможно, вам придется использовать 64-битные целые, чтобы получить нужную точность. Этот способ предполагает, что вы сами будете отслеживать дробные части чисел. Допустим, изначально вы вели учет денежных сумм, применяя числа с плавающей запятой, при этом центы указывались как дробная часть. Это обычный способ обработки долларов и центов. Когда вы переключаетесь на целые числа, вам нужно вести учет центов с помощью целых, а долларов — с помощью чисел, кратных 100 центам. Иначе говоря, вы умножаете сумму в долларах на 100 и храните центы в этой переменной в интервале от 0 до 99. Такое решение может показаться абсурдным, но оно эффективно и с точки зрения скорости, и с точки зрения точности. Вы можете упростить эти манипуляции, создав класс DollarsAndCents, скрывающий целое представление чисел и предоставляющий необходимые числовые операции.

Проверяйте поддержку специальных типов данных в языке и дополнительных библиотеках Некоторые языки, включая Visual Basic, предоставляют такие типы данных, как Currency, предназначенные для данных, чувствительных к ошибкам округления. Если ваш язык содержит встроенный тип данных, предоставляющий такую функциональность, используйте его!

Символы и строки

Избегайте магических символов и строк Магические символы — это литеральные символы (например, 'А'), а магические строки — это литеральные строки (например, ”Gigamatic Accounting Program”), которые разбросаны по всей программе. Если ваш язык программирования поддерживает применение именованных констант, то лучше задействуйте их. В противном случае  используйте глобальные переменные.

  • Для таких часто встречающихся строк, как имя программы, названия команд, заголовки отчетов и т. п., вам может понадобиться поменять содержимое. Например, ”Gigamatic Accounting Program” в более поздней версии может измениться на ”New and Improved! Gigamatic Accounting Program”.
  • Все большее значение приобретают международные рынки, и строки, сгруппированные в файле ресурсов переводить гораздо легче, чем раскиданные по всей программе.
  • Строковые литералы обычно занимают много места. Они используются для меню, сообщений, экранов помощи, форм ввода и т. д. Если их слишком много, они выходят из-под контроля и вызывают проблемы с памятью. Во многих системах объем памяти, занимаемый строками, не является причиной для беспокойства. Однако при программировании встроенных систем и других приложений, в которых каждый байт на счету, проблему хранения строк легче решить, если эти строки относительно независимы от кода. 
  • Символьные и строковые литералы могут быть загадочными. Комментарии или именованные константы проясняют ваши намерения.

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

Если вам известно, что нужно поддерживать только один алфавит, рассмотрите вариант использования набора символов ISO 8859 Для приложений, использующих только один алфавит (например, английский), которым не надо поддерживать несколько языков или какой-либо идеографический язык (такой как письменный китайский), расширенный ASCII#набор стандарта ISO 8859 — хорошая альтернатива символам Unicode.
Если вам необходимо поддерживать несколько языков, используйте Unicode Unicode обеспечивает более полную поддержку международных наборов символов, чем ISO 8859 или другие стандарты.

Логические переменные

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

if ( ( elementIndex < 0 ) || ( MAX_ELEMENTS < elementIndex ) ||
   ( elementIndex == lastElementIndex )
   ) {
   ...
   }

можно написать

finished = ( ( elementIndex < 0 ) || ( MAX_ELEMENTS < elementIndex ) );
repeatedEntry = ( elementIndex == lastElementIndex );
if ( finished || repeatedEntry ) {
   ...
}

Используйте логические переменные для упрощения сложных условий

Например вместо 

If ( ( document.AtEndOfStream() ) And ( Not inputError ) ) And _
   ( ( MIN_LINES <= lineCount ) And ( lineCount <= MAX_LINES ) ) And _
   ( Not ErrorProcessing() ) Then
   ‘ делаем что-то
   ...
End If

лучше написать

allDataRead = ( document.AtEndOfStream() ) And ( Not inputError )
legalLineCount = ( MIN_LINES <= lineCount ) And ( lineCount <= MAX_LINES )
// Вот упрощенная проверка.
If ( allDataRead ) And ( legalLineCount ) And ( Not ErrorProcessing() ) Then
   ‘ делаем что-то
   ...
End If

Перечислимые типы

Примеры перечислимых типов (Visual Basic)
Public Enum Color
   Color_Red
   Color_Green
   Color_Blue
End Enum

Используйте перечислимые типы для читабельности Вместо выражений
вроде:
if chosenColor = 1
вы можете написать более читабельную фразу, например:
if chosenColor = Color_Red

Используйте перечислимые типы как альтернативу логическим переменным

Настройте первый и последний элемент перечислимого типа для использования в качестве границ циклов

например:

Public Enum Country
   Country_First = 0
   Country_China = 0
   Country_England = 1
   Country_France = 2
   Country_Germany = 3
   Country_India = 4
   Country_Japan = 5
   Country_Usa = 6
   Country_Last = 6
End Enum

Теперь элементы Country_First и Country_Last могут служить как границы цикла.

Зарезервируйте первый элемент перечислимого типа как недопустимый. Большинство компиляторов присваивает первому элементу перечисления значение 0. Объявление некорректным элемента, приравненного 0, поможет выявить неправильно проинициализированные переменные, так как они вероятнее всего будут равны 0, чем любому другому неправильному значению.

Public Enum Country
   Country_InvalidFirst = 0
   Country_First = 1

   ...

Именованные константы

Избегайте литеральных значений, даже «безопасных» Как вы думаете, что в следующем цикле означает число 12?

For i = 1 To 12
   profit( i ) = revenue( i ) – expense( i )
Next

Пример более понятного кода (Visual Basic)
For month = Month_January To Month_December
   profit( month ) = revenue( month ) – expense( month )
Next

Последовательно используйте именованные константы Опасно использовать для представления одной сущности именованные константы в одном месте и литералы в другом.

Массивы

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

В многомерном массиве убедитесь, что его индексы используются в правильном порядке Очень легко написать Array[ i ][ j ], имея в виду Array[ j ][ i ], так что не жалейте времени для проверки правильного порядка индексов. Попробуйте использовать более значимые имена, чем i и j, когда их назначение не вполне очевидно.

Остерегайтесь пересечения индексов При использовании вложенных циклов легко написать Array[ j ], имея в виду Array[ i ]. Перемена мест индексов называется «пересечением индексов» (index cross-talk). Проверьте эту возможность. Опять же, используйте более значимые имена индексов, чем i и j, чтобы ошибки пересечения изначально сложнее было совершить.

Создание собственных типов данных (псевдонимы)

Основные принципы создания собственных типов

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

Избегайте предопределенных типов Если есть хоть малейшая возможность, что тип может измениться, избегайте применения предопределенных типов везде, кроме определений typedef или type.

Не переопределяйте предопределенные типы Изменение определения стандартного типа может вызвать путаницу. Например, если в вашем языке есть предопределенный тип Integer, не  создавайте свой тип с именем Integer.

Рассмотрите вопрос создания класса вместо использования typedef

Структуры

Используйте структуры для прояснения взаимоотношений между данными Структуры объединяют группы взаимосвязанных элементов.

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

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

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

Глобальные данные

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

Используйте глобальные данные только как последнее средство.

Причины для использования глобальных данных:

  • Хранение глобальных значений
  • Эмуляция именованных констант
  • Эмуляция перечислимых типов
  • Оптимизация обращений к часто используемым данным. Иногда переменная так часто вызывается, что упоминается в списке параметров каждого метода. Вместо того чтобы включать ее в каждый список параметров, вы можете сделать ее глобальной.
  • Исключение бродячих данных. Иногда вы передаете данные методу или классу только для того, чтобы передать в другой метод или класс. Например, у вас может быть объект-обработчик ошибок, применяемый в каждом методе. Если метод в середине цепочки вызовов не использует этот объект, он называется «бродячим» (tramp data). Применение  глобальных переменных помогает исключить бродячие данные.

 

Категория: Стив Макконнелл "Совершенный код" | Добавил: leshic (27.10.2021)
Просмотров: 383 | Рейтинг: 0.0/0
Всего комментариев: 0
Имя *:
Email *:
Код *:
Вход на сайт
Поиск
Категории раздела
Стив Макконнелл "Совершенный код" [20]
Стив Макконнелл "Совершенный код"
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0