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

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


Скачать 5.88 Mb.
НазваниеРуководство по стилю программирования и конструированию по
АнкорСовершенный код
Дата31.03.2023
Размер5.88 Mb.
Формат файлаpdf
Имя файлаСовершенный код. Мастер-класс. Стив Макконнелл.pdf
ТипРуководство
#1028502
страница54 из 106
1   ...   50   51   52   53   54   55   56   57   ...   106
ГЛАВА 18 Табличные методы
423
Контрольный список: табличные методы
 Рассмотрены ли табличные методы в качестве альтерна- тивы сложной логике?
 Рассмотрены ли табличные методы в качестве альтернативы сложным струк- турам с наследованием?
 Рассмотрен ли вопрос размещения табличных данных отдельно от программы и их чтения во время выполнения, чтобы позволить обновлять данные без изменения кода?
 Если доступ к таблице нельзя осуществить напрямую с помощью простого индекса массива (как в примере с возрастами), помещены ли вычисления ключа доступа в отдельный метод, а не разбросаны по всему коду?
Ключевые моменты

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

Основной вопрос при использовании таблиц состоит в выборе способа до#
ступа к таблице. Вы можете использовать прямой, индексный или ступенча#
тый доступ.

Другой основной вопрос состоит в выборе того, что конкретно будет поме#
щено в таблицу.
http://cc2e.com/1872

424
ЧАСТЬ IV Операторы
Г Л А В А 1 9
Общие вопросы управления
Содержание

19.1. Логические выражения

19.2. Составные операторы (блоки)

19.3. Пустые выражения

19.4. Укрощение опасно глубокой вложенности

19.5. Основа программирования: структурное програм#
мирование

19.6. Управляющие структуры и сложность
Связанные темы

Последовательный код: глава 14

Код с условными операторами: глава 15

Код с циклами: глава 16

Нестандартные управляющие структуры: глава 17

Сложность в разработке ПО: подраздел «Главный Технический Императив Раз#
работки ПО: управление сложностью» раздела 5.2
Обсуждение способов управления было бы неполным без углубления в несколь#
ко общих вопросов, касающихся управляющих структур. Большая часть этой гла#
вы посвящена подробностям их практического применения. Если вам интерес#
ней читать о теории управляющих структур, а не о мелких деталях, сосредоточь#
тесь на исторической перспективе структурного программирования (раздел 19.5),
а затем на взаимодействии управляющих структур (раздел 19.6).
19.1. Логические выражения
Кроме простейших случаев, таких, в которых происходит последовательное вы#
полнение операторов, все управляющие структуры зависят от вычисления логи#
ческих выражений.
http://cc2e.com/1978

ГЛАВА 19 Общие вопросы управления
425
Использование true и false в логических проверках
В логических выражениях следует использовать идентификаторы
true и false, а не значения
0 и 1. Большинство современных языков реализует логический тип дан#
ных и предоставляет предопределенные идентификаторы для значений «истина»
и «ложь». В таких языках все просто: они даже не позволят вам присвоить логиче#
ским переменным другие значения, кроме
true или false. В языках, не содержащих встроенного логического типа, от вас требуется большая дисциплинированность,
чтобы сделать логические выражения читабельными. Приведем пример:
Примеры использования неоднозначных флажков
для логических значений (Visual Basic)
Dim printerError As Integer
Dim reportSelected As Integer
Dim summarySelected As Integer
If printerError = 0 Then InitializePrinter()
If printerError = 1 Then NotifyUserOfError()
If reportSelected = 1 Then PrintReport()
If summarySelected = 1 Then PrintSummary()
If printerError = 0 Then CleanupPrinter()
Если использование флажков
0 и 1 — общая практика, то чем она плоха? При чте#
нии кода неочевидно, когда выполняются вызовы функций: когда проверки усло#
вия истинны или когда они ложны. Ничего в этом фрагменте не говорит о том,
представляет ли
1 «истину», а 0 — «ложь» или же все наоборот. Нельзя даже утвер#
ждать, что
1 и 0 служат в качестве значений «истина» и «ложь». Например, в строке
If reportSelected = 1 число 1 может легко обозначать первый отчет, 2 — второй, 3
третий; ничто в коде не наводит на мысль, что
1 означает либо «истину», либо «ложь».
Кроме того, легко можно написать
0, имея в виду 1, и наоборот.
Используйте термины
true и false для проверки логических выражений. Если ваш язык не поддерживает их напрямую, создайте их с помощью макросов препроцес#
сора или глобальных переменных. Вот как можно переписать предыдущий пример,
используя встроенные идентификаторы
True и False языка Visual Basic:
Хорошие, но не превосходные примеры использования True
и False вместо числовых значений для проверок условий (Visual Basic)
Dim printerError As Boolean
Dim reportSelected As ReportType
Dim summarySelected As Boolean
If ( printerError = False ) Then InitializePrinter()
If ( printerError = True ) Then NotifyUserOfError()
If ( reportSelected = ReportType_First ) Then PrintReport()
If ( summarySelected = True ) Then PrintSummary()
Перекрестная ссылка Еще луч- ший подход к выполнению тех же проверок можно найти в сле- дующем примере кода.

