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

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


Скачать 5.88 Mb.
НазваниеРуководство по стилю программирования и конструированию по
АнкорСовершенный код
Дата31.03.2023
Размер5.88 Mb.
Формат файлаpdf
Имя файлаСовершенный код. Мастер-класс. Стив Макконнелл.pdf
ТипРуководство
#1028502
страница90 из 106
1   ...   86   87   88   89   90   91   92   93   ...   106
ГЛАВА 31 Форматирование и стиль
739
totalBill = totalBill + customerPurchases[ customerID ]
+ SalesTax( customerPurchases[ customerID ] );
Хотя такой стиль не приведет к появлению синтаксической ошибки при повиса#
нии операторов
&& или +, он сильно упрощает поиск операторов по левой гра#
нице столбца — там, где текст выровнен, а не по правой — где край текста неров#
ный. Его дополнительное преимущество в том, что он позволяет пояснить струк#
туру операций, как показано в листинге 31#39.
Листинг 31-39. Пример стиля, поясняющего смысл сложной операции (Java)
totalBill = totalBill
+ customerPurchases[ customerID ]
+ CitySalesTax( customerPurchases[ customerID ] )
+ StateSalesTax( customerPurchases[ customerID ] )
+ FootballStadiumTax()
 SalesTaxExemption( customerPurchases[ customerID ] );
Располагайте сильно связанные элементы вместе Разбивая строку на части,
оставляйте рядом взаимосвязанные элементы: обращения к массиву, аргументы ме#
тода и т. д. Пример, приведенный в листинге 31#40, демонстрирует плохой вариант:
Листинг 31-40. Пример неправильного
разбиения строки (Java)
customerBill = PreviousBalance( paymentHistory[ customerID ] ) + LateCharge(
paymentHistory[ customerID ] );
Надо признать, что такой разрыв строки соответствует принципу подчеркивания очевидности незавершенных выражений, но это сделано так, что надобности зат#
рудняет чтение выражения. Можно придумать ситуации, когда такое разбиение будет оправданным, но это не тот случай. Лучше оставить все обращения к мас#
сиву на одной строке. Листинг 31#41 показывает лучшее форматирование:
Листинг 31-41. Пример правильного разбиения строки (Java)
customerBill = PreviousBalance( paymentHistory[ customerID ] ) +
LateCharge( paymentHistory[ customerID ] );
При переносе строк в вызове метода используйте отступ стандартно'
го размера Если в циклах и условных выражениях вы обычно делаете отступ в три пробела, то при переносе строки в вызове метода тоже сделайте отступ в три пробела. Листинг 31#42 содержит несколько примеров:
Листинг 31-42. Примеры переноса строк в вызовах методов
с применением стандартного размера отступов (Java)
DrawLine( window.north, window.south, window.east, window.west,
currentWidth, currentAttribute );
SetFontAttributes( faceName[ fontId ], size[ fontId ], bold[ fontId ],
italic[ fontId ], syntheticAttribute[ fontId ].underline,
syntheticAttribute[ fontId ].strikeout );

740
ЧАСТЬ VII Мастерство программирования
Одной из альтернатив такому подходу служит выравнивание перенесенных строк под первым аргументом, как показано в листинге 31#43:
Листинг 31-43. Пример отступов при переносе строк в вызове методов,
позволяющих выделить имена методов (Java)
DrawLine( window.north, window.south, window.east, window.west,
currentWidth, currentAttribute );
SetFontAttributes( faceName[ fontId ], size[ fontId ], bold[ fontId ],
italic[ fontId ], syntheticAttribute[ fontId ].underline,
syntheticAttribute[ fontId ].strikeout );
С эстетической точки зрения, в сравнении с первым вариантом этот код выгля#
дит несколько неровно. Кроме того, этот подход тяжело сопровождать, так как имена методов и аргументов могут изменяться. Большинство программистов со временем склоняются к использованию первого стиля.
Упростите поиск конца строки с продолжением Одна из проблем пока#
занного выше подхода — сложность поиска конца каждой строки. Альтернативой может служить размещение каждого элемента на отдельной строке и выделение окончания всей группы с помощью закрывающей скобки (листинг 31#44).
Листинг 31-44. Пример форматирования переноса строк в вызовах методов,
в котором каждый аргумент размещается на отдельной строке (Java)
DrawLine(
window.north,
window.south,
window.east,
window.west,
currentWidth,
currentAttribute
);
SetFontAttributes(
faceName[ fontId ],
size[ fontId ],
bold[ fontId ],
italic[ fontId ],
syntheticAttribute[ fontId ].underline,
syntheticAttribute[ fontId ].strikeout
);
Ясно, что такой подход требует много места. Однако если аргументами функции являются длинные названия полей объектов или имена указателей, например, такие,
как два последних в приведенном примере, размещение одного аргумента в строке существенно улучшает читаемость. Знаки
); в конце блока делают заметным его окончание. Вам также не придется переформатировать код при добавлении па#
раметра — вы просто добавите новую строку.
На практике только небольшая часть методов нуждается в разбиении на строки.
Остальные можно располагать на одной строке. Любой из трех вариантов фор#

ГЛАВА 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++ при объявлении указателей располагайте звездочку рядом с именем
переменной или объявляйте типы'указатели Очень часто приходится ви#

1   ...   86   87   88   89   90   91   92   93   ...   106


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