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

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


Скачать 5.88 Mb.
НазваниеРуководство по стилю программирования и конструированию по
АнкорСовершенный код
Дата31.03.2023
Размер5.88 Mb.
Формат файлаpdf
Имя файлаСовершенный код. Мастер-класс. Стив Макконнелл.pdf
ТипРуководство
#1028502
страница39 из 106
1   ...   35   36   37   38   39   40   41   42   ...   106
ГЛАВА 12 Основные типы данных
291
носитесь все#таки с подозрением к выражениям, включающим переменные с обоими префиксами.
Объявляйте для строк в стиле C длину, равную КОНСТАНТА+1 В C и C++
ошибки завышения на 1 в C#строках — обычное явление, потому что очень легко забыть, что строка длины
n требует для хранения n + 1 байт, и не выделить место для нулевого терминатора (байта в конце строки, установленного в 0). Эффектив#
ный способ решения этой проблемы — использовать именованные константы при объявлении всех строк. Суть в том, что именованные константы применяются всегда одинаково: Сначала длина строки объявляется как
КОНСТАНТА+1, а затем КОНСТАН%
ТА используется для обозначения длины строки во всем остальном коде. Вот пример:
Пример правильных объявлений строк (С)
/* Объявляем строку длиной «константа+1».
Во всех остальных местах программы используем «константа»,
а не «константа +1». */
Эта строка объявлена с длиной NAME_LENGTH +1.
char name[ NAME_LENGTH + 1 ] = { 0 }; /* Длина строки — NAME_LENGTH */
/* Пример 1: Заполняем строку символами ‘A’, используя константу NAME_LENGTH
для определения количества символов ‘A’, которые можно скопировать.
Заметьте: используется NAME_LENGTH, а не NAME_LENGTH + 1. */
В действиях со строкой NAME_LENGTH используется здесь…
for ( i = 0; i < NAME_LENGTH; i++ )
name[ i ] = ‘A’;
/* Пример 2: Копируем другую строку в первую, используя константу для определения максимальной длины, которую можно копировать. */
…и здесь.
strncpy( name, some_other_name, NAME_LENGTH );
Если у вас не будет соглашения по этому поводу, иногда вы будете объявлять строки длиной
NAME_LENGTH, а в операциях использовать NAME_ LENGTH%1; а иногда вы будете объявлять строки длиной
NAME_LENGTH+1 и работать с NAME_LENGTH. Каждый раз при использовании строки вам придется вспоминать, как вы ее объявили.
Если же вы всегда одинаково объявляете строки, думать, как работать с каждой из них, не надо, и вы избежите ошибок из#за того, что забыли особенность объявле#
ния данной строки. Выработка соглашения минимизирует умственную перегруз#
ку и ошибки при программировании.
Инициализируйте строки нулем во избежание строк
бесконечной длины Язык C определяет конец строки пу#
тем поиска нулевого терминатора — байта в конце строки,
установленного в 0. Какой предполагалась длина строки, зна#
>
>
>
Перекрестная ссылка Подроб- нее об инициализации данных см. раздел 10.3.

292
ЧАСТЬ III Переменные чения не имеет: C никогда не найдет ее конец, если не найдет нулевой байт. Если вы забыли поместить нулевой байт в конец строки, строковые операции могут ра#
ботать не так, как вы ожидаете.
Вы можете предупредить появление бесконечных строк двумя способами. Во#пер#
вых, при объявлении инициализируйте символьные массивы
0:
Пример правильного объявления символьного массива (C)
char EventName[ MAX_NAME_LENGTH + 1 ] = { 0 };
Во#вторых, при динамическом создании строк инициализируйте их
0, используя функцию
calloc() вместо malloc(). Функция calloc() выделяет память и инициали#
зирует ее
0. malloc() выделяет память без инициализации, поэтому вы рискуете,
используя память, выделенную с помощью
malloc().
Используйте в C массивы символов вместо указате'
лей Если объем занимаемой памяти некритичен (а часто так и есть), объявляйте все строковые переменные как мас#
сивы символов. Это поможет избежать проблем с указателями, а компилятор бу#
дет выдавать больше предупреждений в случае неправильных действий.
Используйте strncpy() вместо strcpy() во избежание строк бесконечной
длины Строковые функции в C существуют в опасной и безопасной версиях. Более опасные функции, такие как
strcpy() и strcmp(), продолжают работу до обнаруже#
ния нулевого терминатора. Их более безобидные спутники —
strncpy() и strncmp()
— принимают максимальную длину в качестве параметра, так что, даже если строки будут бесконечными, ваши вызовы функций не зациклятся.
12.5. Логические переменные
Логические или булевы переменные сложно использовать неправильно, а их вдум#
чивое применение сделает вашу программу аккуратней.
Используйте логические переменные для документи'
рования программы Вместо простой проверки логиче#
ского выражения вы можете присвоить его значение пере#
менной, которая сделает смысл теста очевидным. Например,
в этом фрагменте из условия
if не ясно, выполняется ли проверка завершения, ошибочной ситуации или чего#то еще:
Пример логического условия, чье назначение неочевидно (Java)
if ( ( elementIndex < 0 ) || ( MAX_ELEMENTS < elementIndex ) ||
( elementIndex == lastElementIndex )
) {
}
В следующем фрагменте применение логических переменных делает назначение
if#проверки яснее:
Перекрестная ссылка О масси- вах см. раздел 12.8.
Перекрестная ссылка Об ис- пользовании комментариев для документирования программы см. главу 32.
Перекрестная ссылка Пример использования логической фун- кции для документирования программы см. в подразделе
«Упрощение сложных выраже- ний» раздела 19.1.

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

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


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