426
ЧАСТЬ IV Операторы
If ( printerError = False ) Then CleanupPrinter()
Применение констант
True и False проясняет назначение кода. Вам не нужно по#
мнить, что обозначают
1 и 0, и вы не сможете их случайно перепутать. Более того,
в переписанном коде стало понятно, что в некоторых случаях
1 и 0 в исходном примере на Visual Basic не являлись логическими флагами. Выражение
If report%
Selected = 1 было не проверкой логического значения, а проверкой того, выбран ли первый отчет.
Этот подход сообщает читателю, что вы выполняете логическую проверку. Кро#
ме того, сложнее написать
true, подразумевая false, чем 1, подразумевая 0. Также вы избежите распространения магических чисел
0 и 1 по всему коду. Далее при#
ведены советы по использованию
true и false в логических проверках.
Используйте неявное сравнение логических величин с true или false
Вы сможете сделать проверку условия более понятной, если будете рассматривать про#
веряемые выражения как логические. Например, пишите:
while ( not done ) ...
while ( a > b ) ...
вместо:
while ( done = false ) ...
while ( (a > b) = true ) ...
Использование неявных сравнений уменьшает число элементов, которые придется помнить при чтении кода, и приближает получаемое выражение к разговорному английскому. Вот как улучшить стиль предыдущего примера:
Улучшенные примеры неявных проверок True или False (Visual Basic)
Dim printerError As Boolean
Dim reportSelected As ReportType
Dim summarySelected As Boolean
If ( Not printerError ) Then InitializePrinter()
If ( printerError ) Then NotifyUserOfError()
If ( reportSelected = ReportType_First ) Then PrintReport()
If ( summarySelected ) Then PrintSummary()
If ( Not printerError ) Then CleanupPrinter()
Если ваш язык не поддерживает логические переменные и вам приходится их эмулировать, то, вероятно, вы не смо#
жете использовать эту технологию, поскольку искусствен#
ные
true и false не всегда могут проверяться в таких выра#
жениях, как
while ( not done ).
Перекрестная ссылка О логи- ческих переменных см. раздел
12.5.

ГЛАВА 19 Общие вопросы управления
427
Упрощение сложных выражений
Вы можете предпринять несколько описанных далее шагов для упрощения слож#
ных выражений.
Разбивайте сложные проверки на части с помощью новых логических
переменных Вместо создания чудовищных условий с полудюжиной элементов присвойте значения этих элементов промежуточным переменным, что позволит выполнять упрощенную проверку.
Размещайте сложные выражения в логических функциях Если какая#то проверка выполняется неоднократно или отвлекает от основного хода алгорит#
ма программы, поместите ее код в отдельную функцию и проверяйте значение этой функции. Вот пример сложного условия:
Пример проверки сложного условия (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)
Function DocumentIsValid( _
ByRef documentToCheck As Document, _
lineCount As Integer, _
inputError As Boolean _
) As Boolean
Dim allDataRead As Boolean
Dim legalLineCount As Boolean
Промежуточные переменные добавлены для упрощения проверки в самой последней строке.
allDataRead = ( documentToCheck.AtEndOfStream ) And ( Not inputError )
legalLineCount = ( MIN_LINES <= lineCount ) And ( lineCount <= MAX_LINES )
DocumentIsValid = allDataRead And legalLineCount And ( Not ErrorProcessing() )
End Function
Здесь предполагается, что
ErrorProcessing() — это некоторая логическая функция,
определяющая текущее состояние обработки документа. Теперь, когда вы читае#
те основной ход алгоритма, вам не надо разбираться со сложным условием:
Перекрестная ссылка О методи- ке использования промежуточ- ных переменных для проясне- ния логических проверок см.
подраздел «Используйте логи- ческие переменные для доку- ментирования программы» раз- дела 12.5.
>

