Файл: Руководство по стилю программирования и конструированию по.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 795
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
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.
1 ... 48 49 50 51 52 53 54 55 ... 104
ГЛАВА 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) вычисляется слева направо,
следующее логически эквивалентное выражение работать не будет:
ГЛАВА 19 Общие вопросы управления
433
Пример условия, в котором короткозамкнутое вычисление не спасает от ошибки (Java)
if ( ( ( item / denominator ) > MIN_VALUE ) && ( denominator != 0 ) ) ...
Здесь
item / denominator вычисляется раньше, чем denominator != 0. Следователь- но, в этом коде происходит ошибка деления на 0.
Язык Java еще более усложняет эту картину, предоставляя «логические» операто- ры. Логические операторы
& и | языка Java гарантируют, что все элементы будут вычислены полностью независимо от того, определяется ли истинность или лож- ность выражения без его полного вычисления. Иначе говоря, в Java такое усло- вие будет безопасно:
Пример условия, которое работает благодаря
короткозамкнутому (условному) вычислению (Java)
if ( ( denominator != 0 ) && ( ( item / denominator ) > MIN_VALUE ) ) ...
А вот такое — нет:
Пример условия, которое не будет работать, потому что короткозамкнутое
вычисление не гарантируется (Java)
if ( ( denominator != 0 ) & ( ( item / denominator ) > MIN_VALUE ) ) ...
Разные языки используют разные способы вычисления, и случается, что разработчики языка чересчур свободно обращаются с правилами вычис- ления выражений, поэтому обратитесь к руководству по вашей версии языка, чтобы выяснить, как в нем выполняются эти операции. Еще лучше (поскольку читатель может не обладать вашей сообразительностью) использовать вложенные условия, проясняющие ваши намерения, и не зависеть от порядка обработки вы- ражений и короткозамкнутых вычислений.
Упорядочение числовых выражений
в соответствии со значениями на числовой прямой
Организуйте числовые условия так, чтобы они соответствовали порядку точек на числовой прямой. В общем случае структурируйте выражения так, чтобы сравне- ния выглядели следующим образом:
MIN_ELEMENTS <= i and i <= MAX_ELEMENTS
i < MIN_ELEMENTS or MAX_ELEMENTS < i
Идея в том, чтобы расположить элементы по порядку слева направо, от наимень- ших к наибольшим. В первой строке
MIN_ELEMENTS и MAX_ELEMENTS — это две граничные точки, и поэтому они расположены по краям выражения. Подразуме- вается, что переменная
i должна находиться между ними, и поэтому она располо- жена в середине. Во втором примере вы проверяете, находится ли
i вне диапазо- на, поэтому
i расположена по краям условия, а MIN_ELEMENTS и MAX_ELEMENTS —
посредине. Этот подход позволяет легко создать визуальное представление срав- нения (рис. 19-1):