Главная страница

Совершенный код. Совершенный код. Мастер-класс. Стив Макконнелл. Руководство по стилю программирования и конструированию по


Скачать 5.88 Mb.
НазваниеРуководство по стилю программирования и конструированию по
АнкорСовершенный код
Дата31.03.2023
Размер5.88 Mb.
Формат файлаpdf
Имя файлаСовершенный код. Мастер-класс. Стив Макконнелл.pdf
ТипРуководство
#1028502
страница40 из 106
1   ...   36   37   38   39   40   41   42   43   ...   106
ГЛАВА 12 Основные типы данных
301
Пример еще более понятного кода (Visual Basic)
For month = 1 To NUM_MONTHS_IN_YEAR
profit( month ) = revenue( month ) – expense( month )
Next
Этот пример выглядит весьма неплохо, но мы можем сделать еще один шаг впе#
ред, применив перечислимый тип:
Пример очень понятного кода (Visual Basic)
For month = Month_January To Month_December profit( month ) = revenue( month ) – expense( month )
Next
В последнем примере не может возникнуть никаких сомнений относительно назначения цикла. Даже если вы считаете, что литеральное значение безопасно,
используйте вместо него именованную константу. Фанатично искорените лите#
ралы из вашего кода. С помощью текстового редактора выполните поиск
2, 3, 4, 5,
6, 7, 8 и 9, чтобы убедиться, что вы не используете их случайно.
Имитируйте именованные константы с помощью
переменных или классов правильной области види'
мости Если ваш язык не поддерживает именованные кон#
станты, их можно создать. Подход, аналогичный приведен#
ному выше Java#примеру, имитирующему перечислимые типы, позволяет получить преимущества использования именованных констант. Старайтесь применять обычные правила области види#
мости: отдавайте предпочтение локальной, классовой или глобальной области видимости именно в таком порядке.
Последовательно используйте именованные константы Опасно исполь#
зовать для представления одной сущности именованные константы в одном мес#
те и литералы в другом. Некоторые приемы программирования напрашиваются на ошибки, а этот просто доставляет вам ошибки на дом. Если значение имено#
ванной константы нужно изменить, вы сделаете это и подумаете, что выполнили все необходимые изменения. Вы не обратите внимания на жестко закодирован#
ные литералы, и ваша программа будет демонстрировать таинственные дефекты.
Их устранение может потребовать так много усилий, что захочется схватить те#
лефонную трубку и молить о помощи.
12.8. Массивы
Массивы — простейшие и наиболее часто используемые типы структурирован#
ных данных. В некоторых языках это единственный вид структурированных дан#
ных. Массивы состоят из группы элементов одинакового типа, доступ к которым осуществляется напрямую по индексу.
Убедитесь, что все значения индексов массива не выходят за его
границы Все проблемы с массивами так или иначе связаны с тем, что доступ к их элементам может осуществляться произвольно. Наиболее часто
Перекрестная ссылка Об ими- тации перечислимых типов см.
подраздел «Если ваш язык не поддерживает перечислимые типы» раздела 12.6.