428
ЧАСТЬ IV Операторы
Пример основного хода алгоритма, не содержащего сложное условие (Visual Basic)
If ( DocumentIsValid( document, lineCount, inputError ) ) Then
‘ Делаем то или иное.
End If
Если вы проверяете условие только раз, вам может показаться, что его не стоит помещать в отдельный метод. Но вынесение условия в отдель#
ную, хорошо названную функцию улучшит читабельность кода и упрос тит понимание того, что этот код делает. Это достаточная причина для создания функции. Имя нового метода привносит в программу абстракцию, которая доку#
ментирует назначение проверки
прямо в коде. Это даже лучше, чем документи#
рование условия с помощью комментариев, потому что код будет читаться и мо#
дифицироваться с большей вероятностью, чем комментарии.
Используйте таблицы решений для замены сложных
условий
Иногда нужно проверять сложные условия, со#
держащие несколько переменных. В этом случае для выпол#
нения проверки удобней применять таблицы решений, а не операторы
if или case. Таблицу решений изначально проще кодировать — она требует пары строк кода и никаких изощренных управляющих структур. Такая ми#
нимизация сложности уменьшает возможность ошибок. При изменении данных вы можете изменить таблицу решений, не меняя код: вам всего лишь надо обно#
вить содержимое структуры данных.
Составление позитивных логических выражений
Не немногие люди не имеют проблем с непониманием не#
коротких неположительных фраз, т. е. большинство людей имеют трудности с пониманием большого количества от#
рицаний. Вы можете предпринять какие#то действия, что#
бы избежать кодирования сложных отрицательных логических выражений в про#
грамме.
В операторах if заменяйте негативные выражения позитивными, меняя
местами блоки if и else
Вот пример негативного условия:
Пример сбивающего с толку отрицательного логического условия (Java)
Здесь оператор отрицания.
if ( !statusOK ) {
// Делаем чтото.
}
else {
// Делаем чтото еще.
}
Перекрестная ссылка Об ис- пользовании таблиц для заме- ны сложной логики см. главу 18.
Я не не нетупой.
Гомер Симпсон
(Homer Simpson)
>

ГЛАВА 19 Общие вопросы управления
429
Это условие можно заменить другим, выраженным положительно:
Пример более понятного логического условия на Java
Условие в этой строке было заменено на противоположное.
if ( statusOK ) {
// Делаем чтото еще.
Код в этом блоке был поменян местами...
}
else {
...с кодом в этом блоке.
// Делаем чтото.
}
Второй фрагмент кода логически совпадает с первым, но его легче читать, потому что отрицательное выражение было изменено на положительное.
В качестве альтернативы вы можете использовать другие имена переменных, которые изменят истинное значение условия на противоположное. В приведенном примере вы можете заменить переменную
statusOK на ErrorDetected, ко#
торая будет истиной, когда
statusOK будет ложно.
Применяйте теоремы Деморгана для упрощения логи'
ческих проверок с отрицаниями
Теоремы Деморгана позволяют эксплуатировать логическую взаимосвязь меж#
ду некоторым выражением и версией этого выражения, обозначающего то же са#
мое, благодаря использованию двойного отрицания. Рассмотрим фрагмент кода,
содержащий следующее условие:
Пример условия с отрицанием (Java)
if ( !displayOK || !printerOK ) ...
Это условие логически эквивалентно следующему:
Пример после применения теорем Деморгана (Java)
if ( !( displayOK && printerOK ) ) ...
В данном случае вам не надо менять местами блоки
if и else — выражения в двух последних фрагментах кода логически эквивалентны. Для применения теорем
Деморгана к логическому оператору
and или or и паре операндов вы инвертиру#
ете оба операнда, меняете местами операторы
and и or и инвертируете все выра#
жение целиком. Табл. 19#1 обобщает возможные преобразования в соответствии с теоремами Деморгана.
Перекрестная ссылка Рекомен- дация по составлению положи- тельных логических выражений иногда идет вразрез с рекомен- дацией кодировать номиналь- ный вариант в блоке if, а не в блоке else (см. раздел 15.1).
В этом случае вам нужно взвесить преимущества каждого подхода и решить, какой вариант в вашей ситуации будет наилучшим.
>
>
>

