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

Руководство по стилю программирования и конструированию по


Скачать 7.6 Mb.
НазваниеРуководство по стилю программирования и конструированию по
Дата18.05.2023
Размер7.6 Mb.
Формат файлаpdf
Имя файлаCode_Complete.pdf
ТипРуководство
#1139697
страница38 из 104
1   ...   34   35   36   37   38   39   40   41   ...   104
ГЛАВА 12 Основные типы данных
293
Пример логического условия, чье назначение понятно (Java)
finished = ( ( elementIndex < 0 ) || ( MAX_ELEMENTS < elementIndex ) );
repeatedEntry = ( elementIndex == lastElementIndex );
if ( finished || repeatedEntry ) {
}
Используйте логические переменные для упрощения сложных условий
Чтобы правильно закодировать сложное условие, часто приходится делать несколько попыток. Когда через некоторое время нужно модифицировать это условие, быва- ет сложно разобраться, что же оно проверяет. Логические переменные могут уп- ростить проверку. В предыдущем примере программа на самом деле проверяет два условия: завершено ли выполнение метода и выполняется ли этот метод повторно.
Создав логические переменные
finished и repeatedEntry, вы упрощаете if-проверку:
теперь ее легче читать, легче изменять, и она меньше подвержена ошибкам.
Вот другой пример сложного условия:
Пример сложного условия (Visual Basic)
If ( ( document.AtEndOfStream() ) And ( Not inputError ) ) And _
( ( MIN_LINES <= lineCount ) And ( lineCount <= MAX_LINES ) ) And _
( Not ErrorProcessing() ) Then
‘ делаем что-то
End If
Условие в примере достаточно запутанное, но все же часто встречающееся. Оно налагает на читателя тяжелую умственную нагрузку. Могу предположить, что вы даже не попытаетесь разобраться в этой
if-проверке, а посмотрите и скажете: «Раз- берусь с этим позже, если и впрямь понадобится». Обратите внимание на эту мысль,
ведь это абсолютно то же самое, что сделают другие люди, читая ваш код, содер- жащий подобные условия.
А вот как переписать этот код, используя логические переменные, добавленные для упрощения условия:
Пример упрощенного условия (Visual Basic)
allDataRead = ( document.AtEndOfStream() ) And ( Not inputError )
legalLineCount = ( MIN_LINES <= lineCount ) And ( lineCount <= MAX_LINES )
Вот упрощенная проверка.
If ( allDataRead ) And ( legalLineCount ) And ( Not ErrorProcessing() ) Then
‘ делаем что-то
End If
Вторая версия проще. Думаю, вы легко прочитаете логическое выражение в
if- проверке.
>

294
ЧАСТЬ III Переменные
Создайте свой логический тип в случае необходимости Некоторые язы- ки, такие как C++, Java и Visual Basic, имеют предопределенный логический тип,
другие — скажем, C — не имеют. В языках, подобных C, вы можете определить свой логический тип. В C это можно сделать так:
Пример определения типа BOOLEAN
с помощью обычного typedef на языке C
typedef int BOOLEAN;
Или можно это сделать, определив дополнительно значения
true и false:
Пример определения типа Boolean с помощью Enum на языке C
enum Boolean {
True=1,
False=(!True)
};
Объявление переменных как
BOOLEAN, а не int делает их последующее использо- вание более очевидным, а вашу программу — самодокументируемой.
12.6. Перечислимые типы
Перечислимым называется тип данных, который позволяет описать на естествен- ном языке каждый элемент класса или объекта. Перечислимые типы реализова- ны в C++ и Visual Basic и обычно используются, когда вы знаете все возможные значения переменной и хотите выразить их словами. Вот примеры перечисли- мых типов на Visual Basic:
Примеры перечислимых типов (Visual Basic)
Public Enum Color
Color_Red
Color_Green
Color_Blue
End Enum
Public Enum Country
Country_China
Country_England
Country_France
Country_Germany
Country_India
Country_Japan
Country_Usa
End Enum
Public Enum Output
Output_Screen
Output_Printer

