Файл: Руководство по стилю программирования и конструированию по.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 783
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
ГЛАВА 31 Форматирование и стиль
741
матирования многострочных вызовов методов работает хорошо при последова- тельном применении.
При переносе строк в управляющем выражении делайте отступ стандар-
тного размера При нехватке места для цикла for, цикла while или оператора if,
перенося строку, сделайте такой же отступ, как и в выражениях внутри цикла или после условия
if. В листинге 31-45 приведены два примера:
Листинг 31-45. Примеры отступов при переносе строк в управляющих
выражениях (Java)
while ( ( pathName[ startPath + position ] != ‘;’ ) &&
В продолжении строки отступ содержит стандартное количество пробелов...
( ( startPath + position ) <= pathName.length() ) ) {
}
for ( int employeeNum = employee.first + employee.offset;
...так же, как и в этом случае.
employeeNum < employee.first + employee.offset + employee.total;
employeeNum++ ) {
}
Приведенный код соответствует критериям, установленным ранее в этой главе. Перенос строки выполняется логично
— в нем всегда есть отступ относительно выражения, кото- рое он продолжает. Дальнейшее выравнивание может вы- полняться как обычно — новая строка занимает всего на несколько пробелов больше, чем исходный вариант. Он так же читаем, как и любой другой код, и его так же просто сопровождать. В некоторых случаях вы могли бы повысить удобочитаемость, на- строив отступы или пробелы, но, рассматривая вопросы тонкой настройки, не забывайте о компромиссе с точки зрения удобства сопровождения.
Не выравнивайте правые части выражений присваивания В первом из- дании этой книги я рекомендовал выравнивать правые части выражений, содер- жащие присваивания (листинг 31-46):
Листинг 31-46. Примеры форматирования в конце строки, используемого
при выравнивании выражений присваивания, — плохая практика (Java)
customerPurchases = customerPurchases + CustomerSales( CustomerID );
customerBill = customerBill + customerPurchases;
totalCustomerBill = customerBill + PreviousBalance( customerID ) +
LateCharge( customerID );
customerRating = Rating( customerID, totalCustomerBill );
Перекрестная ссылка Иногда для сложных условий лучше всего поместить их в логичес- кие функции (см. подраздел
«Упрощение сложных выраже- ний» раздела 19.1).
>
>
742
ЧАСТЬ VII Мастерство программирования
С высоты 10-летнего опыта могу сказать, что, хотя этот стиль может выглядеть привлекательно, он превращается в настоящую головную боль, когда приходится поддерживать выравнивание знаков равенства при изменении имен переменных и прогоне кода через утилиты, заменяющие пробелы знаками табуляции, а знаки табуляции — пробелами. Кроме того, его тяжело сопровождать при перемещении строк между частями программы, в которых применяются разные размеры отступов.
Для обеспечения единообразия с другими принципами отступов, а также учиты- вая вопросы удобства сопровождения, обращайтесь с группами выражений, со- держащими операции присваивания, так же, как вы бы обращались с любыми другими выражениями (листинг 31-47):
Листинг 31-47. Пример использования стандартных отступов для выравнивания
выражений присваивания — хорошая практика (Java)
customerPurchases = customerPurchases + CustomerSales( CustomerID );
customerBill = customerBill + customerPurchases;
totalCustomerBill = customerBill + PreviousBalance( customerID ) +
LateCharge( customerID );
customerRating = Rating( customerID, totalCustomerBill );
При переносе строк в выражениях присваивания применяйте отступы
стандартного размера В листинге 31-47 при переносе строки в третьем при- сваивании используется стандартный размер отступа. Это сделано с той же це- лью, что и отказ от специального форматирования выражений присваивания: из общих соображений читаемости и удобства сопровождения.
Размещение одного оператора на строке
Современные языки, такие как C++ и Java, позволяют располагать несколько опе- раторов на одной строке. Однако когда дело касается этого вопроса, мощь сво- бодного форматирования оборачивается палкой о двух концах. Следующая стро- ка содержит несколько выражений, которые, с точки зрения логики, вполне мо- гут располагаться в отдельных строках:
i = 0; j = 0; k = 0; DestroyBadLoopNames( i, j, k );
Аргументом в защиту размещения нескольких выражений на одной строке может служить факт, что в этом случае требуется меньшее число строк экранного про- странства или бумаги для распечатки, что позволяет одновременно видеть боль- ший объем кода. Это также позволяет сгруппировать взаимосвязанные выраже- ния, а некоторые программисты даже полагают, что так они подсказывают ком- пилятору, как можно оптимизировать код.
Все так, но основания для самоограничения, требующие оставлять не более од- ного оператора в строке, гораздо серьезней.
쐽
Размещение каждого оператора на отдельной строке дает точное представле- ние о сложности программы. При этом не скрывается сложность из-за того,
что сложные операторы выглядят тривиальными. Сложные операторы и вы- глядят сложными, простые — простыми.
ГЛАВА 31 Форматирование и стиль
743
쐽
Размещение нескольких операторов на одной строке не помогает современным компиляторам в оптимизации.
Сегодняшние оптимизирующие компиляторы не нужда- ются в подсказках, сделанных с помощью форматирова- ния (см. ниже).
쐽
Если операторы расположены на отдельных строках, чтение кода происходит сверху вниз, а не сверху вниз и слева направо. При поиске определенной строки у взгляда должна быть возможность придерживаться левого края кода. Он не должен просматривать каждую строку целиком только потому, что в одной строке может быть два оператора.
쐽
При размещении операторов на отдельных строках легко найти синтаксичес- кие ошибки, если компилятор сообщает только номера строк, где они произош- ли. При расположении нескольких операторов на одной строке ее номер ни- чего не скажет о том, какой оператор содержит ошибку.
쐽
При размещении операторов на отдельных строках легко выполнять пошаго- вую отладку кода, используя построчные отладчики. Если строка содержит несколько операторов, отладчик выполнит их все одновременно, и вам при- дется переключиться на ассемблерный листинг для выполнения пошаговой отладки отдельных выражений.
쐽
Когда строка содержит только один оператор, его легко редактировать — можно удалить или временно закомментировать всю строку. Если же на одной стро- ке вы разместили несколько операторов, вам придется выполнять редактиро- вание между остальными операторами.
В C++ избегайте выполнения нескольких операций в одной строке (побоч-
ные эффекты) Побочные эффекты — это последствия выполнения некоторо- го выражения, проявляющиеся в дополнение к основным результатам выполне- ния этого выражения. Так, в C++ оператор
++, расположенный на одной строке с другими операторами, приводит к проявлению побочного эффекта. Присваива- ние значения переменной и применение левой части этого присваивания в ус- ловном операторе также является примером побочного эффекта.
Побочные эффекты снижают читаемость кода. Например, если
n равно 4, что напечатает выражение, приведенное в листинге 31-48?
Листинг 31-48. Пример непредсказуемого побочного эффекта (C++)
PrintMessage( ++n, n + 2 );
4 и 6? Или 5 и 7? А может, 5 и 6? Правильный ответ: «Ни то, ни другое и не тре- тье». Первый аргумент —
++n — равен 5. Но язык C++ не определяет порядок вы- числения условий выражения или аргументов функции. Поэтому компилятор может вычислить второй аргумент,
n + 2, либо до, либо после первого аргумента, и ре- зультат может быть равен
6 или 7 в зависимости от компилятора. В листинге 31-
49 показано, как переписать это выражение, чтобы прояснить свои намерения:
Перекрестная ссылка Об опти- мизации производительности на уровне кода см. главы 25 и 26.
744
ЧАСТЬ VII Мастерство программирования
Листинг 31-49. Пример избавления от непредсказуемого побочного эффекта (C++)
++n;
PrintMessage( n, n + 2 );
Если вы все еще не совсем уверены в том, что побочные эффекты надо выносить в отдельные строки, попробуйте понять, что делает функция, приводимая в лис- тинге 31-50:
Листинг 31-50. Пример слишком большого количества операции в строке (C)
strcpy( char * t, char * s ) {
while ( *++t = *++s )
;
}
Некоторые опытные программисты не видят сложности в этом примере, потому что эта функция им знакома. Они смотрят на нее и говорят: «Это функция
strcpy()».
Однако в нашем случае это не совсем
strcpy(). Она содержит ошибку. Если вы ска- зали «Это
strcpy()», увидев данный код, вы узнали код, а не прочитали его. В такую же ситуацию вы попадаете при отладке программы: код, на который вы не обрати- ли внимания, потому что «узнали», а не прочли его, может содержать ошибку, по- иск которой займет гораздо больше времени, чем она этого заслуживает.
Фрагмент, показанный в листинге 31-51, функционально идентичен первому ва- рианту и гораздо удобней для чтения:
Листинг 31-51. Пример читаемого количества операций в каждой строке (C)
strcpy( char * t, char * s ) {
do {
++t;
++s;
*t = *s;
}
while ( *t != ‘\0’ );
}
В этом переформатированном коде ошибка очевидна. Конечно,
t и s инкремен- тируются до того, как
*s будет скопирована в *t. Первый символ пропускается.
Второй пример выглядит продуманней первого, хотя операции, выполняемые во втором примере, идентичны первому. Причина такого впечатления в том, что во втором варианте не скрывается сложность выполняемых действий.
Рост производительности также не оправдывает размеще- ния нескольких операций на одной строке. Поскольку обе функции
strcpy() логически эквивалентны, можно ожидать,
что компилятор сгенерирует для них идентичный код. Од- нако при профилировании обеих функций выяснилось, что для копирования
5 000 000 строк первой функции понадобилось 4,81 секунды, а второй —4,35.
В нашем случае «умная» версия показала снижение скорости на 11%, что делает ее гораздо менее умной. Результаты могут изменяться от компилятора к компиля-
Перекрестная ссылка О на- стройке кода см. главы 25 и 26.
ГЛАВА 31 Форматирование и стиль
745
тору, но в целом они свидетельствуют о том, что пока вы не измерили прирост производительности, следует сначала стремиться к ясности и корректности, а уж затем — к производительности.
Даже если вы легко читаете выражения с побочными эффектами, пожалейте тех,
кому придется разбираться с вашим кодом. Большинству программистов нужно дважды подумать, чтобы понять выражения с побочными эффектами. Позвольте им использовать мозговые клетки для осмысления более общих вопросов рабо- ты вашего кода, а не синтаксических деталей конкретного выражения.
Размещение объявлений данных
Располагайте каждое объявление данных в отдельной
строке Как показали предыдущие примеры, каждому объявлению данных надо выделять отдельную строку. Тог- да проще дописывать комментарии к каждому объявлению
— ведь каждое расположено в отдельной строке. Проще ис- правлять объявления, поскольку все они изолированы друг от друга. Проще находить конкретные переменные, так как можно просканировать одну колонку, а не читать каждую строчку. Проще искать и исправлять синтаксические ошибки — строка, указанная компилятором, содер- жит только одно объявление.
А ну-ка, скажите быстренько: какой тип имеет переменная
currentBottom в объяв- лении данных, приведенном в листинге 31-52?
Листинг 31-52. Пример скопления нескольких объявлений
переменных в одной строке (C++)
int rowIndex, columnIdx; Color previousColor, currentColor, nextColor; Point previousTop, previousBottom, currentTop, currentBottom, nextTop, nextBottom; Font previousTypeface, currentTypeface, nextTypeface; Color choices[ NUM_COLORS ];
Это, конечно, крайний случай, однако он не так уж далек от гораздо более рас- пространенного стиля, показанного в листинге 31-53:
Листинг 31-53. Пример скопления нескольких объявлений
переменных в одной строке (C++)
int rowIndex, columnIdx;
Color previousColor, currentColor, nextColor;
Point previousTop, previousBottom, currentTop, currentBottom, nextTop, nextBottom;
Font previousTypeface, currentTypeface, nextTypeface;
Color choices[ NUM_COLORS ];
Это не такой уж и редкий стиль объявления переменных, а конкретную перемен- ную все так же тяжело найти, поскольку все объявления свалены в кучу. Тип пере- менной тоже тяжело выяснить. А теперь скажите, какой тип имеет
nextColor в листинге 31-54?
Перекрестная ссылка О доку- ментировании объявлений дан- ных см. подраздел «Комменти- рование объявлений данных»
раздела 32.5. Об использовании данных см. в главах 10-13.
746
ЧАСТЬ VII Мастерство программирования
Листинг 31-54. Пример читаемого кода, достигнутого благодаря размещению
только одной переменной в каждой строке (C++)
int rowIndex;
int columnIdx;
Color previousColor;
Color currentColor;
Color nextColor;
Point previousTop;
Point previousBottom;
Point currentTop;
Point currentBottom;
Point nextTop;
Point nextBottom;
Font previousTypeface;
Font currentTypeface;
Font nextTypeface;
Color choices[ NUM_COLORS ];
Вероятно, переменную
nextColor было проще найти, чем nextTypeface в листинге
31-53. Такой стиль характеризуется наличием в каждой строке одного полного объявления, включающего тип переменной.
Надо признать, что такой стиль съедает больше экранного пространства — 20 строк,
а не 3, как в первом примере, хотя те три строки и выглядели довольно безобраз- но. Я не могу процитировать ни одного исследования, показывающего, что этот стиль приводит к меньшему количеству ошибок или к лучшему пониманию про- граммы. Однако если Салли попросит меня посмотреть ее код, а объявления дан- ных будут выглядеть, как в первом примере, я отвечу: «Ни за что — это читать невозможно». Если они будут выглядеть, как во втором примере, я скажу: «Гм… Может,
попозже». А если они будут выглядеть, как в третьем примере, я скажу: «Конечно,
с удовольствием!»
Объявляйте переменные рядом с местом их первого использования Еще более предпочтительный вариант, чем объявление всех переменных в одном боль- шом блоке, — это стиль, при котором переменная объявляется рядом с местом ее первого использования. Это уменьшает срок службы и время жизни переменной и позволяет проще выполнить рефакторинг кода на меньшие методы (см. подраздел
«Делайте время жизни переменных как можно короче» раздела 10.4).
Разумно упорядочивайте объявления В листинге 31-54 объявления сгруппи- рованы по типам. Такая группировка обычно имеет смысл, поскольку перемен- ные одинаковых типов часто используются в аналогичных операциях. В других случаях можно упорядочивать их по алфавиту в соответствии с именами перемен- ных. Хотя у алфавитной сортировки много сторонников, мне кажется, затрачи- ваемых не нее усилий она не стоит. Если ваш список переменных настолько дли- нен, что ему помогает алфавитное упорядочиение, ваш метод, вероятно, слишком велик. Разбейте его на части, чтобы создать меньшие по размеру методы с неболь- шим количеством переменных.
В C++ при объявлении указателей располагайте звездочку рядом с именем
переменной или объявляйте типы-указатели Очень часто приходится ви-
ГЛАВА 31 Форматирование и стиль
747
деть объявления указателей, в которых звездочка расположена рядом с типом, как показано в листинге 31-55:
Листинг 31-55. Примеры расположения звездочек в объявлениях указателей (C++)
EmployeeList* employees;
File* inputFile;
При размещении звездочки рядом с именем типа, а не переменной возникает опасность, что в случае добавления нового объявления в той же строке звездочка будет относиться только к первой переменной, хотя визуальное форматирование наводит на мысль, что она применяется ко всем переменным в строке. Вы може- те решить эту проблему, поместив звездочку рядом с именем переменной, а не с именем типа (листинг 31-56):
Листинг 31-56. Пример расположения звездочек в объявлениях указателей (C++)
EmployeeList *employees;
File *inputFile;
Недостаток такого подхода в том, что в этом случае звездочка может казаться частью имени переменной, а это не соответствует действительности. Переменную мож- но применять как со звездочкой, так и без нее.
Наилучший вариант — объявление и использование типа-указателя (листинг 31-57):
Листинг 31-57. Пример правильного применения типа-указателя в объявлениях (C++)
EmployeeListPointer employees;
FilePointer inputFile;
Указанную проблему можно решить, либо требуя объявления всех указателей с помощью типов-указателей (листинг 31-57), либо запрещая размещение более од- ной переменной в строке. Убедитесь, что применяете хотя бы одно из этих реше- ний!
31.6. Размещение комментариев
Хорошо оформленные комментарии могут значительно улуч- шить читаемость программы; плохо — сильно ей повредить.
Делайте в комментарии такой же отступ, как и в
соответствующем ему коде Визуальные отступы вно- сят важный вклад в понимание логической структуры программы, и хорошие ком- ментарии не мешают визуальному выравниванию. Например, что можно сказать о логической структуре метода, приведенного в листинге 31-58?
Листинг 31-58. Пример комментариев с неправильными
отступами (Visual Basic)
For transactionId = 1 To totalTransactions
’ получаем информацию о транзакции
Перекрестная ссылка О других аспектах комментариев см. гла- ву 32.
748
ЧАСТЬ VII Мастерство программирования
GetTransactionType( transactionType )
GetTransactionAmount( transactionAmount )
’ обрабатываем транзакцию в зависимости от ее типа
If transactionType = Transaction_Sale Then
AcceptCustomerSale( transactionAmount )
Else
If transactionType = Transaction_CustomerReturn Then
’ процесс либо оформляет автоматический возврат, либо в случае необходимости
’ ждет подтверждения от менеджера
If transactionAmount >= MANAGER_APPROVAL_LEVEL Then
’ пытаемся получить подтверждение от менеджера, а затем оформляем или отменяем
’ возврат средств в зависимости от полученного подтверждения
GetMgrApproval( isTransactionApproved )
If ( isTransactionApproved ) Then
AcceptCustomerReturn( transactionAmount )
Else
RejectCustomerReturn( transactionAmount )
End If
Else
’ подтверждение менеджера не требуется, поэтому оформляем возврат
AcceptCustomerReturn( transactionAmount )
End If
End If
End If
Next
В этом примере вам не удастся сходу разобраться в логической структуре кода,
так как комментарии полностью скрывают его визуальный формат. Вероятно,
тяжело поверить, что кто-то в здравом уме решит использовать такой стиль от- ступов, но я неоднократно встречал его в профессиональных программах и знаю минимум один учебник, рекомендующий его.
Код, приведенный в листинге 31-59, абсолютно идентичен предыдущему приме- ру из листинга 31-58 за исключением отступов в комментариях.
Листинг 31-59. Пример комментариев с правильными отступами (Visual Basic)
For transactionId = 1 To totalTransactions
‘ получаем информацию о транзакции
GetTransactionType( transactionType )
GetTransactionAmount( transactionAmount )
‘ обрабатываем транзакцию в зависимости от ее типа
If transactionType = Transaction_Sale Then
AcceptCustomerSale( transactionAmount )
741
матирования многострочных вызовов методов работает хорошо при последова- тельном применении.
При переносе строк в управляющем выражении делайте отступ стандар-
тного размера При нехватке места для цикла for, цикла while или оператора if,
перенося строку, сделайте такой же отступ, как и в выражениях внутри цикла или после условия
if. В листинге 31-45 приведены два примера:
Листинг 31-45. Примеры отступов при переносе строк в управляющих
выражениях (Java)
while ( ( pathName[ startPath + position ] != ‘;’ ) &&
В продолжении строки отступ содержит стандартное количество пробелов...
( ( startPath + position ) <= pathName.length() ) ) {
}
for ( int employeeNum = employee.first + employee.offset;
...так же, как и в этом случае.
employeeNum < employee.first + employee.offset + employee.total;
employeeNum++ ) {
}
Приведенный код соответствует критериям, установленным ранее в этой главе. Перенос строки выполняется логично
— в нем всегда есть отступ относительно выражения, кото- рое он продолжает. Дальнейшее выравнивание может вы- полняться как обычно — новая строка занимает всего на несколько пробелов больше, чем исходный вариант. Он так же читаем, как и любой другой код, и его так же просто сопровождать. В некоторых случаях вы могли бы повысить удобочитаемость, на- строив отступы или пробелы, но, рассматривая вопросы тонкой настройки, не забывайте о компромиссе с точки зрения удобства сопровождения.
Не выравнивайте правые части выражений присваивания В первом из- дании этой книги я рекомендовал выравнивать правые части выражений, содер- жащие присваивания (листинг 31-46):
Листинг 31-46. Примеры форматирования в конце строки, используемого
при выравнивании выражений присваивания, — плохая практика (Java)
customerPurchases = customerPurchases + CustomerSales( CustomerID );
customerBill = customerBill + customerPurchases;
totalCustomerBill = customerBill + PreviousBalance( customerID ) +
LateCharge( customerID );
customerRating = Rating( customerID, totalCustomerBill );
Перекрестная ссылка Иногда для сложных условий лучше всего поместить их в логичес- кие функции (см. подраздел
«Упрощение сложных выраже- ний» раздела 19.1).
>
>
742
ЧАСТЬ VII Мастерство программирования
С высоты 10-летнего опыта могу сказать, что, хотя этот стиль может выглядеть привлекательно, он превращается в настоящую головную боль, когда приходится поддерживать выравнивание знаков равенства при изменении имен переменных и прогоне кода через утилиты, заменяющие пробелы знаками табуляции, а знаки табуляции — пробелами. Кроме того, его тяжело сопровождать при перемещении строк между частями программы, в которых применяются разные размеры отступов.
Для обеспечения единообразия с другими принципами отступов, а также учиты- вая вопросы удобства сопровождения, обращайтесь с группами выражений, со- держащими операции присваивания, так же, как вы бы обращались с любыми другими выражениями (листинг 31-47):
Листинг 31-47. Пример использования стандартных отступов для выравнивания
выражений присваивания — хорошая практика (Java)
customerPurchases = customerPurchases + CustomerSales( CustomerID );
customerBill = customerBill + customerPurchases;
totalCustomerBill = customerBill + PreviousBalance( customerID ) +
LateCharge( customerID );
customerRating = Rating( customerID, totalCustomerBill );
При переносе строк в выражениях присваивания применяйте отступы
стандартного размера В листинге 31-47 при переносе строки в третьем при- сваивании используется стандартный размер отступа. Это сделано с той же це- лью, что и отказ от специального форматирования выражений присваивания: из общих соображений читаемости и удобства сопровождения.
Размещение одного оператора на строке
Современные языки, такие как C++ и Java, позволяют располагать несколько опе- раторов на одной строке. Однако когда дело касается этого вопроса, мощь сво- бодного форматирования оборачивается палкой о двух концах. Следующая стро- ка содержит несколько выражений, которые, с точки зрения логики, вполне мо- гут располагаться в отдельных строках:
i = 0; j = 0; k = 0; DestroyBadLoopNames( i, j, k );
Аргументом в защиту размещения нескольких выражений на одной строке может служить факт, что в этом случае требуется меньшее число строк экранного про- странства или бумаги для распечатки, что позволяет одновременно видеть боль- ший объем кода. Это также позволяет сгруппировать взаимосвязанные выраже- ния, а некоторые программисты даже полагают, что так они подсказывают ком- пилятору, как можно оптимизировать код.
Все так, но основания для самоограничения, требующие оставлять не более од- ного оператора в строке, гораздо серьезней.
쐽
Размещение каждого оператора на отдельной строке дает точное представле- ние о сложности программы. При этом не скрывается сложность из-за того,
что сложные операторы выглядят тривиальными. Сложные операторы и вы- глядят сложными, простые — простыми.
ГЛАВА 31 Форматирование и стиль
743
쐽
Размещение нескольких операторов на одной строке не помогает современным компиляторам в оптимизации.
Сегодняшние оптимизирующие компиляторы не нужда- ются в подсказках, сделанных с помощью форматирова- ния (см. ниже).
쐽
Если операторы расположены на отдельных строках, чтение кода происходит сверху вниз, а не сверху вниз и слева направо. При поиске определенной строки у взгляда должна быть возможность придерживаться левого края кода. Он не должен просматривать каждую строку целиком только потому, что в одной строке может быть два оператора.
쐽
При размещении операторов на отдельных строках легко найти синтаксичес- кие ошибки, если компилятор сообщает только номера строк, где они произош- ли. При расположении нескольких операторов на одной строке ее номер ни- чего не скажет о том, какой оператор содержит ошибку.
쐽
При размещении операторов на отдельных строках легко выполнять пошаго- вую отладку кода, используя построчные отладчики. Если строка содержит несколько операторов, отладчик выполнит их все одновременно, и вам при- дется переключиться на ассемблерный листинг для выполнения пошаговой отладки отдельных выражений.
쐽
Когда строка содержит только один оператор, его легко редактировать — можно удалить или временно закомментировать всю строку. Если же на одной стро- ке вы разместили несколько операторов, вам придется выполнять редактиро- вание между остальными операторами.
В C++ избегайте выполнения нескольких операций в одной строке (побоч-
ные эффекты) Побочные эффекты — это последствия выполнения некоторо- го выражения, проявляющиеся в дополнение к основным результатам выполне- ния этого выражения. Так, в C++ оператор
++, расположенный на одной строке с другими операторами, приводит к проявлению побочного эффекта. Присваива- ние значения переменной и применение левой части этого присваивания в ус- ловном операторе также является примером побочного эффекта.
Побочные эффекты снижают читаемость кода. Например, если
n равно 4, что напечатает выражение, приведенное в листинге 31-48?
Листинг 31-48. Пример непредсказуемого побочного эффекта (C++)
PrintMessage( ++n, n + 2 );
4 и 6? Или 5 и 7? А может, 5 и 6? Правильный ответ: «Ни то, ни другое и не тре- тье». Первый аргумент —
++n — равен 5. Но язык C++ не определяет порядок вы- числения условий выражения или аргументов функции. Поэтому компилятор может вычислить второй аргумент,
n + 2, либо до, либо после первого аргумента, и ре- зультат может быть равен
6 или 7 в зависимости от компилятора. В листинге 31-
49 показано, как переписать это выражение, чтобы прояснить свои намерения:
Перекрестная ссылка Об опти- мизации производительности на уровне кода см. главы 25 и 26.
744
ЧАСТЬ VII Мастерство программирования
Листинг 31-49. Пример избавления от непредсказуемого побочного эффекта (C++)
++n;
PrintMessage( n, n + 2 );
Если вы все еще не совсем уверены в том, что побочные эффекты надо выносить в отдельные строки, попробуйте понять, что делает функция, приводимая в лис- тинге 31-50:
Листинг 31-50. Пример слишком большого количества операции в строке (C)
strcpy( char * t, char * s ) {
while ( *++t = *++s )
;
}
Некоторые опытные программисты не видят сложности в этом примере, потому что эта функция им знакома. Они смотрят на нее и говорят: «Это функция
strcpy()».
Однако в нашем случае это не совсем
strcpy(). Она содержит ошибку. Если вы ска- зали «Это
strcpy()», увидев данный код, вы узнали код, а не прочитали его. В такую же ситуацию вы попадаете при отладке программы: код, на который вы не обрати- ли внимания, потому что «узнали», а не прочли его, может содержать ошибку, по- иск которой займет гораздо больше времени, чем она этого заслуживает.
Фрагмент, показанный в листинге 31-51, функционально идентичен первому ва- рианту и гораздо удобней для чтения:
Листинг 31-51. Пример читаемого количества операций в каждой строке (C)
strcpy( char * t, char * s ) {
do {
++t;
++s;
*t = *s;
}
while ( *t != ‘\0’ );
}
В этом переформатированном коде ошибка очевидна. Конечно,
t и s инкремен- тируются до того, как
*s будет скопирована в *t. Первый символ пропускается.
Второй пример выглядит продуманней первого, хотя операции, выполняемые во втором примере, идентичны первому. Причина такого впечатления в том, что во втором варианте не скрывается сложность выполняемых действий.
Рост производительности также не оправдывает размеще- ния нескольких операций на одной строке. Поскольку обе функции
strcpy() логически эквивалентны, можно ожидать,
что компилятор сгенерирует для них идентичный код. Од- нако при профилировании обеих функций выяснилось, что для копирования
5 000 000 строк первой функции понадобилось 4,81 секунды, а второй —4,35.
В нашем случае «умная» версия показала снижение скорости на 11%, что делает ее гораздо менее умной. Результаты могут изменяться от компилятора к компиля-
Перекрестная ссылка О на- стройке кода см. главы 25 и 26.
ГЛАВА 31 Форматирование и стиль
745
тору, но в целом они свидетельствуют о том, что пока вы не измерили прирост производительности, следует сначала стремиться к ясности и корректности, а уж затем — к производительности.
Даже если вы легко читаете выражения с побочными эффектами, пожалейте тех,
кому придется разбираться с вашим кодом. Большинству программистов нужно дважды подумать, чтобы понять выражения с побочными эффектами. Позвольте им использовать мозговые клетки для осмысления более общих вопросов рабо- ты вашего кода, а не синтаксических деталей конкретного выражения.
Размещение объявлений данных
Располагайте каждое объявление данных в отдельной
строке Как показали предыдущие примеры, каждому объявлению данных надо выделять отдельную строку. Тог- да проще дописывать комментарии к каждому объявлению
— ведь каждое расположено в отдельной строке. Проще ис- правлять объявления, поскольку все они изолированы друг от друга. Проще находить конкретные переменные, так как можно просканировать одну колонку, а не читать каждую строчку. Проще искать и исправлять синтаксические ошибки — строка, указанная компилятором, содер- жит только одно объявление.
А ну-ка, скажите быстренько: какой тип имеет переменная
currentBottom в объяв- лении данных, приведенном в листинге 31-52?
Листинг 31-52. Пример скопления нескольких объявлений
переменных в одной строке (C++)
int rowIndex, columnIdx; Color previousColor, currentColor, nextColor; Point previousTop, previousBottom, currentTop, currentBottom, nextTop, nextBottom; Font previousTypeface, currentTypeface, nextTypeface; Color choices[ NUM_COLORS ];
Это, конечно, крайний случай, однако он не так уж далек от гораздо более рас- пространенного стиля, показанного в листинге 31-53:
Листинг 31-53. Пример скопления нескольких объявлений
переменных в одной строке (C++)
int rowIndex, columnIdx;
Color previousColor, currentColor, nextColor;
Point previousTop, previousBottom, currentTop, currentBottom, nextTop, nextBottom;
Font previousTypeface, currentTypeface, nextTypeface;
Color choices[ NUM_COLORS ];
Это не такой уж и редкий стиль объявления переменных, а конкретную перемен- ную все так же тяжело найти, поскольку все объявления свалены в кучу. Тип пере- менной тоже тяжело выяснить. А теперь скажите, какой тип имеет
nextColor в листинге 31-54?
Перекрестная ссылка О доку- ментировании объявлений дан- ных см. подраздел «Комменти- рование объявлений данных»
раздела 32.5. Об использовании данных см. в главах 10-13.
746
ЧАСТЬ VII Мастерство программирования
Листинг 31-54. Пример читаемого кода, достигнутого благодаря размещению
только одной переменной в каждой строке (C++)
int rowIndex;
int columnIdx;
Color previousColor;
Color currentColor;
Color nextColor;
Point previousTop;
Point previousBottom;
Point currentTop;
Point currentBottom;
Point nextTop;
Point nextBottom;
Font previousTypeface;
Font currentTypeface;
Font nextTypeface;
Color choices[ NUM_COLORS ];
Вероятно, переменную
nextColor было проще найти, чем nextTypeface в листинге
31-53. Такой стиль характеризуется наличием в каждой строке одного полного объявления, включающего тип переменной.
Надо признать, что такой стиль съедает больше экранного пространства — 20 строк,
а не 3, как в первом примере, хотя те три строки и выглядели довольно безобраз- но. Я не могу процитировать ни одного исследования, показывающего, что этот стиль приводит к меньшему количеству ошибок или к лучшему пониманию про- граммы. Однако если Салли попросит меня посмотреть ее код, а объявления дан- ных будут выглядеть, как в первом примере, я отвечу: «Ни за что — это читать невозможно». Если они будут выглядеть, как во втором примере, я скажу: «Гм… Может,
попозже». А если они будут выглядеть, как в третьем примере, я скажу: «Конечно,
с удовольствием!»
Объявляйте переменные рядом с местом их первого использования Еще более предпочтительный вариант, чем объявление всех переменных в одном боль- шом блоке, — это стиль, при котором переменная объявляется рядом с местом ее первого использования. Это уменьшает срок службы и время жизни переменной и позволяет проще выполнить рефакторинг кода на меньшие методы (см. подраздел
«Делайте время жизни переменных как можно короче» раздела 10.4).
Разумно упорядочивайте объявления В листинге 31-54 объявления сгруппи- рованы по типам. Такая группировка обычно имеет смысл, поскольку перемен- ные одинаковых типов часто используются в аналогичных операциях. В других случаях можно упорядочивать их по алфавиту в соответствии с именами перемен- ных. Хотя у алфавитной сортировки много сторонников, мне кажется, затрачи- ваемых не нее усилий она не стоит. Если ваш список переменных настолько дли- нен, что ему помогает алфавитное упорядочиение, ваш метод, вероятно, слишком велик. Разбейте его на части, чтобы создать меньшие по размеру методы с неболь- шим количеством переменных.
В C++ при объявлении указателей располагайте звездочку рядом с именем
переменной или объявляйте типы-указатели Очень часто приходится ви-
ГЛАВА 31 Форматирование и стиль
747
деть объявления указателей, в которых звездочка расположена рядом с типом, как показано в листинге 31-55:
Листинг 31-55. Примеры расположения звездочек в объявлениях указателей (C++)
EmployeeList* employees;
File* inputFile;
При размещении звездочки рядом с именем типа, а не переменной возникает опасность, что в случае добавления нового объявления в той же строке звездочка будет относиться только к первой переменной, хотя визуальное форматирование наводит на мысль, что она применяется ко всем переменным в строке. Вы може- те решить эту проблему, поместив звездочку рядом с именем переменной, а не с именем типа (листинг 31-56):
Листинг 31-56. Пример расположения звездочек в объявлениях указателей (C++)
EmployeeList *employees;
File *inputFile;
Недостаток такого подхода в том, что в этом случае звездочка может казаться частью имени переменной, а это не соответствует действительности. Переменную мож- но применять как со звездочкой, так и без нее.
Наилучший вариант — объявление и использование типа-указателя (листинг 31-57):
Листинг 31-57. Пример правильного применения типа-указателя в объявлениях (C++)
EmployeeListPointer employees;
FilePointer inputFile;
Указанную проблему можно решить, либо требуя объявления всех указателей с помощью типов-указателей (листинг 31-57), либо запрещая размещение более од- ной переменной в строке. Убедитесь, что применяете хотя бы одно из этих реше- ний!
31.6. Размещение комментариев
Хорошо оформленные комментарии могут значительно улуч- шить читаемость программы; плохо — сильно ей повредить.
Делайте в комментарии такой же отступ, как и в
соответствующем ему коде Визуальные отступы вно- сят важный вклад в понимание логической структуры программы, и хорошие ком- ментарии не мешают визуальному выравниванию. Например, что можно сказать о логической структуре метода, приведенного в листинге 31-58?
Листинг 31-58. Пример комментариев с неправильными
отступами (Visual Basic)
For transactionId = 1 To totalTransactions
’ получаем информацию о транзакции
Перекрестная ссылка О других аспектах комментариев см. гла- ву 32.
748
ЧАСТЬ VII Мастерство программирования
GetTransactionType( transactionType )
GetTransactionAmount( transactionAmount )
’ обрабатываем транзакцию в зависимости от ее типа
If transactionType = Transaction_Sale Then
AcceptCustomerSale( transactionAmount )
Else
If transactionType = Transaction_CustomerReturn Then
’ процесс либо оформляет автоматический возврат, либо в случае необходимости
’ ждет подтверждения от менеджера
If transactionAmount >= MANAGER_APPROVAL_LEVEL Then
’ пытаемся получить подтверждение от менеджера, а затем оформляем или отменяем
’ возврат средств в зависимости от полученного подтверждения
GetMgrApproval( isTransactionApproved )
If ( isTransactionApproved ) Then
AcceptCustomerReturn( transactionAmount )
Else
RejectCustomerReturn( transactionAmount )
End If
Else
’ подтверждение менеджера не требуется, поэтому оформляем возврат
AcceptCustomerReturn( transactionAmount )
End If
End If
End If
Next
В этом примере вам не удастся сходу разобраться в логической структуре кода,
так как комментарии полностью скрывают его визуальный формат. Вероятно,
тяжело поверить, что кто-то в здравом уме решит использовать такой стиль от- ступов, но я неоднократно встречал его в профессиональных программах и знаю минимум один учебник, рекомендующий его.
Код, приведенный в листинге 31-59, абсолютно идентичен предыдущему приме- ру из листинга 31-58 за исключением отступов в комментариях.
Листинг 31-59. Пример комментариев с правильными отступами (Visual Basic)
For transactionId = 1 To totalTransactions
‘ получаем информацию о транзакции
GetTransactionType( transactionType )
GetTransactionAmount( transactionAmount )
‘ обрабатываем транзакцию в зависимости от ее типа
If transactionType = Transaction_Sale Then
AcceptCustomerSale( transactionAmount )