430
ЧАСТЬ IV Операторы
Табл. 19-1. Преобразования логических переменных
в соответствии с теоремами Деморгана
Исходное выражение
Эквивалентное выражение
not A and not B
not ( A or B )
not A and B
not ( A or not B )
A and not B
not ( not A or B )
A and B
not ( not A or not B )
not A or not B*
not ( A and B )
not A or B
not ( A and not B )
A or not B
not ( not A and B )
A or B
not ( not A and not B )
*
Это выражение и используется в примере.
Использование скобок для пояснения
логических выражений
Если у вас есть сложное логическое выражение, не надей#
тесь на порядок вычисления его операндов в языке програм#
мирования — используйте скобки, чтобы сделать ваши на#
мерения понятными. Скобки уменьшают требования, предъ#
являемые к читателю кода, который может и не разобрать#
ся в тонкостях вычисления логических выражений в вашем языке программирования. Если вы благоразумны, то не будете полагаться на соб#
ственную или чью#то еще способность правильно запомнить приоритет вычис#
лений, особенно если приходится переключаться между двумя или более языка#
ми. Использование скобок не похоже на отправку телеграммы: вы не платите за каждую букву — дополнительные символы бесплатны.
Вот пример выражения, содержащего слишком мало скобок:
Пример выражения, содержащего слишком мало скобок (Java)
if ( a < b == c == d ) ...
Начнем с того, что это выражение слишком запутано. Оно тем более сбивает с толку,
что не ясно, хотел ли кодировщик проверить условие
( a < b ) == ( c == d ) или ( ( a <
b ) == c ) == d. Следующая версия все равно не идеальна, но скобки все же помогают:
Пример выражения, частично улучшенного с помощью скобок (Java)
if ( ( a < b ) == ( c == d ) ) ...
В этом случае скобки повышают удобство чтения и корректность программы,
поскольку компилятор не истолковал бы первый фрагмент таким способом. Ког#
да сомневаетесь, используйте скобки.
Используйте простой метод подсчета для проверки симметричности
скобокЕсли у вас возникают проблемы с поиском парных скобок, то вот про#
стой способ подсчета. Начните считать, сказав «ноль». Двигайтесь вдоль выраже#
Перекрестная ссылка Примеры применения скобок для прояс- нения других видов выражений см. в подразделе «Скобки» раз- дела 31.2.