ГЛАВА 12 Основные типы данных
295
Output_File
End Enum
Перечислимые типы — мощная альтернатива устаревшим схемам, в которых вы подробно расписываете: «1 — это красный, 2 — зеленый, 3 — голубой…». В связи с этим можно предложить следующие принципы применения таких типов.
Используйте перечислимые типы для читабельности Вместо выражений вроде:
if chosenColor = 1
вы можете написать более читабельную фразу, например:
if chosenColor = Color_Red
Каждый раз, когда вы видите числовую константу, подумайте: не заменить ли ее перечислимым типом.
Перечислимые типы особенно полезны для определения параметров методов. Кто поймет, что означают параметры этой функции?
Пример вызова функции, в котором стоило бы
использовать перечислимые типы (C++)
int result = RetrievePayrollData( data, true, false, false, true );
А параметры в этом вызове функции гораздо понятнее:
Пример вызова функции, использующей
перечислимые типы для читабельности (C++)
int result = RetrievePayrollData(
data,
EmploymentStatus_CurrentEmployee,
PayrollType_Salaried,
SavingsPlan_NoDeduction,
MedicalCoverage_IncludeDependents
);
Используйте перечислимые типы для надежности В некоторых языках (осо- бенно в Ada) перечислимые типы заставляют компилятор выполнять более тщатель- ную проверку типов, чем при работе с целыми значениями и константами. При использовании именованных констант компилятору неоткуда узнать, что единствен- ные возможные значения переменной — это
Color_Red, Color_Green и Color_Blue.
Компилятор не будет возражать, встретив выражения вроде
color = Country_England
или
country = Output_Printer. Если вы используете перечислимые типы, то при объяв- лении переменной типа
Color компилятор позволит присвоить ей только значе- ния
Color_Red, Color_Green или Color_Blue.
Используйте перечислимые типы для модифицируемости Перечислимые типы позволяют вам легко модифицировать код. Обнаружив пробел в своей схе- ме «1 — это красный, 2 — зеленый, 3 — голубой», придется пройти по коду и из- менить все вхождения
1, 2, 3 и т. д. А используя перечислимые типы, вы можете

296
ЧАСТЬ III Переменные продолжить добавление элементов в список, просто поместив их в определение типа и перекомпилировав программу.
Используйте перечислимые типы как альтернативу логическим пере-
менным Очень часто логической переменной недостаточно для представления необходимых значений. Представьте, что у вас есть метод, возвращающий
True,
если он успешно выполнил свою задачу, и
False в противном случае. Позднее вы можете обнаружить, что на самом деле у вас есть два варианта
False. Первый оз- начает, что задание не выполнено, но это повлияло только на сам метод; второй —
что невыполнение задания привело к фатальной ошибке, которую надо передать в остальную часть программы. В этом случае перечислимый тип со значениями
Status_Success, Status_Warning и Status_FatalError подходит больше, чем логический тип со значениями
true и false. Эту схему очень легко расширять новыми вариан- тами успешного или неудачного выполнения.
Проверяйте некорректные значения Когда вы используете перечислимый тип в условиях
if или case, проверяйте появление недопустимых значений. В переклю- чателе
case для перехвата неправильных значений применяется оператор else:
Хороший пример проверки некорректных значений
в перечислимом типе (Visual Basic)
Select Case screenColor
Case Color_Red
Case Color_Blue
Case Color_Green
Здесь выполняется проверка неправильного значения.
Case Else
DisplayInternalError( False, “Internal Error 752: Invalid color.” )
End Select
Настройте первый и последний элемент перечислимого типа для исполь-
зования в качестве границ циклов Определение первого и последнего эле- мента в перечислении в виде
Color_First, Color_Last, Country_First, Country_Last и т. д., позволяет писать циклы, проходящие по всем элементам типа. Вы настраи- ваете свой перечислимый тип, используя явные значения:
Пример установки значений First и Last перечислимого типа (Visual Basic)
Public Enum Country
Country_First = 0
Country_China = 0
Country_England = 1
Country_France = 2
Country_Germany = 3
Country_India = 4
Country_Japan = 5
>