302
ЧАСТЬ III Переменные возникающая проблема объясняется попыткой доступа к элементу по индексу, вы#
ходящему за пределы массива. В некоторых языках при этом генерируется ошиб#
ка, а в других — получаются причудливые и неожиданные результаты.
Обдумайте применение контейнеров вместо массивов или рассматривай'
те массивы как последовательные структуры Некоторые именитые в ком#
пьютерной науке люди предлагали запретить произвольный доступ к массиву,
заменив его последовательным (Mills and Linger, 1986). Аргументтруют они это тем,
что произвольный доступ к массиву похож на случайные операторы
goto в про#
грамме: их применение приводит к неаккуратному, подверженному ошибкам коду,
в корректности которого сложно быть уверенным. Поэтому вместо массивов пред#
лагается использовать множества, стеки и очереди, доступ к элементам которых выполняется последовательно.
Проведя небольшой эксперимент, Миллз (Mills) и Линджер (Linger) вы#
яснили, что разработанный таким образом проект потребовал исполь#
зования меньшего числа переменных и меньшего числа ссылок на эти переменные. То есть проект был относительно эффективнее, что привело к со#
зданию более надежного ПО.
Рассмотрите вопрос использования контейнерных классов с последовательным доступом — наборов, стеков, очередей и т. п. — как альтернативу прежде, чем выбрать массив.
Проверяйте конечные точки массивов Как бывает по#
лезно продумать применение конечных точек в операторе цикла, так и вы сможете обнаружить немало ошибок, прове#
рив крайние элементы массивов. Задайтесь вопросом, пра#
вильно ли выполняется доступ к первому элементу массива или случайно используется элемент перед ним либо после него. А что с последним элементом? Нет ли в коде ошибки потери единицы? И, наконец, спросите себя, пра#
вильно ли код обращается к элементам в середине массива.
В многомерном массиве убедитесь, что его индексы используются в пра'
вильном порядке Очень легко написать Array[ i ][ j ], имея в виду Array[ j ][ i ],
так что не жалейте времени для проверки правильного порядка индексов. Попро#
буйте использовать более значимые имена, чем
i и j, когда их назначение не вполне очевидно.
Остерегайтесь пересечения индексов При использовании вложенных цик#
лов легко написать
Array[ j ], имея в виду Array[ i ]. Перемена мест индексов назы#
вается «пересечением индексов» (index cross#talk). Проверьте эту возможность.
Опять же, используйте более значимые имена индексов, чем
i и j, чтобы ошибки пересечения изначально сложнее было совершить.
В языке C для работы с массивами используйте макрос ARRAY_LENGTH()
Вы можете добавить гибкости вашей работе с массивами, определив макрос
ARRAY_
LENGTH():
Пример определения макроса ARRAY_LENGTH() на языке C
#define ARRAY_LENGTH( x ) (sizeof(x)/sizeof(x[0]))
Перекрестная ссылка Вопросы применения массивов и циклов имеют много общего. Подроб- нее о циклах см. главу 16.

ГЛАВА 12 Основные типы данных
303
При выполнении операций над массивами для указания верхней границы исполь#
зуйте макрос
ARRAY_LENGTH() вместо именованной константы. Например:
Пример использования макроса ARRAY_LENGTH()
для операций с массивами на языке C
ConsistencyRatios[] =
{ 0.0, 0.0, 0.58, 0.90, 1.12,
1.24, 1.32, 1.41, 1.45, 1.49,
1.51, 1.48, 1.56, 1.57, 1.59 };
Вот здесь используется макрос.
for ( ratioIdx = 0; ratioIdx < ARRAY_LENGTH( ConsistencyRatios ); ratioIdx++ );
Этот способ особенно полезен для массивов неопределенного размера, как в этом примере. Если вы добавляете или удаляете элементы, вам не надо помнить об изменении именованной константы, определяющей размер массива. Разумеется,
эта технология работает и с массивами заданного размера, но, используя этот подход, вам не всегда надо будет создавать дополнительные именованные кон#
станты для объявления массивов.
12.9. Создание собственных
типов данных (псевдонимы)
Типы данных, определяемые программистом, — одна из наиболее мощ#
ных возможностей, позволяющих наиболее четко обозначить ваше понимание программы. Они защищают программу от непредвиденных изменений и упрощают ее прочтение, и все это — без необходимости проекти#
ровать, разрабатывать или тестировать новые классы. Если вы программируете на
C, C++ или других языках, поддерживающих такие типы, задействуйте это преиму#
щество!
Чтобы оценить возможности создания типов, представьте, что вы пишете программу для преобразования координат из сис#
темы
x, y, z в широту, долготу и высоту. Вам кажется, что могут потребоваться числа с плавающей запятой двойной точнос#
ти, но пока вы абсолютно в этом не уверены, предпочитаете писать программу, используя числа с одинарной точностью. Вы можете создать но#
вый тип данных специально для координат, применив оператор
typedef в C или C++
или его эквивалент в другом языке. Вот как вы определите такой тип в C++:
Пример создания типа (C++)
typedef float Coordinate; // для координатных переменных
Это определение объявляет новый тип
Coordinate, функционально идентичный типу
float. Чтобы задействовать этот тип, вы просто объявляете с ним переменные точ#
но так же, как и с любым предопределенным типом вроде
float. Пример:
Перекрестная ссылка Во многих случаях лучше создавать класс,
чем простой тип данных. Под- робнее см. главу 6.
>

304
ЧАСТЬ III Переменные
Пример использования созданного типа (C++)
Routine1( ... ) {
Coordinate latitude; // широта в градусах
Coordinate longitude; // долгота в градусах
Coordinate elevation; // высота в метрах от центра Земли
}
Routine2( ... ) {
Coordinate x; // координата x в метрах
Coordinate y; // координата y в метрах
Coordinate z; // координата z в метрах
}
Здесь все переменные
latitude, longitude, elevation, x, y и z объявлены с типом
Coordinate.
Теперь допустим, что программа изменилась и вы выяснили, что все#таки нужны переменные с двойной точностью. Поскольку вы создали тип специально для координатных данных, все, что вам нужно изменить, — это определение типа.
И сделать это вам необходимо только в одном месте — в выражении
typedef. Вот как выглядит новое определение типа:
Пример измененного определения типа (C++)
Первоначальный тип float заменен на double.
typedef double Coordinate; // для координатных переменных
Вот еще один пример — теперь на языке Pascal. Представьте, что вы разрабатыва#
ете систему расчета заработной платы, в которой длина имен работников не пре#
вышает 30 символов. Пользователи сказали вам, что
ни у кого нет имени длинней
30 символов. Закодируете ли вы число
30 по всей программе? Если да, то вы дове#
ряете вашим пользователям гораздо больше, чем я — своим. Лучший подход со#
стоит в определении типа для имен работников:
Пример создания типа для имен работников (Pascal)
Type employeeName = array[ 1..30 ] of char;
Когда речь идет о строке или массиве, обычно разумно определить именованную константу, содержащую длину строки или массива, а затем задействовать ее в определении типа. Вы найдете в своей программе много мест, в которых стоит использовать константу, и это — первое из них. Вот как это выглядит:
Пример лучшего создания типа (Pascal)
Const
>

ГЛАВА 12 Основные типы данных
305
Вот объявление именованной константы.
NAME_LENGTH = 30;
Type
Здесь эта именованная константа используется.
employeeName = array[ 1..NAME_LENGTH ] of char;
Еще более усовершенствованный пример может комбинировать идею создания собственных типов с технологией сокрытия информации. Порой сведения, ко#
торые вы хотите скрыть, и есть информация о типе данных.
Пример с координатами на C++ частично удовлетворяет принципу сокрытия ин#
формации. Если вы всегда будете использовать
Coordinate вместо float или double,
вы эффективно спрячете исходный тип данных. В C++ это практически все воз#
можное сокрытие информации, которое язык позволяет сделать разработчику. Все последующие пользователи вашего кода должны соблюдать дисциплину и не смот#
реть на определение
Coordinate. C++ предоставляет скорее фигуральную, а не бук#
вальную возможность сокрытия информации.
Другие языки, например Ada, делают шаг вперед и поддерживают буквальное со#
крытие информации. Вот как фрагмент кода для типа
Coordinate будет выглядеть в модуле Ada, где он был объявлен:
Пример сокрытия деталей реализации типа внутри модуля (Ada)
package Transformation is
Это выражение объявляет Coordinate скрытым в данном модуле.
type Coordinate is private;
Вот как тип
Coordinate будет выглядеть в другом модуле, где он используется:
Пример использования типа из другого модуля (Ada)
with Transformation;
procedure Routine1(...) ...
latitude: Coordinate;
longitude: Coordinate;
begin
 операторы, использующие широту и долготу end Routine1;
Заметьте: тип
Coordinate объявлен в модуле как private. Это значит, что единственная часть программы, которая знает определение типа
Coordinate, — это закрытая часть модуля
Transformation. При групповой разработке проекта вы можете распрост#
ранить только спецификацию модуля, что затруднит программисту, работающе#
му с другим модулем, просмотр исходного типа
Coordinate. Информация будет
>
>
>

306
ЧАСТЬ III Переменные буквально спрятана. Такие языки, как C++, которые требуют распространять определение типа
Coordinate в заголовочном файле, подрывают идею реального сокрытия информации.
Следующие примеры иллюстрируют несколько причин для создания собственных типов.

Упростить модификацию кода Сделать новый тип легко, а это дает вам большую гибкость.

Избежать излишнего распространения информации Явная типизация распространяет сведения о типе данных по всей программе вместо их цент#
рализации в одном месте. Это пример принципа сокрытия информации с це#
лью достижения централизации, (см. раздел 6.2).

Увеличить надежность В Ada вы можете объявлять типы как
type Age is range
0..99. После этого компилятор генерирует проверки времени выполнения, чтобы удостовериться, что значение любой переменной типа
Age всегда попадает в диапазон
0..99.

Замаскировать недостатки языка Если ваш язык не содержит необходи#
мого предопределенного типа, вы можете создать его сами. Например, в C нет булева или логического типа. Этот недостаток легко исправить, создав тип:
typedef int Boolean;
Почему приведены примеры создания типов
на языках Pascal и Ada?
Языки Pascal и Ada сейчас подобны динозаврам, а языки, заменившие их, в основном гораздо практичнее. Однако в области определения простых типов мне кажется,
что C++, Java и Visual Basic представляют случай трех шагов вперед и одного шага назад. В Ada такое объявление, как:
currentTemperature: INTEGER range 0..212;
содержит важную семантическую информацию, которую объявление:
int temperature;
не содержит. Если посмотреть глубже, то определение:
type Temperature is range 0..212;
currentTemperature: Temperature;
позволяет компилятору удостовериться, что
currentTemperature присваивается только другим переменным типа
Temperature, и такая дополнительная прослойка безопасности требует минимального кодирования.
Естественно, программист может создать класс
Temperature, чтобы реализовать те же семантические правила, автоматически предоставляемые в Ada, но между со#
зданием простого типа данных в одну строку и созданием класса дистанция ог#
ромного размера. Зачастую программист будет использовать простой тип данных,
но не станет делать дополнительных усилий для создания класса.

ГЛАВА 12 Основные типы данных
307
Основные принципы создания собственных типов
Имейте в виду следующие принципы, когда решите создавать собственный тип.
Создавайте типы с именами, отражающими их функ'
циональность Избегайте имен типов, которые ссылаются на данные, лежащие в основе этих типов. Используйте имена,
которые отражают те элементы реальной задачи, которые этот тип представляет. Определения из предыдущих приме#
ров — понятно названные типы для координат и имен ра#
ботников — это реальные сущности. Точно так же вы можете создавать типы для валюты, кодов платежей, возрастов и т. д., а именно для аспектов действительно существующих задач.
Будьте осторожны, создавая имена типов, ссылающиеся на предопределенные типы.
Такие имена, как
BigInteger или LongString, описывают компьютерные данные, а не конкретную задачу. Большое преимущество создания собственных типов данных состоит в том, что добавляется слой, изолирующий программу от языка разработки.
Имена типов, ссылающиеся на типы языка, лежащие в их основе, нарушают эту изоляцию. Они не дают вам большого преимущества по сравнению с примене#
нием предопределенных типов. Проблемно#ориентированные имена, с другой стороны, облегчают процесс внесения изменений и предоставляют самодокумен#
тируемые объявления типов.
Избегайте предопределенных типов Если есть хоть малейшая возможность,
что тип может измениться, избегайте применения предопределенных типов вез#
де, кроме определений
typedef или type. Легко создать новые функционально#ори#
ентированные типы — менять же данные в программе, использующей жестко за#
кодированные типы, гораздо сложней. Более того, функционально#ориентирован#
ные типы частично документируют объявленные с ними переменные. Объявле#
ние
Coordinate x сообщит вам об x гораздо больше, чем объявление float x. Исполь#
зуйте собственные типы везде, где только можно.
Не переопределяйте предопределенные типы Изменение определения стан#
дартного типа может вызвать путаницу. Например, если в вашем языке есть пре#
допределенный тип
Integer, не создавайте свой тип с именем Integer. Читающие ваш код могут забыть, что вы его переопределили, и будут считать, что видят тот же
Integer, который привыкли видеть.
Определите подстановки для переносимости В отличие от совета не изме#
нять определение стандартных типов вы можете создать для этих типов подста#
новки, так что на разных платформах переменные будут представлены одними и теми же сущностями. Так, вы можете определить тип
INT32 и использовать его вместо
int или тип LONG64 вместо long. Изначально единственной разницей между двумя типами будет применение заглавных букв. Но при переходе на другую плат#
форму вы сможете переопределить варианты с большими буквами так, чтобы они совпадали с типами для данных аппаратных средств.
Не создавайте типы, которые легко перепутать с предопределенными. Существу#
ет возможность определить
INT вместо INT32, но лучше сделать явное различие между типами, созданными вами, и типами, предоставленными языком програм#
мирования.
Перекрестная ссылка В каждом случае следует решать, не луч- ше ли использовать класс, а не простой тип данных. Подробнее см. главу 6.

308
ЧАСТЬ III Переменные
Рассмотрите вопрос создания класса вместо использования typedef Прос#
тые операторы
typedef позволяют проделать большой путь в сторону сокрытия ин#
формации об исходном типе переменной. Однако иногда вам может потребоваться дополнительная гибкость и управляемость, которой позволяют добиться классы.
Подробнее см. главу 6.
Контрольный список: основные данные
Числа в общем
 Не содержит ли код магические числа?
 Предупреждаются ли в коде ошибки деления на ноль?
 Очевидны ли преобразования типов?
 Если переменные двух разных типов используются в од- ном выражении, будет ли оно вычислено так, как вы это предполагаете?
 Не происходит ли сравнение переменных разных типов?
 Компилируется ли программа без предупреждений ком- пилятора?
Целые числа
 Работают ли выражения, содержащие целочисленное деление так, как это предполагалось?
 Предупреждаются ли в целочисленных выражениях проблемы целочислен- ного переполнения?
Числа с плавающей запятой
 Не содержит ли код операции сложения и вычитания слишком разных по величине чисел?
 Предупреждаются ли в коде ошибки округления?
 Не выполняется сравнение на равенство чисел с плавающей запятой?
Символы и строки
 Не содержит ли код магических символов и строк?
 Свободны ли операции со строками от ошибки потери единицы?
 Различаются ли в коде на C строковые указатели и массивы символов?
 Соблюдается ли в коде на C соглашение об объявлении строк с длиной
CONSTANT+1?
 Используются ли в C массивы символов вместо указателей там, где это допустимо?
 Инициализируются ли в C строки с помощью NULL во избежание бесконеч- ных строк?
 Используются ли в коде на C strncpy() вместо strcpy()? А strncat() и strncmp()?
Логические переменные
 Используются ли в программе дополнительные логические переменные для документирования проверок условия?
 Используются ли в программе дополнительные логические переменные для упрощения проверок условия?
http://cc2e.com/1206
Перекрестная ссылка Список вопросов, затрагивающих дан- ные вообще, без подразделения на конкретные типы, см. в кон- трольном списке главы 10. Спи- сок вопросов по вариантам именования см. в контрольном списке главы 11.

1   ...   36   37   38   39   40   41   42   43   ...   106


написать администратору сайта