ГЛАВА 19 Общие вопросы управления
431
ния слева направо. Встретив открывающую скобку, скажите
«один». Каждый раз при встрече открывающей скобки уве#
личивайте число. А встречая закрывающую скобку, умень#
шайте это число. Если к концу выражения у вас опять по#
лучится 0, то ваши скобки симметричны.
Пример симметричных скобок (Java)
Читаем это выражение.
if ( ( ( a < b ) == ( c == d ) ) && !done ) ...
| | | | | | | |
0 1 2 3 2 3 2 1 0
Произносим эти числа.
В этом примере в конце получился 0, следовательно, скобки симметричны. В сле#
дующем примере количество отрывающих и закрывающих скобок не одинаково:
Пример несимметричных скобок (Java)
Читаем это выражение.
if ( ( a < b ) == ( c == d ) ) && !done ) ...
| | | | | | |
0 1 2 1 2 1 0 1
Произносим эти числа.
Значение 0, полученное до последней закрывающей скобки, подсказывает, что где#
то до этой точки была пропущена скобка. Вы не должны получить 0, не достиг#
нув последней скобки в выражении.
Заключайте в скобки логическое выражение целиком Скобки ничего вам не стоят, а читабельность улучшают. Привычка заключать в скобки все логичес#
кое выражение целиком — хорошая практика программирования.
Понимание правил вычисления логических выражений
Множество языков содержит неявную управляющую форму, которая начинает действовать при вычислении логических выражений. Компиляторы некоторых языков вычисляют каждый элемент логического выражения перед объединением всех этих элементов и вычисления значения всего выражения. Компиляторы других используют «короткозамкнутый» (или «ленивый») алгоритм, обрабатывая только необходимые элементы выражения. Это особенно важно, когда в зависимости от результатов первой проверки вы можете не захотеть выполнять следующий тест.
Допустим, вы проверяете элементы массива с помощью следующего выражения:
Пример псевдокода неправильной проверки условия
while ( i < MAX_ELEMENTS and item[ i ] <> 0 ) ...
Если вычисляется выражение целиком, вы получите ошибку при последней ите#
рации цикла. В этом случае переменная
i равна maxElements, а значит, выражение
item[ i ] эквивалентно item[ maxElements ], что является недопустимым значением
Перекрестная ссылка Многие текстовые редакторы, ориенти- рованные на программистов,
предоставляют команды для поиска парных круглых, квад- ратных и фигурных скобок.
О редакторах для программиро- вания см. подраздел «Редакти- рование» раздела 30.2.
>
>
>
>

432
ЧАСТЬ IV Операторы индекса. Вы можете возразить, что это не имеет значения, поскольку вы только обращаетесь к элементу, а не изменяете его. Но это неряшество способно сбить с толку читателя вашего кода. Во многих средах этот код будет также генерировать ошибку выполнения или нарушение защиты.
Используя псевдокод, можно реструктурировать данное условие так, чтобы эта ошибка не возникала:
Пример псевдокода правильно реструктурированной проверки условия
while ( i < MAX_ELEMENTS )
if ( item[ i ] <> 0 ) then
Этот вариант корректен, так как
item[ i ] будет вычисляться, только когда i мень#
ше, чем
maxElements.
Многие современные языки предоставляют средства, которые изначально предот#
вращают возможность возникновения такой ошибки. Так, C++ использует корот#
козамкнутые вычисления: если значение первого операнда в операции
and лож#
но, то второй операнд не вычисляется, потому что полное выражение в любом случае будет ложным. Иначе говоря, в C++ единственный элемент выражения:
if ( SomethingFalse && SomeCondition ) ...
который будет вычисляться, — это
SomethingFalse. Обработка выражения заверша#
ется, поскольку значение
SomethingFalse определяется как ложное.
Аналогичное укороченное вычисление будет производиться и для оператора
or.
В C++ и Java в выражении:
if ( somethingTrue || someCondition ) ...
вычисляется только
somethingTrue. Обработка завершается, как только операнд
somethingTrue определяется как истинный, так как все выражение будет истинным,
если истинна хотя бы одна из его частей. В результате такого способа вычисле#
ния следующее выражение вполне допустимо:
Пример условия, которое работает благодаря короткозамкнутому вычислению (Java)
if ( ( denominator != 0 ) && ( ( item / denominator ) > MIN_VALUE ) ) ...
Если бы это выражение вычислялось целиком, то в случае, когда переменная
denominator равна 0, операция деления во втором операнде генерировала бы ошибку деления на 0. Но поскольку вторая часть не вычисляется, если значение первой ложно, то когда
denominator равен 0, вторая операция не выполняется, и ошибка деления на 0 не возникает.
С другой стороны, из#за того что операция
&& (and) вычисляется слева направо,
следующее логически эквивалентное выражение работать не будет:

1   ...   50   51   52   53   54   55   56   57   ...   106


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