ГЛАВА 12 Основные типы данных
297
Country_Usa = 6
Country_Last = 6
End Enum
Теперь элементы
Country_First и Country_Last могут служить как границы цикла:
Хороший пример циклического вызова элементов перечисления (Visual Basic)
‘ Вычисляем курсы валют по отношению к долларам США.
Dim usaCurrencyConversionRate( Country_Last ) As Single
Dim iCountry As Country
For iCountry = Country_First To Country_Last usaCurrencyConversionRate( iCountry ) = ConversionRate( Country_Usa, iCountry )
Next
Зарезервируйте первый элемент перечислимого типа как недопустимый
При объявлении перечислимого типа зарезервируйте его первый элемент в ка- честве недопустимого значения. Большинство компиляторов присваивает перво- му элементу перечисления значение
0. Объявление некорректным элемента, при- равненного
0, поможет выявить неправильно проинициализированные перемен- ные, так как они вероятнее всего будут равны
0, чем любому другому неправиль- ному значению.
Вот как при этом подходе будет выглядеть перечислимый тип
Country:
Пример объявления первого элемента перечисления недопустимым (Visual Basic)
Public Enum Country
Country_InvalidFirst = 0
Country_First = 1
Country_China = 1
Country_England = 2
Country_France = 3
Country_Germany = 4
Country_India = 5
Country_Japan = 6
Country_Usa = 7
Country_Last = 7
End Enum
Точно укажите в стандартах кодирования для проекта, как должны ис-
пользоваться первый и последний элементы, и неукоснительно придержи-
вайтесь этого Использование в перечислениях элементов InvalidFirst, First и
Last может сделать объявления массивов и операторы циклов читабельнее. Но есть вероятность неразберихи в таких вопросах: с чего начинаются значимые элементы перечисления — с
0 или 1 и является ли допустимыми первый и последний эле- менты? Если используется эта технология, стандарты кодирования для проекта в целях уменьшения количества ошибок должны требовать применения элементов
InvalidFirst, First и Last для всех без исключения перечислений.

298
ЧАСТЬ III Переменные
Помните о подводных камнях в присваивании явных значений элементам
перечисления Некоторые языки позволяют присваивать конкретные значения элементам перечисления:
Пример явного присваивания значений элементам перечисления (C++)
enum Color {
Color_InvalidFirst = 0,
Color_First = 1,
Color_Red = 1,
Color_Green = 2,
Color_Blue = 4,
Color_Black = 8,
Color_Last = 8
};
Если в этом примере вы объявите переменную цикла типа
Color и попробуете пройти по всем элементам
Color, то этой переменной наряду с допустимыми зна- чениями 1, 2, 4 и 8 будут присваиваться и недопустимые — 3, 5, 6 и 7.
Если ваш язык не поддерживает
перечислимые типы
Если в вашем языке нет перечислимых типов, их можно имитировать, используя глобальные переменные или клас- сы. Например, такие объявления можно использовать в языке
Java:
Пример имитации перечислимых типов (Java)
// Создание перечислимого типа Country.
class Country {
private Country() {}
public static final Country China = new Country();
public static final Country England = new Country();
public static final Country France = new Country();
public static final Country Germany = new Country();
public static final Country India = new Country();
public static final Country Japan = new Country();
}
// Создание перечислимого типа Output.
class Output {
private Output() {}
public static final Output Screen = new Output();
public static final Output Printer = new Output();
public static final Output File = new Output();
}
Перекрестная ссылка На момент написания этой книги Java не поддерживает перечислимых ти- пов. Но, возможно, уже делает это к моменту, когда вы это чи- таете.

ГЛАВА 12 Основные типы данных
299
Такие перечислимые типы делают вашу программу читабельнее, поскольку вы можете использовать открытые члены класса, например,
Country. England и Out-
put. Screen, вместо именованных констант. Именно такой способ создания пере- числимых типов еще и обеспечивает безопасность типов, так как каждый тип объяв- лен как класс и компилятор будет проверять некорректные присваивания вроде
Output output = Country. England (Bloch, 2001).
В языках, не поддерживающих классы, такого же эффекта можно достичь, акку- ратно используя глобальные переменные для каждого элемента перечисления.
12.7. Именованные константы
Именованные константы аналогичны переменным за исключением того, что вы не можете изменить значение константы после ее инициализации. Именованные кон- станты позволяют ссылаться на постоянные величины, например, максимальное количество работников, используя имя, а не число —
MAXIMUM_EMPLOYEES, а не 1000.
Применение именованных констант — это способ «параметризации» программы,
т. е. размещение некоторой характеристики, которая может измениться, в пара- метре. В результате вам потребуется изменить его только в одном месте, а не по всей программе. Если вы когда-нибудь объявляли массив такой длины, какой, по- вашему, должно хватить для любых ситуаций, а потом выяснялось, что массив слишком мал, вы оцените значение именованных констант. Когда размер масси- ва изменяется, вы меняете только определение константы, используемой для объяв- ления массива. Такое «централизованное управление» имеет большое значение для того, чтобы сделать работу с ПО действительно приятной: упростить его поддер- жку и модификацию.
Используйте именованные константы в объявлениях данных Именован- ные константы повышают читабельность и удобство сопровождения объявлений данных и выражений, которым необходимо знать размеры обрабатываемых дан- ных. В следующем примере для описания длины телефонных номеров работни- ков лучше использовать
LOCAL_NUMBER_LENGTH, а не число 7.
Хороший пример применения именованных констант
в объявлениях данных (Visual Basic)
Const AREA_CODE_LENGTH = 3
LOCAL_NUMBER_LEN GTH здесь объявляется как константа.
Const LOCAL_NUMBER_LENGTH = 7
Type PHONE_NUMBER
areaCode( AREA_CODE_LENGTH ) As String
А здесь используется.
localNumber( LOCAL_NUMBER_LENGTH ) As String
End Type
>
>

300
ЧАСТЬ III Переменные
’ Убедимся, что все символы в телефонном номере — это цифры.
И здесь тоже используется.
For iDigit = 1 To LOCAL_NUMBER_LENGTH
If ( phoneNumber.localNumber( iDigit ) < “0” ) Or _
( “9” < phoneNumber.localNumber( iDigit ) ) Then
‘ Выполняем обработку ошибок.
Это простой пример, но вы вполне можете представить программу, в которой сведения о длине телефонных номеров требуются во многих местах.
На момент создания программы все работники живут в одной стране, поэтому вам нужно только семь цифр для их телефонных номеров. По мере расширения ком- пании ее филиалы открываются в разных странах, и вам понадобятся более длин- ные телефонные номера. Если вы параметризовали эту длину, вам надо сделать изменение только в одном месте — в определении именованной константы
LOCAL_
NUMBER_LENGTH.
Как вы, наверное, поняли, именованные константы делают сопровождение программы удобнее. Как правило, любая технология, централизующая управление объектами, подвер- женными изменениям, — это хороший способ уменьшить затраты на сопровождение (Glass, 1991).
Избегайте литеральных значений, даже «безопасных» Как вы думаете, что в следующем цикле означает число
12?
Пример непонятного кода (Visual Basic)
For i = 1 To 12
profit( i ) = revenue( i ) – expense( i )
Next
Исходя из специфического содержимого кода, можно предположить, что выпол- няется цикл по 12 месяцам в году. Но вы
уверены? Вы поставите на это свое со- брание «Монти Пайтон»?
В этом случае вам не нужно использовать именованные константы для поддерж- ки расширяемости: вряд ли число месяцев в году изменится в ближайшем буду- щем. Но если при написании кода остается хотя бы тень сомнения в его предназ- начении, развейте ее с помощью хорошо названной именованной константы,
например, так:
Пример более понятного кода (Visual Basic)
For i = 1 To NUM_MONTHS_IN_YEAR
profit( i ) = revenue( i ) – expense( i )
Next
Это уже лучше, но для завершения примера индекс цикла тоже нужно назвать более информативно:
Дополнительные сведения О зна- чении централизованного управ- ления см. стр. 57–60 в книге
«Software Conflict» (Glass, 1991).
>

ГЛАВА 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.

1   ...   34   35   36   37   38   39   40   41   ...   104


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