Академический Документы
Профессиональный Документы
Культура Документы
Стив Макконнелл - Совершенный код, 2-е издание (мастер-класс) - 2010
Стив Макконнелл - Совершенный код, 2-е издание (мастер-класс) - 2010
CODE
COMPLETE
Second Edition
Стив Макконнелл
Совершенный
КОД
МАСТЕР-КЛАСС
2010
УДК 004.45
ББК 32.973.26–018.2
М15
Макконнелл С.
М15 Совершенный код. Мастер#класс / Пер. с англ. — М. : Издательство «Русская
редакция», 2010. — 896 стр. : ил.
ISBN 978-5750200641
Более 10 лет первое издание этой книги считалось одним из лучших практических
руководств по программированию. Сейчас эта книга полностью обновлена с учетом
современных тенденций и технологий и дополнена сотнями новых примеров, ил-
люстрирующих искусство и науку программирования. Опираясь на академические
исследования, с одной стороны, и практический опыт коммерческих разработок ПО —
с другой, автор синтезировал из самых эффективных методик и наиболее эффектив-
ных принципов ясное прагматичное руководство. Каков бы ни был ваш профессио-
нальный уровень, с какими бы средствами разработками вы ни работали, какова бы
ни была сложность вашего проекта, в этой книге вы найдете нужную информацию,
она заставит вас размышлять и поможет создать совершенный код.
УДК 004.45
ББК 32.973.26–018.2
Содержание
Предисловие .................................................................................................................... XIII
Благодарности ................................................................................................................ XIX
Контрольные списки ...................................................................................................... XXI
Часть IV Операторы
14 Организация последовательного кода .............................................................. 338
14.1. Операторы, следующие в определенном порядке ........................................... 338
14.2. Операторы, следующие в произвольном порядке ........................................... 342
15 Условные операторы ............................................................................................. 346
15.1. Операторы if ........................................................................................................................................... 346
15.2. Операторы case ................................................................................................................................... 353
16 Циклы ........................................................................................................................ 359
16.1. Выбор типа цикла ............................................................................................................................. 359
16.2. Управление циклом ........................................................................................................................ 365
16.3. Простое создание цикла — изнутри наружу ......................................................... 378
16.4. Соответствие между циклами и массивами ........................................................... 379
17 Нестандартные управляющие структуры ......................................................... 382
17.1. Множественные возвраты из метода ............................................................................ 382
17.2. Рекурсия ...................................................................................................................................................... 385
17.3. Оператор goto ....................................................................................................................................... 389
17.4. Перспективы нестандартных управляющих структур ................................ 401
18 Табличные методы ................................................................................................. 404
18.1. Основные вопросы применения табличных методов ................................ 405
18.2. Таблицы с прямым доступом ................................................................................................. 406
18.3. Таблицы с индексированным доступом .................................................................... 418
18.4. Таблицы со ступенчатым доступом ................................................................................ 419
18.5. Другие примеры табличного поиска ............................................................................ 422
19 Общие вопросы управления ................................................................................ 424
19.1. Логические выражения ............................................................................................................... 424
19.2. Составные операторы (блоки) ............................................................................................ 436
19.3. Пустые выражения ........................................................................................................................... 437
19.4. Укрощение опасно глубокой вложенности ........................................................... 438
X Содержание
Опытные программисты
Эта книга окажется полезной опытным программистам, желающим получить всесторон#
нее и удобное руководство по разработке ПО. Так как эта книга фокусируется на констру#
ировании — самой известной части жизненного цикла ПО, — описанные в ней методики
будут понятны и программистам, имеющим соответствующее образование, и программи#
стам#самоучкам.
Технические лидеры
Многие технические лидеры используют первое издание этой книги для обучения менее
опытных членов своих групп. Вы также можете использовать эту книгу для восполнения
пробелов в своих знаниях. Если вы — опытный программист, то, наверное, согласитесь
не со всеми моими выводами (обратное было бы странным), но, если вы прочитаете весь
материал и обдумаете каждый поднятый вопрос, едва ли какая#то возникшая проблема
конструирования окажется для вас новой.
XIV Предисловие
Программисты-самоучки
Если вы не имеете специального образования, вы не одиноки. Ежегодно программистами
становятся около 50 000 человек (BLS, 2004, Hecker 2004), однако число дипломов, вруча#
емых ежегодно в нашей отрасли, составляет лишь около 35 000 (NCES, 2002). Легко прий#
ти к выводу, что многие программисты изучают разработку ПО самостоятельно. Програм#
мисты#самоучки встречаются среди инженеров, бухгалтеров, ученых, преподавателей, вла#
дельцев малого бизнеса и представителей других профессий, которые занимаются про#
граммированием в рамках своей работы, но не всегда считают себя программистами. Каким
бы ни было ваше программистское образование, в этом руководстве вы найдете инфор#
мацию об эффективных методиках программирования.
Студенты
В отличие от программистов, которые обладают опытом, но не могут похвастаться специ#
альным обучением, недавние выпускники вузов часто имеют обширные теоретические знания,
но плохо владеют практическими ноу#хау, связанными с созданием реальных программ.
Передача практических навыков хорошего кодирования зачастую идет медленно, в риту#
альных танцах архитекторов ПО, лидеров проектов, аналитиков и более опытных програм#
мистов. Еще чаще эти навыки приобретаются программистами в результате собственных
проб и ошибок. Эта книга — альтернатива традиционным неспешным интеллектуальным
ритуалам. В ней собраны полезные советы и эффективные стратегии разработки, которые
ранее можно было узнать главным образом только непосредственно у других людей. Это
трамплин для студентов, переходящих из академической среды в профессиональную.
Примеры написаны на нескольких языках, потому что освоение более одного языка час#
то является поворотным пунктом в карьере профессионального программиста. Как толь#
ко программист понимает, что принципы программирования не зависят от синтаксиса
конкретного языка, он начинает приобретать знания, позволяющие достичь новых высот
качества и производительности труда.
Чтобы как можно более облегчить бремя применения нескольких языков, я избегал ред#
ких возможностей языков, кроме тех фрагментов, в которых именно они и обсуждаются.
Вам не нужно понимать каждый нюанс фрагментов кода, чтобы понять их суть. Если вы
сосредоточитесь на обсуждаемых моментах, вы сможете читать код на любом языке. Что#
бы сделать вашу задачу еще легче, я пояснил важные части примеров.
Доступ к другим источникам информации В данном руководстве приводятся под#
робные сведения о конструировании ПО, но едва ли это последнее слово. В разделах «До#
полнительные ресурсы» я указал другие книги и статьи, которые вы можете прочитать, если
заинтересуетесь той или иной темой.
Web'сайт книги Обновленные контрольные списки, списки
http://cc2e.com/1234 книг и журнальных статей, Web#ссылки и другую информацию
можно найти на Web#сайте cc2e.com. Для получения информации,
связанной с «Code Complete, 2d ed.», введите в браузере cc2e.com/
и четырехзначное число, пример которого показан слева. Читая книгу, вы много раз на#
толкнетесь на такие ссылки.
Конструирование важно
Другая причина того, что конструирование игнорируется учеными и авторами, заключа#
ется в ошибочной идее, что в сравнении с другими процессами разработки ПО констру#
ирование является относительно механическим процессом, допускающим мало возмож#
ностей улучшения. Ничто не может быть дальше от истины.
На конструирование кода обычно приходятся около 65% работы в небольших и 50% в
средних проектах. Во время конструирования допускаются около 75% ошибок в неболь#
Предисловие XVII
ших проектах и от 50 до 75% в средних и крупных. Очевидно, что любой процесс, связан#
ный с такой долей ошибок, можно значительно улучшить (подробнее эти статистические
данные рассматриваются в главе 27).
Некоторые авторы указывают, что, хотя ошибки конструирования и составляют высокий
процент от общего числа ошибок, их обычно дешевле исправлять, чем ошибки в требо#
ваниях или архитектуре, поэтому они менее важны. Утверждение, что ошибки конструи#
рования дешевле исправлять, верно, но вводит в заблуждение, потому что стоимость не#
исправленной ошибки конструирования может быть крайней высокой. Ученые обнару#
жили, что одними из самых дорогих ошибок в истории, приведшими к убыткам в сотни
миллионов долларов, были мелкие ошибки кодирования (Weinberg, 1983; SEN, 1990). Не#
высокая стоимость исправления ошибок не подразумевает, что их исправление можно
считать низкоприоритетной задачей.
Ирония ослабления внимания к конструированию состоит в том, что конструирование
— единственный процесс, который выполняется всегда. Требования можно предположить,
а не разработать, архитектуру можно обрисовать в самых общих чертах, а тестирование
можно сократить или вообще опустить. Но если вы собираетесь написать программу, из#
бежать конструирования не удастся, и это делает конструирование на редкость плодотвор#
ной областью улучшения методик разработки.
К читателям
Я буду рад получить от вас вопросы по темам, обсуждаемым в этой книге, сообщения об
обнаруженных ошибках, комментарии и предложения. Для связи со мной используйте адрес
stevemcc@construx.com или мой Web#сайт www.stevemcconnell.com.
Беллвью, штат Вашингтон
30 мая 2004 года
XVIII Предисловие
Ключевой момент
Достоверные данные
Ужасный код
ГЛАВА 1 Добро пожаловатьв мир конструирования ПО! XIX
Благодарности
Книги никогда не создаются в одиночку (по крайней мере это относится ко всем
моим книгам), а работа над вторым изданием — еще более коллективное пред#
приятие.
Мне хотелось бы поблагодарить всех, кто принял участие в обзоре данной книги:
это Хакон Агустссон (Hбkon Бgъstsson), Скотт Эмблер (Scott Ambler), Уилл Барнс
(Will Barns), Уильям Д. Бартоломью (William D. Bartholomew), Ларс Бергстром (Lars
Bergstrom), Ян Брокбанк (Ian Brockbank), Брюс Батлер (Bruce Butler), Джей Цин#
котта (Jay Cincotta), Алан Купер (Alan Cooper), Боб Коррик (Bob Corrick), Эл Кор#
вин (Al Corwin), Джерри Девилль (Jerry Deville), Джон Ивз (Jon Eaves), Эдвард Эс#
трада (Edward Estrada), Стив Гоулдстоун (Steve Gouldstone), Оуэйн Гриффитс (Owain
Griffiths), Мэтью Харрис (Matthew Harris), Майкл Ховард (Michael Howard), Энди
Хант (Andy Hunt), Кевин Хатчисон (Kevin Hutchison), Роб Джаспер (Rob Jasper),
Стивен Дженкинс (Stephen Jenkins), Ральф Джонсон (Ralph Johnson) и его группа
разработки архитектуры ПО из Иллинойского университета, Марек Конопка (Marek
Konopka), Джефф Лэнгр (Jeff Langr), Энди Лестер (Andy Lester), Митика Ману (Mitica
Manu), Стив Маттингли (Steve Mattingly), Гарет Маккоан (Gareth McCaughan), Ро#
берт Макговерн (Robert McGovern), Скотт Мейерс (Scott Meyers), Гарет Морган
(Gareth Morgan), Мэтт Пелокин (Matt Peloquin), Брайан Пфладж (Bryan Pflug),
Джеффри Рихтер (Jeffrey Richter), Стив Ринн (Steve Rinn), Даг Розенберг (Doug
Rosenberg), Брайан Сен#Пьер (Brian St. Pierre), Диомидис Спиннелис (Diomidis
Spinellis), Мэтт Стивенс (Matt Stephens), Дэйв Томас (Dave Thomas), Энди Томас#
Крамер (Andy Thomas#Cramer), Джон Влиссидес (John Vlissides), Павел Возенилек
(Pavel Vozenilek), Денни Уиллифорд (Denny Williford), Джек Вули (Jack Woolley) и
Ди Зомбор (Dee Zsombor).
Сотни читателей прислали комментарии к первому изданию этой книги, и еще
больше — ко второму. Спасибо всем, кто потратил время, чтобы поделиться в той
или иной форме своим мнением.
Хочу особо поблагодарить рецензентов из Construx Software, которые провели фор#
мальную инспекцию всей рукописи: это Джейсон Хиллз (Jason Hills), Брейди Хон#
сингер (Bradey Honsinger), Абдул Низар (Abdul Nizar), Том Рид (Tom Reed) и Па#
мела Перро (Pamela Perrott). Я был поистине удивлен тщательностью их обзора,
особенно если учесть, сколько глаз изучило эту книгу до того, как они начали
работать с ней. Спасибо также Брейди, Джейсону и Памеле за помощь в создании
Web#сайта cc2e.com.
Мне было очень приятно работать с Девон Масгрейв (Devon Musgrave) — редак#
тором этой книги. Я работал со многими прекрасными редакторами в других
проектах, но даже на их фоне Девон выделяется добросовестностью и легким
XX Благодарности
Контрольные списки
Требования ................................................................................................................................................................................................................. 42
Архитектура ............................................................................................................................................................................................................ 54
Предварительные условия ....................................................................................................................................................................... 59
Основные методики конструирования ................................................................................................................................... 69
Проектирование при конструировании .............................................................................................................................. 122
Качество классов ............................................................................................................................................................................................ 157
Высококачественные методы ........................................................................................................................................................... 185
Защитное программирование ......................................................................................................................................................... 211
Процесс программирования с псевдокодом .................................................................................................................. 233
Общие вопросы использования данных ............................................................................................................................. 257
Именование переменных ..................................................................................................................................................................... 288
Основные данные .......................................................................................................................................................................................... 316
Применение необычных типов данных .............................................................................................................................. 343
Организация последовательного кода .................................................................................................................................. 353
Использование условных операторов ................................................................................................................................... 365
Циклы .......................................................................................................................................................................................................................... 388
Нестандартные управляющие структуры ........................................................................................................................... 410
Табличные методы ........................................................................................................................................................................................ 429
Вопросы по управляющим структурам ................................................................................................................................. 459
План контроля качества ......................................................................................................................................................................... 476
Эффективное парное программирование ........................................................................................................................ 484
Эффективные инспекции ..................................................................................................................................................................... 491
Тесты ............................................................................................................................................................................................................................. 532
Отладка ...................................................................................................................................................................................................................... 559
Разумные причины выполнения рефакторинга ......................................................................................................... 570
Виды рефакторинга ..................................................................................................................................................................................... 577
Безопасный рефакторинг ..................................................................................................................................................................... 584
Стратегии оптимизации кода .......................................................................................................................................................... 607
Методики оптимизации кода ........................................................................................................................................................... 642
Управление конфигурацией .............................................................................................................................................................. 669
Интеграция ............................................................................................................................................................................................................ 707
Инструменты программирования ............................................................................................................................................... 724
Форматирование ............................................................................................................................................................................................. 773
Самодокументирующийся код ........................................................................................................................................................ 780
Хорошие методики комментирования .................................................................................................................................. 816
Часть I
ОСНОВЫ РАЗРАБОТКИ ПО
Г Л А В А 1
Содержание
http://cc2e.com/0178
1.1. Что такое конструирование ПО?
Связанные темы
Кому следует прочитать эту книгу? (см. предисловие)
блочное тестирование;
интеграционное тестирование;
интеграция;
тестирование системы;
корректирующее сопровождение.
Если вы работали над неформальными проектами, то можете подумать, что этот
список весьма бюрократичен. Если вы работали над слишком формальными про#
ектами, вы это знаете! Достичь баланса между слишком слабым и слишком силь#
ным формализмом нелегко — об этом мы еще поговорим.
Если вы учились программировать самостоятельно или работали преимущественно
над неформальными проектами, вы, возможно, многие действия по созданию
продукта объединили в одну категорию «программирование». Если вы работаете
над неформальными проектами, то скорее всего, думая о создании ПО, вы пред#
ставляете себе тот процесс, который ученые называют «конструированием».
Такое интуитивное представление о «конструировании» довольно верно, однако
оно страдает от недостатка перспективы. Поэтому конструирование целесообразно
рассматривать в контексте других процессов: это помогает сосредоточиться на
задачах конструирования и уделять адекватное внимание другим важным действи#
ям, к нему не относящимся. На рис. 1#1 показано место конструирования среди
других процессов разработки ПО.
ное тестирование и другие процессы. Если бы эта книга была посвящена всем ас#
пектам разработки ПО, в ней было бы приведено сбалансированное обсуждение
всех процессов. Однако это руководство по методам конструирования, так что
остальных тем я почти не касаюсь. Если бы эта книга была собакой, она тщатель#
но принюхивалась бы к конструированию, виляла хвостом перед проектирова#
нием и тестированием и лаяла на прочие процессы.
Иногда конструирование называют «кодированием» или «программированием».
«Кодирование» кажется мне в данном случае не лучшим термином, так как он
подразумевает механическую трансляцию разработанного плана в команды язы#
ка программирования, тогда как конструирование вовсе не механический про#
цесс и часто связано с творчеством и анализом. Смысл слов «программирование»
и «конструирование» кажется мне похожим, и я буду использовать их как равно#
правные.
На рис. 1#1 разработка ПО была изображена в «плоском» виде; более точным от#
ражением содержания этой книги является рис. 1#2.
1
Голдберг, Рубен Лушес («Руб») [Goldberg, «Rube» (Reuben Lucius)] (1883–1970) — карика#
турист, скульптор. Известен своими карикатурами, в которых выдуманное им сложное обору#
дование («inventions») выполняет примитивные и никому не нужные операции. Лауреат Пулит#
церовской премии 1948 г. за политические карикатуры. — Прим. перев.
ГЛАВА 1 Добро пожаловать в мир конструирования ПО! 7
Ключевые моменты
Конструирование — главный этап разработки ПО, без которого не обходится
ни один проект.
Основные этапы конструирования: детальное проектирование, кодирование,
отладка, интеграция и тестирование приложения разработчиками (блочное
тестирование и интеграционное тестирование).
Конструирование часто называют «кодированием» и «программированием».
От качества конструирования во многом зависит качество ПО.
В конечном счете ваша компетентность в конструировании ПО определяет то,
насколько хороший вы программист. Совершенствованию ваших навыков и
посвящена оставшаяся часть этой книги.
8 ЧАСТЬ I Основы разработки ПО
Г Л А В А 2
Метафоры, позволяющие
лучше понять разработку ПО
Содержание
http://cc2e.com/0278
2.1. Важность метафор
2.2. Как использовать метафоры?
2.3. Популярные метафоры, характеризующие разработку ПО
Связанная тема
Эвристика при проектировании: подраздел «Проектирование — эвристический
процесс» в разделе 5.1
Терминология компьютерных наук — одна из самых красочных. Действительно,
в какой еще области существуют стерильные комнаты с тщательно контролируе#
мой температурой, заполненные вирусами, троянскими конями, червями, жучка#
ми и прочей живностью и нечистью?
Все эти яркие метафоры описывают специфические аспекты мира программиро#
вания. Более общие явления характеризуются столь же красочными метафорами,
позволяющих лучше понять процесс разработки ПО.
Остальная часть книги не зависит от обсуждения метафор в этой главе. Можете
пропустить ее, если хотите быстрее добраться до практических советов. Если хотите
яснее представлять разработку ПО, читайте дальше.
Построить дом сложнее, и плохое проектирование при этом приводит к куда более
серьезным последствиям. Сначала вы должны решить, какой тип здания вы хоти#
те построить, что аналогично определению проблемы при разработке ПО. Затем
вы с архитектором должны разработать и утвердить общий план, что похоже на
разработку архитектуры. Далее вы чертите подробные чертежи и нанимаете бри#
гаду строителей — это аналогично детальному проектированию ПО. Вы готовите
стройплощадку, закладываете фундамент, создаете каркас дома, обшиваете его,
кроете крышу и проводите в дом все коммуникации — это похоже на конструи#
рование ПО. Когда строительство почти завершено, в дело вступают ландшафт#
ные дизайнеры, маляры и декораторы, делающие дом максимально удобным и
привлекательным. Это напоминает оптимизацию ПО. Наконец, на протяжении
всего строительства вас посещают инспекторы, проверяющие стройплощадку, фун#
дамент, электропроводку и все, что можно проверить. При разработке ПО этому
соответствуют обзоры и инспекция проекта.
И в строительстве, и в программировании увеличение сложности и масштаба
проекта сопровождается ростом цены ошибок. Конечно, для создания дома нуж#
ны довольно дорогие материалы, однако главной статьей расходов является
оплата труда рабочих. Перемещение стены на 15 см обойдется дорого не потому,
что при этом будет потрачено много гвоздей, а потому, что вам придется опла#
тить дополнительное время работы строителей. Чтобы не тратить время на ис#
правление ошибок, которых можно избежать, вы должны как можно лучше вы#
полнить проектирование (рис. 2#4). Материалы, необходимые для создания про#
граммного продукта, стоят дешевле, чем стройматериалы, однако затраты на ра#
бочую силу в обоих случаях примерно одинаковы. Изменение формата отчета
обходится ничуть не дешевле, чем перемещение стены дома, потому что главным
компонентом затрат в обоих случаях является время людей.
ГЛАВА 2 Метафоры, позволяющие лучше понять разработку ПО 17
Комбинирование метафор
Метафоры имеют эвристическую, а не алгоритмическую природу, поэтому
они не исключают друг друга. Вы можете использовать и метафору ак#
креции, и метафору конструирования. Если хотите, можете представлять
разработку ПО как написание письма, комбинируя эту метафору с вождением
автомобиля, охотой на оборотней или образом динозавра, увязшего в смоляной
луже. Используйте любые метафоры или их комбинации, которые стимулируют
ваше мышление или помогают общаться с другими членами группы.
Использование метафор — дело тонкое. Чтобы метафора привела вас к ценным
эвристическим догадкам, вы должны ее расширить. Но если ее расширить черес#
чур или в неверном направлении, она может ввести в заблуждение. Как и любой
мощный инструмент, метафоры можно использовать неверным образом, однако
благодаря своей мощи они могут стать ценным компонентом вашего интеллек#
туального инструментария.
Дополнительные ресурсы
Среди книг общего плана, посвященных метафорам, моде#
лям и парадигмам, главное место занимает «The Structure of http://cc2e.com/0285
Ключевые моменты
Метафоры являются по природе эвристическими, а не алгоритмическими,
поэтому зачастую они немного небрежны.
Метафоры помогают понять процесс разработки ПО, сопоставляя его с дру#
гими, более знакомыми процессами.
Некоторые метафоры лучше, чем другие.
Сравнение конструирования ПО с возведением здания указывает на необхо#
димость тщательной подготовки к проекту и проясняет различие между круп#
ными и небольшими проектами.
Аналогия между методами разработки ПО и инструментами в интеллектуаль#
ном инструментарии программиста наводит на мысль, что в распоряжении
программистов имеется множество разных инструментов и что ни один ин#
струмент не является универсальным. Выбор правильного инструмента — одно
из условий эффективного программирования.
Метафоры не исключают друг друга. Используйте комбинацию метафор, наи#
более эффективную в вашем случае.
ГЛАВА 2 Метафоры, позволяющие лучше понять разработку ПО 21
Г Л А В А 3
Содержание
http://cc2e.com/0309
3.1. Важность выполнения предварительных условий
3.2. Определите тип ПО, над которым работаете
3.3. Предварительные условия, связанные с определением проблемы
3.4. Предварительные условия, связанные с выработкой требований
3.5. Предварительные условия, связанные с разработкой архитектуры
3.6. Сколько времени посвятить выполнению предварительных условий?
Связанные темы
Основные решения, которые приходится принимать при конструировании:
глава 4
Влияние размера проекта на предварительные условия и процесс конструи#
рования ПО: глава 27
Связь между качеством ПО и аспектами его конструирования: глава 20
Управление конструированием ПО: глава 28
Проектирование ПО: глава 5
Перед началом конструирования дома строители просматривают чертежи, про#
веряют, все ли разрешения получены, и исследуют фундамент. К сооружению
небоскреба, жилого дома и собачьей конуры строители готовились бы по#разно#
му, но, каким бы ни был проект, перед началом конструирования они провели бы
добросовестную подготовку с учетом всех особенностей проекта.
В этой главе мы рассмотрим компоненты подготовки к конструированию ПО. Как
и в строительстве, конечный успех программного проекта во многом определя#
ется до начала конструирования. Если фундамент ненадежен или планирование
выполнено небрежно, на этапе конструирования вы в лучшем случае сможете
только свести вред к минимуму.
22 ЧАСТЬ I Основы разработки ПО
Популярная у плотников поговорка «семь раз отмерь, один раз отрежь» очень
актуальна на этапе конструирования ПО, затраты на который иногда составляют
аж 65% от общего бюджета проекта. В неудачных программных проектах конст#
руирование иногда приходится выполнять дважды, трижды и даже больше. Как и
в любой другой отрасли, повторение самой дорогостоящей части программного
проекта ни к чему хорошему привести не может.
Хотя чтение этой главы является залогом успешного конструирования ПО, само
конструирование в ней не обсуждается. Если вы уже хорошо разбираетесь в цик#
ле разработки ПО или вам не терпится добраться до обсуждения конструирова#
ния, можете перейти к главе 5. Если вам не нравится идея выполнения предвари#
тельных условий конструирования, просмотрите раздел 3.2, чтобы узнать, какую
роль они играют в вашем случае, а затем вернитесь к разделу 3.1, в котором опи#
сываются расходы, связанные с их невыполнением.
Обращение к логике
Подготовка к проекту — одно из главных условий эффективного программирова#
ния, и это логично. Объем планирования зависит от масштаба проекта. С управ#
ленческой точки зрения, планирование подразумевает определение сроков, числа
людей и компьютеров, необходимых для выполнения работ. С технической — пла#
нирование подразумевает получение представления о создаваемой системе, позво#
ляющего не истратить деньги на создание неверной системы. Иногда пользовате#
ли не четко знают, что желают получить, и для определения их требований может
понадобиться больше усилий, чем хотелось бы. Как бы то ни было, это дешевле, чем
создать не то, что нужно, похерить результат и начать все заново.
До начала создания системы не менее важно подумать и о том, как вы собирае#
тесь ее создавать. Никому не хочется тратить время и деньги на бесплодные блуж#
дания по лабиринту.
Обращение к аналогии
Создание программной системы похоже на любой другой проект, требующий
людских и финансовых ресурсов. Возведение дома начинается не с забивания
гвоздей, а с создания, анализа и утверждения чертежей. При разработке ПО на#
личие технического плана означает не меньше.
Никто не наряжает новогоднюю елку, не установив ее. Никто не разводит огонь,
не открыв дымоход. Никто не отправляется в долгий путь с пустым бензобаком.
Никто не принимает душ в одежде и не надевает носки после обуви. И т. д., и т. п.
Программисты — последнее звено пищевой цепи разработки ПО. Архитекторы
поглощают требования, проектировщики потребляют архитектуру, а программи#
сты — проект приложения.
Сравните пищевую цепь разработки ПО с реальной пищевой цепью. В экологи#
чески чистой среде водные жучки служат пищей рыбам, которыми в свою оче#
редь питаются чайки. Это здоровая пищевая цепь. Если на каждом этапе разра#
ботки ПО у вас будет здоровая пища, результатом станет здоровый код, написан#
ный довольными программистами.
26 ЧАСТЬ I Основы разработки ПО
Если среда загрязнена, жучки плавают в ядерных отходах, а рыба плещется в неф#
тяных пятнах. Чайкам не повезло больше всего: находясь в конце пищевой цепи,
они травятся и нефтью, и ядерными отходами. Если ваши требования неудачны,
они отравляют архитектуру, которая в свою очередь травит процесс конструиро#
вания. Результат? Раздражительные программисты и полное изъянов ПО.
При планировании в высокой степени итеративного проекта вы до начала кон#
струирования должны определить важнейшие требования и архитектурные эле#
менты, влияющие на каждый конструируемый фрагмент программы. Строителям,
собирающимся строить поселок, не нужна полная информация о каждом доме до
начала возведения первого дома, однако они должны исследовать место, соста#
вить план канализации и электрических линий и т. д. Если строители плохо под#
готовятся, канализационные трубы, возможно, придется проводить в уже постро#
енный дом.
Обращение к данным
Исследования последних 25 лет убедительно доказали выгоду правильного выпол#
нения проектов с первого раза и дороговизну внесения изменений, которых можно
было избежать.
Ученые из компаний Hewlett#Packard, IBM, Hughes Aircraft, TRW и других
организаций обнаружили, что исправление ошибки к началу конструи#
рования обходится в 10–100 раз дешевле, чем ее устранение в конце ра#
боты над проектом, во время тестирования приложения или после его выпуска
(Fagan, 1976; Humphrey, Snyder, and Willis, 1991; Leffingwell 1997; Willis et al., 1998;
Grady, 1999; Shull et al., 2002; Boehm and Turner, 2004).
Общий принцип прост: исправлять ошибки нужно как можно раньше. Чем доль#
ше дефект сохраняется в пищевой цепи разработки ПО, тем больше вреда он
приносит на следующих этапах. Так как раньше всего вырабатываются требова#
ния, ошибки, допущенные на этом этапе, присутствуют в системе дольше и обхо#
дятся дороже. Кроме того, дефекты, внесенные в систему раньше, оказывают бо#
лее широкое влияние, чем дефекты, внесенные позднее. Это также повышает цену
более ранних дефектов.
Вот данные об относительной дороговизне исправления дефектов в за#
висимости от этапов их внесения и обнаружения (табл. 3#1):
ГЛАВА 3 Семь раз отмерь, один раз отрежь: предварительные условия 27
Эти данные говорят, например, о том, что дефект архитектуры, исправление ко#
торого при проектировании архитектуры обходится в $1000, может во время те#
стировании системы вылиться в $15 000 (рис. 3#1).
Этап внесения
дефекта Затраты
Выработка требований
Проектирование архитектуры
Конструирование
Конструирова Парное или индиви# Парное или индиви# Парное или индиви#
ние дуальное программи# дуальное программи# дуальное программи#
рование. рование. рование.
Неформальная про# Неформальная проце# Формальная процеду#
цедура регистрации дура регистрации кода. ра регистрации кода.
кода или ее отсутст# Обзоры кода по мере Формальные инспек#
вие. надобности. ции кода.
Затраты на исправ# $0 $0 $0 $0
ление дефектов
в конце проекта
Выработка требований
Проектирование архитектуры
Детальное проектирование
Конструирование
Время
Выработка требований
Проектирование архитектуры
Детальное проектирование
Конструирование
Время
Будущие
улучшения
Тестирование системы
Конструирование
Проектирование архитектуры
Выработка требований
Определение проблемы
Рис. 3'5. Прежде чем стрелять, убедитесь в том, что знаете, куда целитесь
Задайте процедуру контроля изменений Если клиент Перекрестная ссылка О том, что
никак не может успокоиться, подумайте об установлении делать с изменениями проекта
стенда контроля изменений для рассмотрения вносимых приложения и самого кода, см.
предложений. В том, что клиенты изменяют точку зрения и раздел 28.2.
понимают, что им нужны дополнительные возможности, нет
ничего аномального. Проблема в том, что они вносят предложения так часто, что
вы не поспеваете за ними. Наличие процедуры контроля изменений осчастливит
всех: вы будете знать, что вам придется работать с изменениями только в опреде#
ленные периоды времени, а клиенты увидят, что вам небезразличны их пожелания.
Используйте те подходы к разработке, которые адап'
Перекрестная ссылка Об итера-
тируются к изменениям Некоторые подходы к разра# тивных подходах к разработке см.
ботке ПО позволяют особенно легко реагировать на изме# подраздел «Используйте итера-
нения требований. Подход эволюционного прототипиро# цию» раздела 5.4 и раздел 29.3.
вания (evolutionary prototyping) помогает исследовать тре#
бования к системе до начала ее создания. Эволюционная поставка ПО подразу#
мевает предоставление системы пользователям по частям. Вы можете создать фраг#
мент системы, получить от пользователей отзывы, немного подкорректировать
проект, внести несколько изменений и приступить к созданию другого фрагмен#
та. Суть этого подхода — короткие циклы разработки, позволяющие быстро реа#
гировать на пожелания пользователей.
Оставьте проект Если требования особенно неудачны
Дополнительные сведения О под-
или изменчивы и никакой из предыдущих советов не рабо# ходах к разработке, поддержи-
тает, завершите проект. Даже если вы не можете на самом вающих гибкие требования, см.
деле завершить его, подумайте об этом. Подумайте о том, книгу «Rapid Development»
насколько хуже он должен стать, чтобы вы от него отказа# (McConnell, 1996).
лись. Если такая ситуация возможна, спросите себя, чем она
отличается от текущей ситуации.
Помните о бизнес'модели проекта Многие проблемы Перекрестная ссылка О разли-
с требованиями исчезают при воспоминании о коммерче# чиях между формальными и не-
ских предпосылках проекта. Требования, которые сначала формальными проектами (кото-
казались прекрасными идеями, могут оказаться ужасными, рые часто объясняются разли-
чиями размеров проектов) см.
когда вы оцените затраты. Программисты, которые прини#
главу 27.
мают во внимание коммерческие следствия своих решений,
ценятся на вес золота, и я был бы рад получить свою комис#
сию за этот совет.
Организация программы
В первую очередь архитектура должна включать общее опи#
Если вы не можете объяснить
сание системы. Без такого описания вам будет трудно со#
что-то шестилетнему ребенку,
ставить согласованную картину из тысячи деталей или хотя
значит, вы сами этого не пони-
маете. бы десятка отдельных классов. Если бы система была моза#
Альберт Эйнштейн икой из 12 фрагментов, ее мог бы с легкостью собрать и го#
довалый ребенок. Головоломку из 12 подсистем собрать труд#
нее, но, если вы не сможете сделать этого, вы не поймете, какой вклад вносит в
систему разрабатываемый вами класс.
Архитектура должна включать подтверждения того, что при ее разработке были
рассмотрены альтернативные варианты, и обосновывать выбор окончательной
организации системы. Никому не хочется разрабатывать класс, если его роль в
системе не кажется хорошо обдуманной. Описывая альтернативные варианты,
ГЛАВА 3 Семь раз отмерь, один раз отрежь: предварительные условия 43
Основные классы
Архитектура должна определять основные классы приложе#
Перекрестная ссылка О проек-
ния, их области ответственности и механизмы взаимодей# тировании классов см. главу 6.
ствия с другими классами. Она должна описывать иерархии
классов, а также изменения состояний и время существова#
ния объектов. Если система достаточно велика, архитектура должна описывать орга#
низацию классов в подсистемы.
Архитектура должна описывать другие рассматривавшиеся варианты организации
классов и обосновывать итоговый вариант. Не все классы системы нужно описы#
вать в спецификации архитектуры. Ориентируйтесь на правило 80/20: описывайте
20% классов, которыми на 80% определяется поведение системы (Jacobsen, Booch,
and Rumbaugh, 1999; Kruchten, 2000).
Организация данных
Архитектура должна описывать основные виды формата
Перекрестная ссылка Об ис-
файлов и таблиц. Она должна описывать рассмотренные аль#
пользовании переменных см.
тернативы и обосновывать итоговые варианты. Если при# главы 10–13.
ложение использует список идентификаторов клиентов и
разработчики архитектуры решили реализовать его при помощи списка с после#
44 ЧАСТЬ I Основы разработки ПО
Бизнес-правила
Архитектура, зависимая от специфических бизнес#правил, должна определять их
и описывать их влияние на проект системы. Возьмем для примера бизнес#прави#
ло, согласно которому информация о клиентах должна устаревать не более чем
на 30 секунд. В данном случае в спецификации архитектуры должно быть указа#
но, как это правило повлияло на выбор метода обеспечения актуальности данных
и их синхронизации.
Пользовательский интерфейс
Пользовательский интерфейс (GUI) часто проектируется на этапе выработки тре#
бований. Если это не так, его следует определить на этапе разработки архитекту#
ры. Архитектура должна описывать главные элементы формата Web#страниц, GUI,
интерфейс командной строки и т. д. Удобство GUI может в итоге определить по#
пулярность или провал программы.
Архитектура должна быть модульной, чтобы GUI можно было изменить, не зат#
ронув бизнес#правил и модулей программы, отвечающих за вывод данных. Напри#
мер, архитектура должна обеспечивать возможность сравнительно легкой заме#
ны группы классов интерактивного интерфейса на группу классов интерфейса
командной строки. Такая возможность весьма полезна; во многом это объясняет#
ся тем, что интерфейс командной строки удобен для тестирования ПО на уровне
блоков или подсистем.
Проектирование GUI заслуживает отдельной книги, и мы его
http://cc2e.com/0393 рассматривать не будем.
Управление ресурсами
Архитектура должна включать план управления ограниченными ресурсами, такими
как соединения с БД, потоки и дескрипторы. При разработке драйверов, встро#
енных систем и других приложений, которые будут работать в условиях ограни#
ГЛАВА 3 Семь раз отмерь, один раз отрежь: предварительные условия 45
Безопасность
Архитектура должна определять подход к безопасности на
уровне проекта приложения и на уровне кода. Если модель http://cc2e.com/0330
угроз до сих пор не разработана, это следует сделать при
проектировании архитектуры. О безопасности нужно по#
Дополнительные сведения Пре-
мнить и при разработке принципов кодирования, в том
красное обсуждение защиты ПО
числе методик обработки буферов и ненадежных данных см. в книге «Writing Secure Code,
(данных, вводимых пользователями, файлов «cookie», кон# 2d Ed.» (Howard and LeBlanc 2003)
фигурационных данных и данных других внешних интер# и в январском номере журнала
фейсов), подходов к шифрованию, уровню подробности «IEEE Software» за 2002 год.
сообщений об ошибках, защите секретных данных, нахо#
дящихся в памяти, и другим вопросам.
Производительность
В требованиях следует определить показатели производи# Дополнительные сведения О про-
тельности. Если они связаны с использованием ресурсов, ектировании высокопроизводи-
надо определить приоритеты для разных ресурсов, в том тельных систем см. книгу Конни
числе соотношение быстродействия, использования памя# Смит «Performance Engineering of
Software Systems» (Smith, 1990).
ти и затрат.
Архитектура должна включать оценки производительности
и объяснять, почему разработчики архитектуры считают эти показатели дости#
жимыми. Если они могут быть не достигнуты, это тоже должно быть отражено в
архитектуре. Если для достижения некоторых показателей требуются специфи#
ческие алгоритмы или типы данных, также укажите это в спецификации архитек#
туры. Кроме того, в архитектуре можно указать объем пространства и время, вы#
деляемые каждому классу или объекту.
Масштабируемость
Масштабируемостью называют возможность системы адаптироваться к росту тре#
бований. Архитектура должна описывать, как система будет реагировать на рост
числа пользователей, серверов, сетевых узлов, записей в БД, транзакций и т. д. Если
развитие системы не предполагается и ее масштабируемость не играет роли, это
должно быть явно указано в архитектуре.
Интернационализация/локализация
«Интернационализацией» называют реализацию в программе поддержки региональ#
ных стандартов. Вместо слова «internationalization» часто используется аббревиату#
ра «I18n», составленная из первой и последней букв слова и числа букв между ними.
«Локализацией» (известной как «L10n» по той же причине) называют перевод интер#
фейса программы и реализацию в ней поддержки конкретного языка.
Вопросы интернационализации заслуживают особого внимания при разработке
архитектуры интерактивной системы. Большинство интерактивных систем вклю#
чает десятки или сотни подсказок, индикаторов состояния, вспомогательных со#
общений, сообщений об ошибках и т. д., поэтому нужно оценить объем ресурсов,
используемых строками. Если разрабатывается коммерческая программа, архитек#
тура должна показывать, что при ее создании были рассмотрены типичные вопро#
сы, связанные со строками и наборами символов, такие как выбор набора симво#
лов (ASCII, DBCS, EBCDIC, MBCS, Unicode, ISO 8859 и т. д.) и типа строк (строки C,
строки Visual Basic и т. д.), а также способа изменения строк, который не требовал
бы изменения кода, и метода перевода строк на иностранные языки, оказывающе#
го минимальное влияние на код и GUI. Строки можно встроить в код, инкапсули#
ровать в класс и использовать посредством интерфейса или сохранить в файле
ресурсов. Архитектура должна объяснять, какой вариант выбран и почему.
Ввод-вывод
Ввод#вывод — еще одна область, на которую стоит обратить внимание при про#
ектировании архитектуры. Архитектура должна определять схему чтения данных:
упреждающее чтение, чтение с задержкой или по требованию. Кроме того, она
должна описывать уровень, на котором будут определяться ошибки ввода#выво#
да: на уровне полей, записей, потоков данных или файлов.
Обработка ошибок
Обработка ошибок — одна из самых сложных проблем современной ин#
форматики, и к ней нельзя относиться с пренебрежением. По оценкам
некоторых ученых код на целых 90% состоит из блоков обработки ис#
ключительных ситуаций, ошибок и т. п., из чего следует, что только 10% кода от#
вечают за номинальный режим работы программы (Shaw in Bentley, 1982). Раз уж
на обработку ошибок приходится такая большая часть кода, стратегия их согла#
сованной обработки должна быть выражена в архитектуре.
Обработку ошибок часто рассматривают на уровне конвенции кодирования, если
вообще рассматривают. Однако она оказывает влияние на всю систему, поэтому
лучше всего рассматривать ее на уровне архитектуры. Вот некоторые вопросы, на
которые нужно обратить внимание.
Является ли обработка ошибок корректирующей или ориентированной на их
простое обнаружение? В первом случае программа может попытаться восста#
новиться от последствий ошибки. Во втором — может продолжить работу как
ни в чем не бывало или завершиться. Как бы то ни было, она должна извес#
тить пользователя об ошибке.
ГЛАВА 3 Семь раз отмерь, один раз отрежь: предварительные условия 47
Отказоустойчивость
При разработке архитектуры системы следует указать ожидаемый уровень ее отка#
зоустойчивости. Отказоустойчивостью называют совокупность свойств системы, по#
вышающих ее надежность путем обнаружения ошибок, восстановления, если это
возможно, и изоляции их плохих последствий, если восстановление невозможно.
Например, вычисление системой квадратного корня можно сделать отказоустой#
чивым несколькими способами.
В случае неудачи система может вернуться в предыдущее состояние и попро#
бовать вычислить корень еще раз. Если первый ответ неверен, она может вер#
нуться в состояние, при котором все наверняка было правильно, и продолжить
работу с этого момента.
48 ЧАСТЬ I Основы разработки ПО
Избыточная функциональность
Надежностью называют способность системы продолжать работу после обнару#
жения ошибки. Частенько в спецификации архитектуры разработчики определя#
ют более надежную систему, чем указано в требованиях. Одна из причин этого в
том, что система, состоящая из многих частей, удовлетворяющих минимальным
требованиям к надежности, в целом может оказаться менее надежной, чем нуж#
но. В мире ПО цепь не так крепка, как слабейшее звено; она так слаба, как все слабые
звенья, вместе взятые. В спецификации архитектуры должно быть явно указано,
могут ли программисты реализовать в своих блоках программы избыточную фун#
кциональность или они должны создать простейшую работоспособную систему.
Определить отношение к реализации избыточной функциональности особенно
важно потому, что многие программисты делают это автоматически, из чувства
профессиональной гордости. Явно выразив ожидания в архитектуре, вы сможете
избежать феномена, при котором некоторые классы исключительно надежны, а
другие лишь отвечают требованиям.
ГЛАВА 3 Семь раз отмерь, один раз отрежь: предварительные условия 49
Повторное использование
Если план предусматривает применение существующего кода, тестов, форматов
данных и т. д., архитектура должна объяснять, как повторно использованные ре#
сурсы будут адаптированы к другим архитектурным особенностям, если это бу#
дет сделано.
Стратегия изменений
Так как при создании продукта и программисты, и пользо# Перекрестная ссылка О систе-
ватели обучаются, приложение скорее всего в период разра# матичной обработке изменений
ботки будет изменяться. Причинами этого могут быть изме# см. раздел 28.2.
нения типов данных, форматов файлов, функциональности,
реализация новых функций и т. д. Изменения могут быть новыми возможностями,
которые были запланированы заранее или не были реализованы в первой версии
системы. Поэтому разработчику архитектуры ПО следует сделать ее достаточно
гибкой, чтобы в систему можно было легко внести вероятные изменения.
Архитектура должна четко описывать стратегию изменений.
Ошибки проектирования часто
Архитектура должна показывать, что возможные улучшения являются довольно тонкими и
рассматривались и что реализация наиболее вероятных объясняются эволюцией, при
улучшений окажется наиболее простой. Если вероятны из# которой по мере реализации
менения форматов ввода или вывода данных, стиля взаимо# новых функций и возможностей
разработчики забывают о сде-
действия с пользователями или требований к обработке,
ланных ранее предположениях.
архитектура должна показывать, что все эти изменения были
Фернандо Дж. Корбати
предвосхищены и каждое из них будет ограничено неболь# (Ferrnando J. Corbatу)
шим числом классов. Архитектурный план внесения изме#
нений может быть совсем простым: включить в файлы данных номера версий, за#
резервировать поля на будущее, спроектировать файлы так, чтобы в них можно
было добавить новые таблицы и т. д. Если применяется генератор кода, архитек#
тура должна показывать, что он поддерживает возможность внесения предпола#
гаемых изменений.
50 ЧАСТЬ I Основы разработки ПО
Дополнительные ресурсы
Ниже я привел список ресурсов, посвященных работе над
http://cc2e.com/0344
требованиями.
Разработка архитектуры
В последние несколько лет было опубликовано много книг,
http://cc2e.com/0372 посвященных разработке архитектуры ПО. Одними из луч#
ших я считаю следующие.
Bass, Len, Paul Clements, and Rick Kazman. Software Architecture in Practice, 2d ed. Boston,
MA: Addison#Wesley, 2003.
Buschman, Frank, et al. Pattern%Oriented Software Architecture, Volume 1: A System of
Patterns. New York, NY: John Wiley & Sons, 1996.
Clements, Paul, ed. Documenting Software Architectures: Views and Beyond. Boston, MA:
Addison#Wesley, 2003.
Clements, Paul, Rick Kazman, and Mark Klein. Evaluating Software Architectures: Meth%
ods and Case Studies. Boston, MA: Addison#Wesley, 2002.
Fowler, Martin. «Patterns of Enterprise Application Architecture». Boston, MA: Addison#
Wesley, 2002.
Jacobson, Ivar, Grady Booch, and James Rumbaugh. The Unified Software Development
Process. Reading, MA: Addison#Wesley, 1999.
IEEE Std 1471%2000. Recommended Practice for Architectural Description of Software%
Intensive Systems. Los Alamitos, CA: IEEE Computer Society Press. Этот документ явля#
ется руководством IEEE#ANSI по созданию спецификаций архитектуры ПО.
ГЛАВА 3 Семь раз отмерь, один раз отрежь: предварительные условия 55
интересны четыре раздела главы 3: «A Typical Day using PSP/TSP», «A Typical Day
using Extreme Programming», «A Crisis Day using PSP/TSP» и «A Crisis Day using Extreme
Programming». Глава 5 посвящена использованию рискованных подходов с целью
уравновешивания гибкости, что может служить руководством по выбору между
гибким методом или методом, основанным на планировании. В главе 6 приводится
хорошо сбалансированная перспектива. Приложение Е включает подробные опыт#
ные данные о гибких методах разработки.
Larman, Craig. Agile and Iterative Development: A Manager’s Guide. Boston, MA: Add#
ison Wesley, 2004. Это основанное на тщательных исследованиях введение в гиб#
кие эволюционные стили разработки включает обзор подходов Scrum, Extreme
Programming, Unified Process и Evo.
Ключевые моменты
Главной целью подготовки к конструированию является снижение риска. Убе#
дитесь, что проводимая вами подготовка снижает риск, а не повышает его.
Если вы хотите разрабатывать высококачественное ПО, внимание к качеству
должно быть частью процесса разработки ПО с начала до конца. Внимание к
качеству в начале процесса оказывает наибольшее влияние на итоговое каче#
ство приложения.
Одним из аспектов профессии программиста является объяснение руководи#
телям и коллегам процесса разработки ПО, в том числе важности адекватной
подготовки к программированию.
Предварительные условия конструирования в большой степени зависят от типа
проекта, над которым вы работаете: многие проекты призывают к использо#
ванию высокоитеративного подхода, другие — более последовательного.
При отсутствии грамотного определения проблемы вы можете на этапе кон#
струирования потратить силы на решение неверной проблемы.
Если не проведена адекватная выработка требований, вы можете упустить важ#
ные детали проблемы. Изменения требований после конструирования обхо#
дятся в 20–100 раз дороже, чем на предыдущих этапах, поэтому перед нача#
лом программирования обязательно убедитесь в правильности требований.
ГЛАВА 3 Семь раз отмерь, один раз отрежь: предварительные условия 57
Г Л А В А 4
Содержание
http://cc2e.com/0489
4.1. Выбор языка программирования
4.2. Конвенции программирования
4.3. Волны развития технологий
4.4. Выбор основных методик конструирования
Связанные темы
Предварительные условия: глава 3
Определение типа ПО, над которым вы работаете: раздел 3.2
Влияние размера программы на ее конструирование: глава 27
Управление конструированием: глава 28
Проектирование ПО: главы 5–9
Как только вы убедились, что адекватный фундамент для конструирования про#
граммы создан, фокусом подготовки становятся решения, более специфичные для
конструирования. В главе 3 мы обсудили программные эквиваленты чертежей и
разрешений на конструирование. Как правило, у программистов нет особого кон#
троля над этими подготовительными действиями, поэтому главной темой главы
3 была оценка того, с чем приходится работать в начале конструирования. Эта
глава посвящена тем аспектам подготовки, за которые прямо или косвенно отве#
чают отдельные программисты и технические руководители проекта. Мы рассмот#
рим выбор специфических инструментов и непосредственную подготовку к ра#
боте над приложением.
Если вы думаете, что уже достаточно знаете о подготовке к конструированию,
можете сразу перейти к главе 5.
ГЛАВА 4 Основные решения, которые приходится принимать при конструировании 59
Описания языков
История разработки некоторых языков и их общие возможности довольно инте#
ресны. Ниже приведены описания языков, наиболее популярных в настоящее время.
Ada
Высокоуровневый язык общего назначения, основанный на языке Pascal. Разра#
ботанный под патронажем Минобороны США, он особенно хорошо подходит для
ГЛАВА 4 Основные решения, которые приходится принимать при конструировании 61
Ассемблер
Низкоуровневый язык, каждая команда которого соответствует одной команде
компьютера. Вследствие этого ассемблер специфичен для отдельных процессо#
ров — например, для конкретных процессоров Intel или Motorola. Ассемблер счи#
тается языком второго поколения. Большинство программистов избегают его и
используют, только если к быстродействию или компактности кода программы
предъявляются повышенные требования.
C
Среднеуровневый язык общего назначения, первоначально тесно связанный с ОС
UNIX. Некоторые свойства (структурированные данные, структурированная управ#
ляющая логика, машинная независимость и богатый набор операторов) делают
его похожим на высокоуровневый язык. Язык C также называют «портируемым
языком ассемблера», поскольку он не строго типизирован, поощряет применение
указателей и адресов и поддерживает некоторые низкоуровневые возможности,
такие как побитовые операции.
Язык C, разработанный в 1970#х компанией Bell Labs, предназаначался для сис#
тем DEC PDP#11. На C были написаны ОС, компилятор C и приложения UNIX для
систем DEC PDP#11. В 1988 г. для систематизации C был издан стандарт ANSI, ко#
торый в 1999 г. был пересмотрен. В 1980#х и 1990#х гг. язык C был стандартом
«де#факто» в области разработки программ для микрокомпьютеров и рабочих стан#
ций.
C++
Этот объектно#ориентированный язык был разработан на базе C в компании Bell
Labs в 1980#х. Совместимый с языком C, он поддерживает классы, полиморфизм,
обработку исключений, шаблоны и обеспечивает более надежную проверку ти#
пов, чем C. Кроме того, он предоставляет разработчикам богатую и эффективную
стандартную библиотеку.
C#
Эта комбинация объектно#ориентированного языка общего назначения и среды
программирования разработана в Microsoft. C# имеет синтаксис, похожий на син#
таксис C, C++ и Java, и включает богатый инструментарий, помогающий разраба#
тывать приложения на платформах Microsoft.
62 ЧАСТЬ I Основы разработки ПО
Cobol
Напоминает английский язык и был разработан в 1959–1961 гг. для нужд Мин#
обороны США. Cobol служит преимущественно для разработки бизнес#приложе#
ний и до сих пор является одним из самых популярных языков, уступая лишь Visual
Basic (Feiman and Driver, 2002). По мере развития языка в нем была реализована
поддержка дополнительных математических функций и ряда объектно#ориенти#
рованных возможностей. Аббревиатура «Cobol» расшифровывается как «COmmon
Business#Oriented Language» (универсальный язык, ориентированный на коммер#
ческие задачи).
Fortran
В этом первом высокоуровневом языке программирования были представлены
концепции переменных и высокоуровневых циклов. Название расшифровывает#
ся как «FORmula TRANslation» (транслятор формул). Разработанный в 1950#х, Fortran
претерпел несколько значительных ревизий: так, в 1977 г. была разработана вер#
сия Fortran 77, в которой была реализована поддержка блочных операторов if#then#
else и манипуляций над символьными строками. В Fortran 90 были включены сред#
ства работы с пользовательскими типами данных, указателями, классами, а также
богатый набор функций для работы с массивами. Fortran применяется преимуще#
ственно для разработки научных и инженерных приложений.
Java
Синтаксис этого объектно#ориентированного языка, разработанного Sun Micro#
systems, Inc., напоминает C и C++. Java — платформенно#независимый язык: ис#
ходный код Java сначала преобразуется в байт#код, который может выполняться
на любой платформе в среде, известной как «виртуальная машина». Java широко
используется для создания Web#приложений.
JavaScript
Этот интерпретируемый язык сценариев мало чем связан с Java. Чаще всего его
используют для создания кода, выполняющегося на клиентской стороне, напри#
мер, для разработки несложных функций и интерактивных приложений для Web#
страниц.
Perl
Этот язык обработки строк основан на C и нескольких утилитах ОС UNIX. Perl часто
используется для решения задач системного администрирования, таких как со#
здание сценариев сборки программ, а также для генерации и обработки отчетов.
Кроме того, на нем создают Web#приложения, такие как Slashdot. Аббревиатура
«Perl» расшифровывается как «Practical Extraction and Report Language» (практи#
ческий язык извлечений и отчетов).
PHP
Этот язык с открытым исходным кодом предназначен для разработки сценариев
и имеет простой синтаксис, похожий на синтаксис языков Perl, JavaScript, C и
оболочки Bourne Shell. PHP поддерживается всеми основными ОС и служит для
ГЛАВА 4 Основные решения, которые приходится принимать при конструировании 63
Python
Этот интерпретируемый интерактивный объектно#ориентированный язык под#
держивает множество сред. Чаще всего его используют для написания сценариев
и небольших Web#приложений, однако он поддерживает и некоторые средства,
помогающие создавать более крупные программы.
SQL
SQL (Structured Query Language, язык структурированных запросов) «де#факто» яв#
ляется стандартным языком выполнения запросов, обновлений реляционнных БД
и управления ими. В отличие от других языков, описанных в этом разделе, SQL
является «декларативным языком», т. е. определяет не последовательность, а резуль#
тат выполнения некоторых операций.
Visual Basic
Basic (Beginner’s All#purpose Symbolic Instruction Code, универсальная система сим#
волического кодирования для начинающих) — это высокоуровневый язык, первая
версия которого была разработана в Дартмутском колледже в 1960#х. Visual Basic
— это высокоуровневая объектно#ориентированная версия Basic, предназначенная
для визуального программирования. Изначально Visual Basic был разработан в
Microsoft для создания приложений Microsoft Windows. Позднее в нем была реали#
зована поддержка настройки Microsoft Office и других приложений для настольных
ПК, создания Web#приложений и других программ. По оценкам экспертов в самом
начале первого десятилетия XXI века Visual Basic являлся самым популярным язы#
ком среди профессиональных разработчиков (Feiman and Driver, 2002).
Кодирование
Решили ли вы, какая часть проекта приложения будет разработана предва-
рительно, а какая во время написания кода?
Выбрали ли вы конвенции именования программных элементов, оформле-
ния комментариев и форматирования кода?
Выбрали ли вы специфические методики кодирования, определяемые архи-
тектурой приложения? Определили ли вы, как будут обрабатываться ошиб-
ки, как будут решаться проблемы, связанные с безопасностью, какие конвенции
будут использоваться при разработке интерфейсов классов, каким стандар-
там должен будет отвечать повторно используемый код, сколько внимания нужно
будет уделять быстродействию приложения при кодировании и т. д.?
Определили ли вы стадию развития используемой технологии и адаптиро-
вали ли к ней свой подход? Если это необходимо, определились ли вы с
тем, как будете программировать с использованием языка, вместо того чтобы
ограничиваться программированием на нем?
Работа в группе
Определили ли вы процедуру интеграции? Иначе говоря, какие специфи-
ческие действия программист должен будет выполнить перед включением
своего кода в исходный код всего проекта?
Будут ли программисты программировать парами, индивидуально или эти
подходы будут скомбинированы?
Гарантия качества
Перекрестная ссылка Гарантия
Должны ли будут программисты разработать тесты для качества рассматривается в гла-
своего кода до написания самого кода? ве 20.
Должны ли будут программисты разработать блочные
тесты для своего кода?
Должны ли будут программисты перед включением своего кода в исходный
код всего проекта проанализировать его в отладчике?
Должны ли будут программисты выполнить интеграционное тестирование
своего кода до его включения в исходный код проекта?
Будут ли программисты выполнять взаимные обзоры или инспекцию кода?
Инструменты
Перекрестная ссылка Об инст-
Выбрали ли вы инструмент управления версиями?
рументах программирования см.
Выбрали ли вы язык, версию языка и версию компиля- главу 30.
тора?
Выбрали ли вы платформу программирования (такую как
J2EE или Microsoft .NET) или явно решили не использовать ее?
Приняли ли вы решение о том, можно ли будет использовать нестандарт-
ные возможности языка?
Определили ли вы другие средства, которые будете применять: редактор,
инструмент рефакторинга, платформу для тестирования, модуль проверки
синтаксиса и т. д.? Приобрели ли вы их?
68 ЧАСТЬ I Основы разработки ПО
Ключевые моменты
Каждый язык программирования имеет достоинства и недостатки. Вы долж#
ны знать отдельные достоинства и недостатки используемого языка.
Определите конвенции программирования до начала программирования.
Позднее адаптировать к ним код станет почти невозможно.
Методик конструирования слишком много, чтобы использовать все в одном
проекте. Тщательно выбирайте методики, наиболее подходящие для вашего про#
екта.
Спросите себя, являются ли используемые вами методики программирования
ответом на выбранный язык программирования или их выбор был определен
языком. Помните, что программировать следует с использованием языка, а не
на языке.
Эффективность конкретных подходов и даже возможность их применения за#
висит от стадии развития соответствующей технологии. Определите стадию раз#
вития используемой технологии и адаптируйте к ней свои планы и ожидания.
ГЛАВА 4 Основные решения, которые приходится принимать при конструировании 69
Часть II
ВЫСОКОКАЧЕСТВЕННЫЙ
КОД
Глава 6. Классы
ГЛ А В А 5
Проектирование
при конструировании
Содержание
http://cc2e.com/0578
5.1. Проблемы, связанные с проектированием ПО
5.2. Основные концепции проектирования
5.3. Компоненты проектирования: эвристические принципы
5.4. Методики проектирования
5.5. Комментарии по поводу популярных методологий
Связанные темы
Разработка архитектуры ПО: раздел 3.5
Классы: глава 6
Характеристики высококачественных методов: глава 7
Защитное программирование: глава 8
Рефакторинг: глава 24
Зависимость конструирования от объема программы: глава 27
Некоторые программисты могут заявить, что проектирование не связано с кон-
струированием, но при работе над небольшими проектами конструирование
часто включает другие процессы, в том числе проектирование. В некоторых бо-
лее крупных проектах формальная архитектура может давать ответы только на
во#просы системного уровня, при этом значительная часть проектирования может
быть намеренно оставлена на этап конструирования. В других крупных проектах
проектирование может быть проведено в таком объеме, что кодирование стано-
вится почти механическим, однако это случается редко — официально или нет,
программисты обычно сами проектируют некоторые фрагменты программы.
В случае небольших неформальных проектов значительная
Перекрестная ссылка Об уров- часть проектирования выполняется за клавиатурой. «Про-
нях формальности, требуемой
ектирование» может выражаться в простом написании ин-
при работе над крупными и не-
большими проектами, см. гла- терфейса класса на псевдокоде до разработки его деталей.
ву 27. Оно может выражаться в рисовании диаграмм отношений
ГЛАВА 5 Проектирование при конструировании 71
и колес, число дверей — все это несущественные свойства. Можете также думать
о них как о второстепенных, произвольных, необязательных и случайных.
Брукс замечает, что главные несущественные проблемы раз-
Перекрестная ссылка В ранних
работки ПО уже давно решены. Например, несущественные средах несущественные пробле-
проблемы, связанные с неудобным синтаксисом языков мы проявляются сильнее, чем в
программирования, постепенно утратили свою значимость зрелых (см. раздел 4.3).
по мере эволюции языков. Несущественные проблемы, свя-
занные с неинтерактивностью компьютеров, исчезли, когда
на смену ОС, работающим в пакетном режиме, пришли системы с разделением
времени. Среды интегрированной разработки избавили программистов от про-
блем, обусловленных плохим взаимодействием инструментов.
В то же время Брукс утверждает, что решение оставшихся существенных проблем
разработки ПО будет более медленным. Это объясняется тем, что разработка
программ по своей сути требует анализа всех деталей крайне сложного набора
взаимосвязанных концепций. Причиной существенных проблем является не-
обходимость анализа сложного неорганизованного реального мира, точного и
полного определения зависимостей и исключений, проектирования абсолютно,
но никак не приблизительно верных решений и т. д. Даже если б мы смогли
придумать язык программирования, основанный на той же терминологии, что
и требующая решения проблема реального мира, программирование все равно
осталось бы сложным из#за необходимости точного определения принципов
функционирования мира. По мере того как разработчики ПО берутся за решение
все более серьезных проблем реального мира, им приходится анализировать все
более сложные взаимодействия между сущностями, что в свою очередь приводит
к повышению существенной сложности программных решений.
Источник всех этих существенных проблем — сложность как несущественная,
так и существенная.
Уровни проектирования
Проектирование программной системы требует нескольких уровней детальности.
Некоторые методы проектирования используются на всех уровнях, а другие только
на одном#двух (рис. 5#2).
довольно крупными, такими как модуль работы с базами данных, модули GUI,
бизнес#правил или создания отчетов, интерпретатор команд и т. д. Суть проек-
тирования на данном уровне заключается в разделении программы на основные
подсистемы и определении взаимодействий между подсистемами. Обычно этот
уровень нужен при работе над любыми проектами, требующими более нескольких
недель. При проектировании отдельных подсистем можно применять разные под-
ходы: выбирайте тот, который кажется вам оптимальным в каждом конкретном
случае. На рис. 5#2 данный уровень проектирования обозначен цифрой 2.
Особенно важный аспект этого уровня — определение правил взаимодействия под-
систем. Если все подсистемы могут взаимодействовать, выгода их разделения исчезает.
Подчеркивайте суть подсистем, ограничивая их взаимодействие между собой.
Допустим, вы определили систему из шести подсистем (рис. 5#3). При отсутствии
каких#либо ограничений в силу второго закона термодинамики энтропия системы
должна увеличиться. Один из способов увеличения энтропии является абсолютно
свободное взаимодействие между подсистемами (рис. 5#4).
Классы и объекты
Один из важнейших аспектов объектно#ориентированного проектирования —
различие между объектами и классами. Объект — это любая конкретная динами-
ческая сущность, имеющая конкретные значения и атрибуты и существующая в
период выполнения программы. Класс — это статическая сущность, с которой вы
имеете дело, просматривая листинг программы. Например, вы можете объявить
класс Person (человек), имеющий такие атрибуты, как фамилия, возраст, пол и т. д.
В период выполнения вы будете работать с объектами nancy, hank, diane, tony и
т. д. — иначе говоря, со специфическими экземплярами класса. Если вы знакомы
с терминологией БД, различие между классом и объектом аналогично различию
между «схемой» и «экземпляром». Класс можно рассматривать как форму для вы-
печки булочек, а объекты — как сами булочки. В этой книге термины «класс» и
«объект» используются неформально и более или менее взаимозаменяемо.
Рис. 5'6. Эта система расчета оплаты состоит из четырех основных объектов
(пример упрощен)
Определить атрибуты объектов не сложнее, чем сами объекты. Каждый объект имеет
характеристики, релевантные для компьютерной программы. Скажем, в системе
расчета повременной оплаты объект «сотрудник» обладал бы такими атрибутами,
как имя/фамилия, должность и уровень оплаты. С объектом «счет» были бы связаны
такие атрибуты, как сумма, имя/фамилия клиента, дата и т. д.
Объектами системы GUI были бы разнообразные окна, кнопки, шрифты и ин-
струменты рисования. При дальнейшем изучении проблемной области вы можете
прийти к выводу, что установление однозначного соответствия между объектами
программы и объектами реального мира — не самый лучший способ определения
объектов, но для начала он тоже неплох.
Определите действия, которые могут быть выполнены над каждым объ-
ектом Объекты могут поддерживать самые разные операции. В нашей системе
расчета оплаты объект «сотрудник» мог бы поддерживать изменение должности
или уровня оплаты, объект «клиент» — изменение реквизитов счета и т. д.
Определите действия, которые каждый объект может выполнять над
другими объектами Суть этого этапа ясна из его названия. Двумя универсаль-
ными действиями, которые объекты могут выполнять друг над другом, являются
включение (containment) и наследование. Какие объекты могут включать другие
(какие?) объекты? Какие объекты могут быть унаследованными от других (ка-
ких?) объектов? На рис. 5#6 объект «карта учета времени» может включать объект
«сотрудник» и объект «клиент», а объект «счет» может включать карты учета вре-
мени. Кроме того, счет может сообщать, что клиент оплатил услуги, а клиент —
оплачивать указанную в счете сумму. Более сложная система включала бы допол-
нительные взаимодействия.
Определите части каждого объекта, видимые другим объектам Один
из главных аспектов проектирования — определение частей объекта, которые
следует сделать открытыми, и частей, которые следует держать закрытыми. Этого
решения требуют и данные, и методы.
86 ЧАСТЬ II Высококачественный код
программу. Она будет скрыта в одном темном уголке системы, пока не придет время
ее изменить.
Зависимости от оборудования Примерами модулей, зависимых от оборудова-
ния, могут служить интерфейсы между программой и разными типами мониторов,
принтеров, клавиатур, дисководов, звуковых плат и сетевых устройств. Изолируй-
те зависимости от оборудования в отдельной подсистеме или отдельном классе. Это
облегчает адаптацию программы к новой аппаратной среде, а также помогает раз-
рабатывать ПО для нестабильных версий устройств. Вы можете разработать ПО,
моделирующее взаимодействие с конкретным устройством, и создать подсистему
аппаратного интерфейса, использующую эту модель, пока устройство нестабиль-
но или недоступно. Когда устройство будет готово к работе, подсистему интерфейса
можно будет отключить от модели и подключить к устройству.
Ввод'вывод На чуть более высоком в сравнении с аппаратными интерфейсами
уровне проектирования частой областью изменений является ввод#вывод. Если ваше
приложение создает собственные файлы данных, его усложнение вполне может
потребовать изменения формата файлов. Аспекты формата ввода#вывода данных,
относящиеся к пользовательскому уровню, такие как позиционирование и число
полей на странице, их последовательность и т. д., изменяются не менее часто.
В общем, анализ всех внешних интерфейсов на предмет возможных изменений —
благоразумная идея.
Нестандартные возможности языка Большинство версий языков поддер-
живает нестандартные расширения, облегчающие работу программистов. Расшире-
ния — палка о двух концах, потому что в другой среде — будь то другая аппаратная
платформа, реализация языка другим производителем или новая версия языка,
выпущенная тем же производителем, — они могут оказаться недоступны.
Если вы применяете нестандартные расширения языка, скройте работу с ними в
отдельном классе, чтобы его можно было заменить при адаптации приложения к
другой среде. Аналогично, используя библиотечные методы, доступные не во всех
средах, скройте их за интерфейсом, поддерживающим все нужные среды.
Сложные аспекты проектирования и конструирования Скрывайте слож-
ные аспекты проектирования и конструирования, потому что их частенько при-
ходится реализовывать заново. Отделите их и минимизируйте влияние, которое
может оказать их неудачное проектирование или конструирование на остальные
части системы.
Переменные статуса Переменные статуса характеризуют состояние програм-
мы и изменяются чаще, чем большинство других видов данных. Так, разработчи-
ки, определившие переменную статуса ошибки как булеву переменную, вполне
могут позднее прийти к выводу, что для этого лучше было бы использовать пере-
числение со значениями ErrorType_None, ErrorType_Warning и ErrorType_Fatal.
Использование переменных статуса можно сделать более гибким и понятным
минимум двумя способами.
В качестве переменных статуса примените не булевы переменные, а перечис-
ления. Диапазон поддерживаемых переменными статуса состояний часто при-
ходится расширять, что в случае перечисления требует лишь перекомпиляции
96 ЧАСТЬ II Высококачественный код
С точки зрения других критериев, эти два модуля кажутся слабо сопряженными:
связь двух модулей посредством объекта employee очевидна и является единствен-
ной. Теперь предположим, что вам нужно использовать модуль LookupVacationBe-
nefit() из третьего модуля, владеющего информацией о дате приема сотрудника
на работу и его должности, но хранит ее не в объекте employee. В этот момент
модуль LookupVacationBenefit() начинает вести себя гораздо менее дружелюбно, не
желая связываться с новым модулем.
Чтобы третий модуль мог обратиться к модулю LookupVacationBenefit(), он должен
знать о существовании класса Employee. Он мог бы подделать объект employee,
используя лишь два поля, но тогда он должен был бы знать внутренние детали
работы метода LookupVacationBenefit(): ему была бы необходима уверенность в том,
что метод LookupVacationBenefit() использует только два этих поля. Такое реше-
ние было бы небрежным и безобразным. Второй вариант мог бы заключаться в
таком изменении метода LookupVacationBenefit(), чтобы вместо объекта employee
он принимал должность сотрудника и дату его приема на работу. В обоих случа-
ях первоначальный модуль оказывается на самом деле гораздо менее гибким, чем
казалось сначала.
Возможен и счастливый конец этой истории: недружелюбный модуль сможет
завести друзей, если пожелает быть гибким — если вместо объекта employee он
согласится принимать должность и дату приема сотрудника на работу.
Короче, чем проще вызывать модуль из других модулей, тем слабее он сопряжен,
и это хорошо, потому что такой модуль более гибок и прост в сопровождении.
Создавая структуру программы, делите ее на блоки с учетом их взаимосвязанно-
сти. Если бы программа была куском дерева, его следовало бы расщепить парал-
лельно волокнам.
Виды сопряжения
Самые распространенные виды сопряжения описаны ниже.
Простое сопряжение посредством данных'параметров Два модуля со-
пряжены таким способом, если между ними передаются только элементарные
типы данных, причем передаются через списки параметров. Этот вид сопряжения
нормален и приемлем.
Простое сопряжение посредством объекта Модуль сопряжен с объектом
этим способом, если он создает экземпляр данного объекта. С этим видом со-
пряжения также все в порядке.
Сопряжение посредством объекта'параметра Два модуля сопряжены друг
с другом объектом#параметром, если Объект 1 требует, чтобы Объект 2 передал
ему Объект 3. Этот вид сопряжения жестче, чем тот вид, при котором Объект 1
требует от Объекта 2 только примитивных типов данных, потому что Объект 2
должен обладать информацией об Объекте 3.
Семантическое сопряжение Самый коварный тип сопряжения имеет место
тогда, когда один модуль использует не какой#то синтаксический элемент друго-
го модуля, а некоторые семантические знания о внутренней работе этого модуля.
Некоторые примеры такого вида сопряжения описаны ниже.
ГЛАВА 5 Проектирование при конструировании 99
Формируйте иерархии
Иерархия — это многоуровневая структура организации информации, при которой
наиболее общая или абстрактная репрезентация концепции соответствует вершине,
а более детальные специализированные репрезентации — более низким уровням.
При разработке ПО иерархии обнаруживаются, например, в наборах классов и в
последовательностях вызовов методов (уровень 4 на рис. 5#2).
Формирование иерархий уже более 2000 лет является важным средством управле-
ния сложными наборами информации. Так, Аристотель использовал иерархию для
организации царства животных. Люди часто организуют сложную информацию
(такую как эта книга) при помощи иерархических схем. Ученые обнаружили, что
люди в целом находят иерархии естественным способом организации сложной
информации. Рисуя сложный объект (скажем, дом), люди рисуют его иерархиче-
ски. Сначала они рисуют очертания дома, затем окна и двери, а после этого — еще
более подробные детали. Они не рисуют дом по отдельным кирпичам, доскам или
гвоздям (Simon, 1996).
Иерархии помогают в достижении Главного Технического Императива Разработ-
ки ПО, позволяя сосредоточиться только на том уровне детальности, который
ГЛАВА 5 Проектирование при конструировании 103
Избегайте неудач
Профессор гражданского строительства Генри Петроски в интересной книге
«Design Paradigms: Case Histories of Error and Judgment in Engineering» (Petroski,
1994), посвященной истории неудач в отрасли проектирования мостов, утверждает,
что многие известные мосты рушились из#за чрезмерного внимания к прошлым
успехам и неадекватного рассмотрения возможных причин аварий. Он делает вы-
вод, что аварий вроде крушения моста Tacoma Narrows можно было бы избежать,
если б инженеры тщательно рассматривали возможные причины аварий, а не
просто копировали другие успешные проекты.
Крупные бреши в защите многих известных систем, обнаруженные в прошедшие
годы, заставляют подумать о том, как применить идеи Петроски в области про-
ектирования ПО.
104 ЧАСТЬ II Высококачественный код
Рисуйте диаграммы
Диаграммы — еще один мощный эвристический инструмент. Все знают, что лучше
один раз увидеть, чем сто раз услышать. Диаграммы позволяют представить про-
блему на более высоком уровне абстракции, и никакие описания их не заменят.
Помните: иногда проблему следует рассматривать на детальном уровне, а иногда
целесообразно иметь дело с более общими аспектами.
Используйте итерацию
Возможно, у вас были случаи, когда вы так много узнали во время написания про-
граммы, что желали бы написать ее заново, опираясь на полученные знания. Этот
же феномен наблюдается и при проектировании, но этап проектирования короче,
тогда как влияние, оказываемое им на последующие этапы, выражено сильнее,
поэтому вы вполне можете выполнить этап проектирования несколько раз.
Проектирование — итеративный процесс. Выйдя из точки А и достигнув
точки Б, не останавливайтесь, а вернитесь в точку А.
Изучая возможные варианты проектирования и пробуя разные подходы,
вы будете рассматривать и высокоуровневые, и низкоуровневые аспекты. Общая
картина, которую вы получаете при работе над высокоуровневыми вопросами,
поможет вам лучше понять низкоуровневые детали. Детали, которые вы узнаете
при работе над низкоуровневыми вопросами, помогут вам создать прочный фун-
дамент для принятия высокоуровневых решений. Некоторые конфликты между
высокоуровневыми и низкоуровневыми соображениями — вполне здоровое яв-
ление; это напряжение способствует созданию структуры, более стабильной, чем
структура, полностью созданная «сверху вниз» или «снизу вверх».
Многим программистам — и вообще многим людям — трудно переключаться между
высокоуровневыми и низкоуровневыми точками зрения, но эта способность —
важное условие эффективного проектирования. Занимательные упражнения, по-
зволяющие развить гибкость ума, можно найти в книге «Conceptual Blockbusting»
(Adams, 2001), описанной в разделе «Дополнительные ресурсы» в конце главы.
108 ЧАСТЬ II Высококачественный код
Разделяй и властвуй
Как указал Эдсгер Дейкстра, никто не обладает умом, способным вместить все
детали сложной программы. То же можно сказать и о проектировании. Разделите
программу на разные области и спроектируйте их по отдельности. Если, работая
над одной из областей, вы попадете в тупик, вспомните про итерацию!
Инкрементное улучшение — мощное средство управления сложностью. Вспомни-
те, как Полья советовал решать математические задачи: поймите задачу, составьте
план решения, осуществите план и оглянитесь назад, чтобы лучше понять, что и
как вы сделали (Polya, 1957).
Экспериментальное прототипирование
Иногда адекватность конкретного проекта невозможно
оценить, не имея дополнительных сведений о деталях реа-
http://cc2e.com/0599
лизации. Вы можете не знать, приемлема ли конкретная ор-
ганизация базы данных, пока не узнаете, будет ли она удовлетворять конкретным
требованиям к производительности. Вы можете не знать, приемлем ли проект
ГЛАВА 5 Проектирование при конструировании 111
Совместное проектирование
Перекрестная ссылка О совмест-
Обычно при проектировании две головы лучше, чем одна,
ной разработке ПО см. главу 21 организованы они формально или неформально. Сотрудни-
чество может принимать любую из следующих форм:
вы подходите к столу коллеги и просите его обсудить с вами некоторые идеи;
вы с коллегой идете в конференц#зал и рисуете на доске варианты проекта;
вы с коллегой садитесь перед клавиатурой и выполняете детальное проекти-
рование на выбранном языке программирования, т. е. вы можете использовать
парное программирование (см. главу 21);
вы назначаете собрание для обсуждения своих идей с одним или несколькими
коллегами;
вы назначаете формальную инспекцию, включающую все аспекты, описанные
в главе 21;
никто не может провести обзор вашей работы, поэтому вы выполняете неко-
торый объем работы, сохраняете ее и возвращаетесь к ней через неделю — вы
забудете достаточно, чтобы самостоятельно провести довольно хороший обзор
своей же работы;
вы обращаетесь за помощью к людям, не работающим в вашей компании: от-
правляете вопросы в специализированный форум или группу новостей.
Если целью является гарантия качества, тогда по причинам, описанным в главе
21, я рекомендую наиболее структурированную методику обзора — формальные
инспекции. Но если цель состоит в содействии творчеству и увеличении числа
предлагаемых вариантов проекта, а не в простом нахождении ошибок, лучше при-
менять менее структурированные подходы. После выбора определенного варианта
проекта может оказаться уместным переход к более формальным инспекциям, что
определяется конкретной ситуацией.
При создании конкретной системы могут иметь место сразу несколько факторов,
подталкивающих к разным решениям: скажем, опытная группа может разраба-
тывать ПО, от которого будет зависеть безопасность людей. Тогда вам, вероятно,
следует отдать предпочтение более высокой детальности и формальности проекта.
Вообще в подобной ситуации нужно оценить роль каждого фактора и решить,
какой из них важнее.
Если разработчики выполняют какую#то часть проектирования индивидуально, то
при снижении проектирования до уровня задачи, которая уже была решена ранее,
или до уровня простого изменения или расширения такой задачи, проектирование,
вероятно, можно прекратить и начать кодирование.
Если я не могу решить, насколько детальным должен быть проект программы
перед началом кодирования, я предпочитаю разрабатывать его более детально.
Самые крупные ошибки проектирования возникали, когда я думал, что выполнил
проектирование в достаточной степени, но позднее оказывалось, что я заблуж-
дался и не учитывал дополнительные проблемы. Иначе говоря, самые серьезные
проблемы проектирования обычно были связаны не с теми областями, которые
я считал сложными и спроектировал неудачно, а с теми, которые я считал легки-
ми и не спроектировал вообще. Мне редко встречаются проекты, страдающие от
чрезмерного проектирования.
114 ЧАСТЬ II Высококачественный код
1
Закон Грешема (Gresham’s Law) — монетарный принцип, названный в честь лорда Томаса
Грешема, управляющего английским монетным двором при королеве Елизавете. Суть закона
Грешема в том, что «худшие» деньги (некачественные или с пониженным содержанием бла-
городного металла) вытесняют «лучшие» деньги из обращения, т. е. более «дешевые» деньги
вытесняют более «дорогие». — Прим. перев.
ГЛАВА 5 Проектирование при конструировании 115
Дополнительные ресурсы
Проектирование ПО описанно во множестве работ. Проблема
http://cc2e.com/0520 в том, чтобы определить, какие из них наиболее полезны.
Позволю себе дать вам некоторые советы.
Теория проектирования ПО
Parnas, David L., and Paul C. Clements. «A Rational Design Process: How and Why to Fake
It». — IEEE Transactions on Software Engineering SE%12, no. 2 (February 1986): 251–57.
В этой классической статье описывается разрыв между реальным и желательным
процессами проектирования программ. Суть статьи в том, что в действительности
никто никогда не следует рациональному упорядоченному процессу проектирования,
но стремление к этому приводит в итоге к созданию лучших проектов.
Работы, в которых было бы приведено исчерпывающее обсуждение сокрытия
информации, мне неизвестны. В большинстве учебников по разработке ПО оно
обсуждается кратко, часто в контексте объектно#ориентированных подходов. На-
верное, до сих пор лучшими материалами по сокрытию информации являются
три статьи, принадлежащие перу Парнаса.
Parnas, David L. «On the Criteria to Be Used in Decomposing Systems into Modules».
— Communications of the ACM 5, no. 12 (December 1972): 1053#58.
Parnas, David L. «Designing Software for Ease of Extension and Contraction». — IEEE
Transactions on Software Engineering SE%5, no. 2 (March 1979): 128#38.
Parnas, David L., Paul C. Clements, and D. M. Weiss. «The Modular Structure of Complex Systems».
— IEEE Transactions on Software Engineering SE%11, no. 3 (March 1985): 259#66.
Шаблоны проектирования
Gamma, Erich, et al. Design Patterns. — Reading, MA: Addison#Wesley, 1995. Очень по-
лезная книга о шаблонах проектирования, написанная «Бандой четырех»1 .
Shalloway, Alan, and James R. Trott. Design Patterns Explained. — Boston, MA: Addison#
Wesley, 2002. Данная книга представляет собой несложное введение в шаблоны
проектирования.
1
«Бандой четырех (Gang of Four)» называют группу авторов, в которую входят Эрих Гамма,
Ричард Хелм, Ральф Джонсон и Джон Влиссидес. — Прим. перев.
118 ЧАСТЬ II Высококачественный код
Проектирование в общем
Adams, James L. Conceptual Blockbusting: A Guide to Better Ideas, 4th ed. — Cambridge,
MA: Perseus Publishing, 2001. Нельзя сказать, что эта книга посвящена непосред-
ственно проектированию ПО, но это не умаляет ее достоинств: она была написана
как учебник по проектированию для студентов инженерного факультета Стэн-
фордского университета. Даже если вы никогда ничего не проектировали и не
проектируете, в ней вы найдете увлекательное обсуждение творческого мышления
и много упражнений, позволяющих развить мышление, для эффективного проек-
тирования. Кроме того, данная книга включает список литературы, посвященной
проектированию и творческому мышлению, с подробными аннотациями. Если
вам нравится решать проблемы, вам понравится эта книга.
Polya, G. How to Solve It: A New Aspect of Mathematical Method, 2d ed. — Princeton,
NJ: Princeton University Press, 1957. Это обсуждение эвристики и решения про-
блем концентрируется на математике, но актуально и для разработки ПО. Книга
Полья стала первым трудом, посвященным применению эвристики для решения
математических проблем. В ней проводится четкое различие между небрежной
эвристикой, используемой для обнаружения решений, и более аккуратными ме-
тодами, которые применяются для представления найденных решений. Читать ее
нелегко, но если вы интересуетесь эвристикой, то в итоге все равно прочитаете ее,
хотите вы того или нет. Полья ясно показывает, что решение проблем не является
детерминированным процессом и что приверженность единственной методоло-
гии аналогично ходьбе в кандалах. Когда#то в Microsoft эту книгу выдавали всем
новым программистам.
Michalewicz, Zbigniew, and David B. Fogel. How to Solve It: Modern Heuristics. — Berlin:
Springer#Verlag, 2000. Это обновленный вариант книги Полья, который содержит
некоторые нематематические примеры и менее требователен к читателю.
Simon, Herbert. The Sciences of the Artificial, 3d ed. — Cambridge, MA: MIT Press, 1996.
В этой интересной книге проводится различие между науками, имеющими дело
с естественным миром (биология, геология и т. д.), и науками, изучающими ис-
кусственный мир, созданный людьми (бизнес, архитектура и информатика). За-
тем в ней обсуждаются характеристики наук об искусственном, при этом особое
внимание уделяется проектированию. Книга написана в академическом стиле, и
ее следует прочитать всем, кто решил сделать карьеру в области разработки ПО
или любой другой «искусственной» области.
Glass, Robert L. Software Creativity. — Englewood Cliffs, NJ: Prentice Hall PTR, 1995. Что
в большей степени управляет процессом разработки ПО: теория или практика?
Является ли он преимущественно творческим или преимущественно детерминиро-
ванным? Какие интеллектуальные качества нужны разработчику ПО? В этой книге
приведено интересное обсуждение природы разработки ПО со специфическим
акцентом на проектировании.
Petroski, Henry. Design Paradigms: Case Histories of Error and Judgment in Engineering.
— Cambridge: Cambridge University Press, 1994. Главная идея этой книги в том, что
анализ прошлых неудач способствует успешному проектированию не в меньшей,
а то и в большей степени, чем исследование прошлых успехов. В подтверждение
своей позиции автор приводит многие факты из области гражданского строи-
тельства (особенно проектирования мостов).
ГЛАВА 5 Проектирование при конструировании 119
Стандарты
IEEE Std 1016%1998, Recommended Practice for Software Design Descriptions. Данный
документ содержит стандарт IEEE#ANSI описания проектов ПО, определяющий,
что следует включать в проектную документацию.
IEEE Std 1471%2000. Recommended Practice for Architectural Description of Software Intensive
Systems. Los Alamitos, CA: IEEE Computer Society Press. Этот документ представляет
собой руководство IEEE#ANSI по созданию спецификаций архитектуры ПО.
Ключевые моменты
Главным Техническим Императивом Разработки ПО является управление слож-
ностью. Управлять сложностью будет гораздо легче, если при проектировании
вы будете стремиться к простоте.
Есть два общих способа достижения простоты: минимизация объема существен-
ной сложности, с которой приходится иметь дело в любой конкретный момент
времени, и подавление необязательного роста несущественной сложности.
Проектирование — эвристический процесс. Слепое следование какой#либо
единственной методологии подавляет творческое мышление и снижает каче-
ство ваших программ.
Оптимальный процесс проектирования итеративен; чем больше вариантов про-
ектирования вы попробуете, тем удачнее будет ваш окончательный проект.
Одной из самых полезных концепций проектирования является сокрытие
информации. Вопрос «Что мне скрыть?» устраняет много сложных проблем
проектирования.
Много полезной и интересной информации о проектировании можно найти
в других книгах. Описанные в этой главе идеи — лишь вершина айсберга.
ГЛАВА 4 Основные решения, которые приходится принимать при конструировании 121
Г Л А В А 6
Классы
Содержание
http://cc2e.com/0665
6.1. Основы классов: абстрактные типы данных
6.2. Качественные интерфейсы классов
6.3. Вопросы проектирования и реализации
6.4. Разумные причины создания классов
6.5. Аспекты, специфические для языков
6.6. Следующий уровень: пакеты классов
Связанные темы
Проектирование при конструировании: глава 5
Архитектура ПО: раздел 3.5
Высококачественные методы: глава 7
Процесс программирования с псевдокодом: глава 9
Рефакторинг: глава 24
На заре компьютерной эпохи программисты думали о программировании в тер#
минах операторов. В 1970–80#е о программах стали думать в терминах методов.
В XXI веке мы рассматриваем программирование в терминах классов.
Класс — это набор данных и методов, имеющих общую, целостную, хо#
рошо определенную сферу ответственности. Данные — необязательный
компонент класса: класс может включать только методы, предоставляю#
щие целостный набор услуг. Одним из главных условий эффективного програм#
мирования является максимизация части программы, которую можно игнориро#
вать при работе над конкретными фрагментами кода. Классы — главное средство
достижения этой цели.
Эта глава содержит экстракт советов по созданию высококачественных классов.
Если вы только знакомитесь с концепциями объектно#ориентированного програм#
мирования, она может показаться слишком сложной. Если вы не прочитали главу
5, вернитесь к ней. Затем начните с раздела 6.1, который поможет понять осталь#
ные разделы главы. Если вы уже знакомы с основами классов, можете, просмот#
рев раздел 6.1, начать серьезное чтение с раздела 6.2, в котором обсуждаются
122 ЧАСТЬ II Высококачественный код
currentFont.size = 16
Создав набор библиотечных методов, код можно было бы сделать чуть понятнее:
currentFont.size = PointsToPixels( 12 )
Кроме того, атрибуту шрифта можно было бы присвоить более определенное имя,
например:
currentFont.sizeInPixels = PointsToPixels( 12 )
Однако при этом вы не смогли бы включить в программу сразу два поля, опреде#
ляющих размер шрифта: currentFont . sizeInPixels (размер шрифта в пикселах) и
currentFont . sizeInPoints (размер шрифта в пунктах), — потому что тогда структура
currentFont не смогла бы узнать, какое из них использовать. Кроме того, изменяя
размеры шрифтов в нескольких местах, вы распространили бы похожие строки
по всей программе.
Для выбора полужирного начертания вы могли бы написать код, использующий
логическое ИЛИ и шестнадцатеричную константу 0x02:
currentFont.attribute = currentFont.attribute or 0x02
Этот код можно немного улучшить, но лучшее, что вы получите, используя спе#
циализированный подход, будет похоже на:
currentFont.attribute = currentFont.attribute or BOLD
Как и в случае с размером шрифта, проблема здесь в том, что клиентский код
должен контролировать элементы данных непосредственно, а это ограничивает
число возможных способов применения структуры currentFont.
Такой подход к программированию способствует распространению похожих строк
кода по всей программе.
currentFont.SetBoldOn()
currentFont.SetBoldOff()
currentFont.SetItalicOn()
currentFont.SetItalicOff()
currentFont.SetTypeFace( faceName )
АТД и классы
Абстрактные типы данных лежат в основе концепции классов. В языках, поддержи#
вающих классы, каждый АТД можно реализовать как отдельный класс. Однако обыч#
но с классами связывают еще две концепции: наследование и полиморфизм. Може#
те рассматривать класс как АТД, поддерживающий наследование и полиморфизм.
Хорошая абстракция
Как я говорил в подразделе «Определите согласованные абстракции» раздела 5.3,
под абстракцией понимается представление сложной операции в упрощенной
форме. Интерфейс класса — это абстракция реализации класса, скрытой за ин#
терфейсом. Интерфейс класса должен предоставлять группу методов, четко согла#
сующихся друг с другом.
Рассмотрим для примера класс «сотрудник». Он может содержать такие данные,
как фамилия сотрудника, адрес, номер телефона и т. д., и предлагать методы ини#
циализации и использования этих данных. Вот как мог бы выглядеть такой класс:
String homePhone,
TaxId taxIdNumber,
JobClassification jobClass
);
virtual ~Employee();
// открытые методы
FullName GetName() const;
String GetAddress() const;
String GetWorkPhone() const;
String GetHomePhone() const;
TaxId GetTaxIdNumber() const;
JobClassification GetJobClassification() const;
...
private:
...
};
В ходе очистки интерфейса одни его методы были перемещены в более подходя#
щие классы, а другие были преобразованы в закрытые методы, используемые
методом InitializeUserInterface() и другими методами.
Данный способ оценки абстракции класса основан на изучении открытых методов
класса, т. е. его интерфейса. Однако из того, что класс в целом формирует хорошую
абстракцию, вовсе не следует, что его отдельные методы также представляют удач#
ные абстракции. Рекомендации по проектированию методов см. в разделе 7.2.
Чтобы ваши классы имели высококачественные абстрактные интерфейсы, соблю#
дайте при их проектировании следующие принципы.
Выражайте в интерфейсе класса согласованный уровень абстракции
Классы полезно рассматривать как механизмы реализации абстрактных типов дан#
ных, описанных в разделе 6.1. В идеале каждый класс должен быть реализацией
только одного АТД. Если класс реализует более одного АТД или если вам не уда#
ется определить, реализацией какого АТД класс является, самое время реоргани#
зовать класс в один или несколько хорошо определенных АТД.
Так, следующий класс имеет несогласованный интерфейс, потому что формируе#
мый им уровень абстракции непостоянен:
То, что начиналось как ясная абстракция, превратилось в смесь почти несогласо#
ванных методов. Между сотрудниками и методами, проверяющими корректность
ГЛАВА 6 Классы 135
почтового индекса, номера телефона или ставки зарплаты (job classification), нет
логической связи. Методы, предоставляющие доступ к деталям SQL#запросов, от#
носятся к гораздо более низкому уровню абстракции, чем класс Employee, нару#
шая общую абстракцию класса.
Не включайте в класс открытые члены, плохо согласующиеся с абстрак'
цией интерфейса Добавляя новый метод в интерфейс класса, всегда спраши#
вайте себя: «Согласуется ли этот метод с абстракцией, формируемой существую#
щим интерфейсом?» Если нет, найдите другой способ внесения изменения, позво#
ляющий сохранить согласованность абстракции.
Рассматривайте абстракцию и связность вместе Понятия абстракции и
связности (cohesion) тесно связаны: интерфейс класса, представляющий хорошую
абстракцию, обычно отличается высокой связностью. И наоборот: классы, имею#
щие высокую связность, обычно представляют хорошие абстракции, хотя эта связь
выражена слабее.
Я обнаружил, что при повышенном внимании к абстракции, формируемой ин#
терфейсом класса, проект класса получается более удачным, чем при концентра#
ции на связности класса. Если вы видите, что класс имеет низкую связность и не
знаете, как это исправить, спросите себя, представляет ли он согласованную аб#
стракцию.
Хорошая инкапсуляция
Как я уже говорил в разделе 5.3, инкапсуляция является бо#
Перекрестная ссылка Об инкап-
лее строгой концепцией, чем абстракция. Абстракция по# суляции см. подраздел «Инкап-
могает управлять сложностью, предоставляя модели, позво# сулируйте детали реализации»
ляющие игнорировать детали реализации. Инкапсуляция не раздела 5.3.
позволяет узнать детали реализации, даже если вы этого
захотите.
Две этих концепции связаны: без инкапсуляции абстракция обычно разрушается.
По своему опыту могу сказать, что вы или имеете и абстракцию, и инкапсуляцию,
или не имеете ни того, ни другого. Промежуточных вариантов нет.
Минимизируйте доступность классов и их членов Ми#
Самым важным отличием хоро-
нимизация доступности — одно из нескольких правил, под# шо спроектированного модуля
держивающих инкапсуляцию. Если вы не можете понять, от плохо спроектированного яв-
каким делать конкретный метод: открытым, закрытым или ляется степень, в которой мо-
защищенным, — некоторые авторы советуют выбирать са# дуль скрывает свои внутренние
данные и другие детали реали-
мый строгий уровень защиты, который работает (Meyers,
зации от других модулей.
1998; Bloch, 2001). По#моему, это прекрасное правило, но
Джошуа Блох (Joshua Bloch)
мне кажется, что еще важнее спросить себя: «Какой вари#
ант лучше всего сохраняет целостность абстракции интер#
фейса?» Если предоставление доступа к методу согласуется с абстракцией, сделайте
его открытым. Если вы не уверены, скрыть больше обычно предпочтительнее, чем
скрыть меньше.
Не делайте данные'члены открытыми Предоставление доступа к данным#
членам нарушает инкапсуляцию и ограничивает контроль над абстракцией. Как
136 ЧАСТЬ II Высококачественный код
указывает Артур Риэль, класс Point (точка), который предоставляет доступ к дан#
ным:
float x;
float y;
float z;
нарушает инкапсуляцию, потому что клиентский код может свободно делать с
данными Point что угодно, при этом сам класс может даже не узнать об их изме#
нении (Riel, 1996). В то же время класс Point, включающий члены:
float GetX();
float GetY();
float GetZ();
void SetX( float x );
void SetY( float y );
void SetZ( float z );
поддерживает прекрасную инкапсуляцию. Вы не имеете понятия о том, реализо#
ваны ли данные как float x, y и z, хранит ли класс Point эти элементы как double,
преобразуя их в float, или же он хранит их на Луне и получает через спутник.
Не включайте в интерфейс класса закрытые детали реализации Истинная
инкапсуляция не позволяла бы узнать детали реализации вообще. Они были бы
скрыты и в прямом, и в переносном смыслах. Однако популярные языки — в том
числе C++ — требуют, чтобы программисты раскрывали детали реализации в
интерфейсе класса, например:
В данном случае можно было бы создать базовый класс и унаследовать от него ряд
производных классов, выполняющих каждую команду при помощи полиморфно#
го метода DoCommand() (как в шаблоне Команда). Но в подобной простой ситуа#
ГЛАВА 6 Классы 145
ции это неуместно: имя метода DoCommand() было бы настолько туманным, что
почти утратило бы всякий смысл, тогда как блоки case довольно информативны.
Делайте все данные закрытыми, а не защищенными Как говорит Джошуа
Блох, «наследование нарушает инкапсуляцию» (Bloch, 2001). Выполняя наследо#
вание от класса, вы получаете привилегированный доступ к его защищенным
методам и данным. Если производному классу на самом деле нужен доступ к ат#
рибутам базового класса, включите в базовый класс защищенные методы доступа.
Множественное наследование
Наследование — мощный и... довольно опасный инструмент.
С множественным наследовани-
В некотором смысле наследование похоже на цепную пилу: ем в C++ связан один неоспори-
при соблюдении мер предосторожности оно может быть мый факт: оно открывает ящик
невероятно полезным, но при неумелом обращении послед# Пандоры, полный проблем, ко-
ствия могут оказаться очень и очень серьезными. торые просто невозможны при
единичном наследовании.
Если наследование — цепная пила, то множественное на#
Скотт Мейерс
следование — это старинная цепная пила с барахлящим (Scott Meyers)
мотором, не имеющая предохранителей и не поддержива#
ющая автоматического отключения. Иногда такой инструмент может пригодить#
ся, но большую часть времени его лучше хранить в гараже под замком.
Некоторые эксперты рекомендуют широкое применение множественного насле#
дования (Meyer, 1997), но по опыту могу сказать, что оно полезно главным обра#
зом только при создании «миксинов» — простых классов, позволяющих добавить
ряд свойств в другой класс. Миксины называются так потому, что они позволяют
«подмешать (mix in)» свойства в производные классы. Миксинами могут быть классы
вроде Displayable, Persistent, Serializable или Sortable. Миксины почти всегда явля#
ются абстрактными и не поддерживают создания экземпляров независимо от
других объектов.
Миксины требуют множественного наследования, но пока все миксины по#насто#
ящему независимы друг от друга, вы можете не бояться классической проблемы,
связанной с ромбовидной схемой наследования. Кроме того, «объединяя» атри#
буты, они делают проект системы понятнее. Программисту легче разобраться с
объектом, использующим миксины Displayable и Persistent, а не 11 более конкрет#
ных методов, которые понадобились бы для реализации этих двух свойств в про#
тивном случае.
Похоже, разработчики Java и Visual Basic понимали ценность миксинов, разрешив
множественное наследование интерфейсов, но только единичное наследование
классов. C++ поддерживает множественное наследование и интерфейсов, и реа#
лизации. Используйте множественное наследование, только тщательно рассмот#
рев все альтернативные варианты и проанализировав влияние выбранного под#
хода на сложность и понятность системы.
Методы-члены и данные-члены
Ниже я даю несколько советов по эффективной реализации
Перекрестная ссылка О методах
в общем см. главу 7. методов#членов и данных#членов.
Включайте в класс как можно меньше методов
В одном исследовании программ на C++ было обнаружено, что большему числу
методов в расчете на один класс соответствует большее число изъянов (Basili, Briand,
and Melo, 1996). Однако важнее оказались другие конкурирующие факторы, в том
числе многоуровневые иерархии наследования, большое число методов, вызыва#
емых из класса, и сильное сопряжение между классами. Разрабатывая класс, стре#
митесь к оптимальному соответствию между этими факторами и минимальным
числом методов.
Блокируйте неявно сгенерированные методы и операторы, которые вам
не нужны Иногда некоторые возможности, такие как создание объекта или при#
сваивание, целесообразно блокировать. Вам может показаться, что сделать это
невозможно, потому что компилятор генерирует эти операции автоматически.
Однако вы можете запретить их использование в клиентском коде, объявив кон#
структор, оператор присваивания или другой метод или оператор как private.
(Создание закрытого конструктора — стандартный способ определения класса#
одиночки, о чем см. ниже.)
Минимизируйте число разных методов, вызываемых классом Одно иссле#
дование показало, что число дефектов в коде класса статистически коррелирует
с общим числом методов, вызываемых классом (Basili, Briand, and Melo, 1996). То
же исследование показало, что число дефектов в коде класса повышается и при
увеличении числа используемых в нем классов. Эти концепции иногда называют
«коэффициентом разветвления по выходу (fan out)».
Избегайте опосредованных вызовов методов других классов Непосред#
ственные связи довольно опасны. Опосредованные связи, такие как account.Con%
tactPerson() . DaytimeContactInfo() . PhoneNumber(), опасны еще больше. В связи с этим
ученые сформулировали «Правило Деметры (Law of Demeter)» (Lieberherr and
Holland, 1989), которое гласит, что Объект A может вызывать любые из собствен#
ных методов. Если он создает Объект B, он может вызывать любые методы Объекта
ГЛАВА 6 Классы 147
Конструкторы
Советы по использованию конструкторов почти не зависят от языка (по крайней
мере это касается C++, Java и Visual Basic). С деструкторами связано больше раз#
личий — см. материалы, указанные в разделе «Дополнительные ресурсы».
Инициализируйте по мере возможности все данные'члены во всех кон'
структорах Инициализация всех данных#членов во всех конструкторах — про#
стой прием защитного программирования.
Создавайте классы'одиночки с помощью закрытого
Дополнительные сведения Ана-
конструктора Если вы хотите определить класс, позво# логичный код, написанный на
ляющий создать только один объект, скройте все конструк# C++, был бы очень похож. Под-
торы класса и создайте статический метод GetInstance(), пре# робнее см. раздел 26 книги «More
доставляющий доступ к единственному экземпляру класса: Effective C++» (Meyers, 1998).
Закрытый конструктор.
Дополнительные ресурсы
Классы в общем
Meyer, Bertrand. Object%Oriented Software Construction, 2d ed. —
New York, NY: Prentice Hall PTR, 1997. В этой книге Мейер http://cc2e.com/0679
рассматривает абстрактные типы данных и объясняет, как они
формируют основу классов. В главах 14–16 подробно обсуждается наследование.
В главе 15 Мейер приводит довод в пользу множественного наследования.
Riel, Arthur J. Object%Oriented Design Heuristics. — Reading, MA: Addison#Wesley, 1996.
Эта книга включает множество советов по улучшению проектирования, относя#
щихся большей частью к уровню классов. Я избегал ее несколько лет, потому что
она казалась слишком большой — воистину сапожник без сапог! Однако основ#
ная часть книги занимает только около 200 страниц. Книга написана доступным
и занимательным языком, а ее содержание сжато и практично.
C++
Meyers, Scott. Effective C++: 50 Specific Ways to Improve Your
Programs and Designs, 2d ed. — Reading, MA: Addison#Wesley, http://cc2e.com/0686
1998.
Meyers, Scott. More Effective C++: 35 New Ways to Improve Your Programs and Designs.
— Reading, MA: Addison#Wesley, 1996. Обе книги Мейерса являются канонически#
ми для программистов на C++. Они очень интересны и позволяют приобрести глу#
бокие знания некоторых нюансов C++.
Java
Bloch, Joshua. Effective Java Programming Language Guide. —
Boston, MA: Addison#Wesley, 2001. В книге Блоха можно найти http://cc2e.com/0693
много полезных советов по Java, а также описания более
общих объектно#ориентированных подходов.
Visual Basic
Ниже указаны книги, в которых хорошо рассмотрена работа
с классами в контексте Visual Basic. http://cc2e.com/0600
Ключевые моменты
Интерфейс класса должен формировать согласованную абстракцию. Многие
проблемы объясняются нарушением одного этого принципа.
Интерфейс класса должен что#то скрывать — особенности взаимодействия с
системой, аспекты проектирования или детали реализации.
Включение обычно предпочтительнее, чем наследование, если только вы не
моделируете отношение «является».
Наследование — полезный инструмент, но оно повышает сложность, что про#
тиворечит Главному Техническому Императиву Разработки ПО, которым явля#
ется управление сложностью.
Классы — главное средство управления сложностью. Уделите их проектирова#
нию столько времени, сколько нужно для достижения этой цели.
ГЛАВА 7 Высококачественные методы 157
Г Л А В А 7
Высококачественные
методы
Содержание
http://cc2e.com/0778
7.1. Разумные причины создания методов
7.2. Проектирование на уровне методов
7.3. Удачные имена методов
7.4. Насколько объемным может быть метод?
7.5. Советы по использованию параметров методов
7.6. Отдельные соображения по использованию функций
7.7. Методы#макросы и встраиваемые методы
Связанные темы
Этапы конструирования методов: раздел 9.3
Классы: глава 6
Общие методики проектирования: глава 5
Архитектура ПО: раздел 3.5
В главе 6 мы подробно рассмотрели создание классов. В этой главе мы обратим
внимание на методы и характеристики, отличающие хорошие методы от плохих.
Если вам хотелось бы сначала разобраться в вопросах, влияющих на проектиро#
вание методов, прочитайте главу 5 и потом вернитесь к этой главе. Некоторые
важные атрибуты высококачественных методов обсуждаются также в главе 8. Если
вас больше интересуют этапы создания методов и классов, см. главу 9.
Прежде чем перейти к деталям, определим два базовых термина. Что такое «метод»?
Метод — это отдельная функция или процедура, выполняющая одну задачу. В раз#
личных языках методы могут называться по#разному, но их суть от этого не меня#
ется. Иногда макросы C и C++ также полезно рассматривать как методы. Многие
советы по созданию высококачественных методов относятся и к макросам.
Что такое высококачественный метод? На этот вопрос ответить сложнее. Возможно,
лучше всего просто показать, что не является высококачественным методом. Вот
пример низкокачественного метода:
158 ЧАСТЬ II Высококачественный код
Пример низкокачественного
метода (C++)
Наверняка это не самая сложная строка кода в вашей жизни. Большинство людей
в итоге поняло бы, что она преобразует некоторую величину, выраженную в ап#
паратных единицах, в соответствующее число точек, а кроме того, что каждая из
десятка строк делает одно и то же. Однако эти фрагменты можно было сделать
еще более ясными, поэтому я создал метод с выразительным именем, выполняю#
щий преобразование в одном месте:
else
DeviceUnitsToPoints = 0
end if
End Function
две функции. Наша цель в том, чтобы каждый метод эффективно решал одну за#
дачу и больше ничего не делал.
Вознаграждением будет более высокая надежность кода. В одном иссле#
довании 450 методов было обнаружено, что дефекты отсутствовали в 50%
методов, обладающих высокой связностью, и только в 18% методов с
низкой связностью (Card, Church, and Agresti, 1986). Другое исследование 450
методов (это просто совпадение, хотя и весьма необычное) показало, что в срав#
нении с методами, имеющими самое низкое отношение «сопряжение/связность»
(coupling#to#cohesion), методы с максимальным отношением «сопряжение/связ#
ность» содержали в 7 раз больше ошибок, а исправление этих методов было в 20
раз более дорогим (Selby and Basili, 1991).
Обсуждение связности обычно касается нескольких ее уровней. Понять эти кон#
цепции важнее, чем запомнить специфические термины. Используйте концепции
как средства, помогающие сделать методы максимально связными.
Функциональная связность — самый сильный и лучший вид связности; она име#
ет место, когда метод выполняет одну и только одну операцию. Примерами мето#
дов, обладающих высокой связностью, являются методы sin() (синус), GetCusto%
merName() (получить фамилию заказчика), EraseFile() (удалить файл), Calculate%
LoanPayment() (вычислить плату за кредит) и AgeFromBirthdate() (определить воз#
раст по дате рождения). Конечно, такая оценка связности предполагает, что эти
методы соответствуют своим именам — иначе они имеют неудачные имена, а об
их связности нельзя сказать ничего определенного.
Ниже описаны другие виды связности, которые обычно считаются менее эффек#
тивными.
Последовательная связность (sequential cohesion) наблюдается в том случае,
когда метод содержит операции, которые обязательно выполняются в опре#
деленном порядке, используют данные предыдущих этапов и не формируют в
целом единую функцию.
Примером метода с последовательной связностью является метод, вычисляю#
щий по дате рождения возраст сотрудника и срок до его ухода на пенсию. Если
метод вычисляет возраст и затем использует этот результат для нахождения
срока до ухода сотрудника на пенсию, он имеет последовательную связность.
Если метод находит возраст сотрудника, после чего в абсолютно другом вы#
числении определяет срок до ухода на пенсию, применяя те же данные о дате
рождения, он имеет только коммуникационную связность.
Как сделать такой метод функционально связным? Создать два отдельных ме#
тода: метод, вычисляющий по дате рождения возраст сотрудника, и метод,
определяющий по дате рождения срок до ухода сотрудника на пенсию. Вто#
рой метод мог бы вызывать метод нахождения возраста. Оба этих метода име#
ли бы функциональную связность. Другие методы могли бы вызывать любой
из них или оба.
Коммуникационная связность (communicational cohesion) имеет место, когда вы#
полняемые в методе операции используют одни и те же данные и не связаны между
собой иным образом. Если метод печатает отчет, после чего заново инициализи#
ГЛАВА 7 Высококачественные методы 165
Класс Employee предоставлял доступ к объекту id, который в свою очередь вклю#
чал метод Get(). Класс Dependent предоставлял для этой цели метод GetId(). Разра#
ГЛАВА 7 Высококачественные методы 169
1
Пилигримы (pilgrims) — пассажиры английского судна «Мейфлауэр» («Mayflower»), основатели
Плимутской колонии в Северной Америке, заключившие Мейфлауэрское соглашение (Mayflower
Compact) о создании «гражданской политической организации» для поддержания порядка и
безопасности, «принятия справедливых и обеспечивающих равноправие законов». Плимутский
камень (Plymouth Rock) — по преданию гранитная глыба, на которую ступил первый сошед#
ший с корабля пилигрим в декабре 1620 г. Почитается в США как национальная святыня. — Прим.
перев.
170 ЧАСТЬ II Высококачественный код
1990). Разработка крупных методов (65 строк или более) в расчете на одну
строку кода была дешевле.
Опытное изучение 450 методов показало, что небольшие методы (включавшие
менее 143 команд исходного кода с учетом комментариев) содержали на 23%
больше ошибок в расчете на строку кода, чем более крупные методы, но ис#
правление меньших методов было в 2,4 раза менее дорогим (Selby and Basili,
1991).
Исследования позволили обнаружить, что код требовал минимальных измене#
ний, если методы состояли в среднем из 100–150 строк (Lind and Vairavan, 1989).
Исследование, проведенное в IBM, показало, что максимальный уровень оши#
бок был характерен для методов, размер которых превышал 500 строк кода.
При дальнейшем увеличении методов уровень ошибок возрастал пропорцио#
нально числу строк (Jones, 1986a).
Так какую же длину методов считать приемлемой в объектно#ориентированных
программах? Многие методы в объектно#ориентированных программах будут ме#
тодами доступа, обычно очень короткими. Время от времени реализация сложно#
го алгоритма будет требовать создания более длинного метода, и тогда методу можно
будет позволить вырасти до 100–200 строк (строкой считается непустая строка
исходного кода, не являющаяся комментарием). Десятилетия исследований гово#
рят о том, что методы такой длины не более подвержены ошибкам, чем методы
меньших размеров. Пусть длину метода определяют не искусственные ограниче#
ния, а такие факторы, как связность метода, глубина вложенности, число перемен#
ных, число точек принятия решений, число комментариев, необходимых для объяс#
нения метода, и другие соображения, связанные со сложностью кода.
Что касается методов, включающих более 200 строк, то к ним следует относиться
настороженно. Ни в одном из исследований, в которых было обнаружено, что более
крупным методам соответствует меньшая стоимость разработки, меньший уровень
ошибок или оба фактора, эта тенденция не усиливалась при увеличении размера
свыше 200 строк, а при превышении этого предела методы неизбежно становят#
ся менее понятными.
В языке Ada ключевые слова in и out поясняют суть входных и выходных параметров.
procedure ChangeSentenceCase(
desiredCase: in StringCase;
sentence: in out Sentence
);
...
procedure PrintPageNumber(
pageNumber: in Integer;
status: out StatusType
);
void ChangeSentenceCase(
IN StringCase desiredCase,
IN OUT Sentence *sentenceToEdit
);
...
172 ЧАСТЬ II Высококачественный код
void PrintPageNumber(
IN int pageNumber,
OUT StatusType &status
);
В этом фрагменте переменная inputVal вводит в заблуждение, потому что при за#
вершении метода она больше не содержит входного значения; она содержит резуль#
тат вычисления, частично основанного на входном значении, и поэтому ее имя
неудачно. Если позднее вам придется задействовать первоначальное входное зна#
чение в другом месте метода, вы, вероятно, задействуйте переменную inputVal, пред#
полагая, что она содержит первоначальное значение, но это предположение бу#
дет ошибочным.
Можно ли решить эту проблему путем переименования inputVal? Наверное, нет.
Переменной можно было бы присвоить имя вроде workingVal, но такое решение было
бы неполным, так как это имя не говорит о том, что первоначальное значение пе#
ременной передается в метод извне. Вы могли бы присвоить ей нелепое имя input%
ValThatBecomesWorkingVal (входное значение, которое становится рабочим значе#
нием) или сдаться и просто назвать ее x или val, но все эти подходы неудачны.
Лучше избегать настоящих и будущих проблем, используя рабочие переменные
явно, например:
Если первоначальное значение inputVal понадобится здесь или где#то еще, оно все
еще доступно.
...
return workingVal;
}
174 ЧАСТЬ II Высококачественный код
Цель уже близка. Однако, если вы используете макрос Cube() в выражении, включа#
ющем операции с более высоким приоритетом, чем умножение, выражение (a)*(a)*(a)
будет вычислено неверно. Что делать? Заключите в скобки все выражение:
Этот макрос работает не так, как работал бы обычный метод: единственной час#
тью макроса, выполняемой в цикле for, является первая строка:
index = (key - 10) / 5;
Чтобы устранить эту проблему, заключите макрос в фигурные скобки:
Встраиваемые методы
Язык C++ поддерживает ключевое слово inline, служащее для определения встра#
иваемых методов. Иначе говоря, программист может разрабатывать код как ме#
тод, но во время компиляции компилятор постарается встроить каждый экземп#
ляр метода прямо в код. Теоретически встраивание методов может повысить бы#
стродействие кода, позволяя избежать затрат, связанных с вызовами методов.
Не злоупотребляйте встраиваемыми методами Встраиваемые методы на#
рушают инкапсуляцию, потому что C++ требует, чтобы программист поместил код
встраиваемого метода в заголовочный файл, доступный остальным программистам.
При встраивании метода каждый его вызов заменяется на полный код метода, что
во всех случаях увеличивает объем кода и само по себе может создать проблемы.
Практическое применение встраивания аналогично применению прочих мето#
дик повышения быстродействия кода: профилируйте код и оценивайте результа#
ты. Если ожидаемое повышение быстродействия не оправдывает забот, связанных
с профилированием, нужным для проверки выгоды, оно не оправдывает и сни#
жения качества кода.
Ключевые моменты
Самая важная, но далеко не единственная причина создания методов — улуч#
шение интеллектуальной управляемости программы. Сокращение кода — не
такая уж и важная причина; повышение его удобочитаемости, надежности и
облегчение его изменения куда важнее.
Иногда огромную выгоду можно извлечь, создав отдельный метод для простой
операции.
Связность методов можно разделить на несколько видов. Самая лучшая — функ#
циональная — достижима практически всегда.
Имя метода является признаком его качества. Плохое, но точное имя часто
указывает на плохое проектирование метода. Плохое и неточное имя не опи#
сывает роль метода. Как бы то ни было, плохое имя предполагает, что программу
нужно изменить.
Функцию следует использовать, только когда главной целью метода является
возврат конкретного значения, описываемого именем функции.
Добросовестные программисты используют методы#макросы с осторожностью
и только в крайнем случае.
182 ЧАСТЬ II Высококачественный код
Г Л А В А 8
Защитное
программирование
Содержание
http://cc2e.com/0861
8.1. Защита программы от неправильных входных данных
8.2. Утверждения
8.3. Способы обработки ошибок
8.4. Исключения
8.5. Изоляция повреждений, вызванных ошибками
8.6. Отладочные средства
8.7. Доля защитного кода в промышленной версии
8.8. Защита от защитного программирования
Связанные темы
Сокрытие информации: подраздел «Скрывайте секреты (к вопросу о сокрытии
информации)» раздела 5.3
Дизайн изменений: подраздел «Определите области вероятных изменений» раз#
дела 5.3
Архитектура программного обеспечения: раздел 3.5
Дизайн в проектировании: глава 5
Отладка: глава 23
Защитное программирование не означает защиту своего кода словами:
«Это так работает!» Его идея совпадает с идеей внимательного вождения,
при котором вы готовы к любым выходкам других водителей: вы не по#
страдаете, даже если они совершат что#то опасное. Вы берете на себя ответствен#
ность за собственную защиту и в тех случаях, когда виноват другой водитель.
В защитном программировании главная идея в том, что если методу передаются
некорректные данные, то его работа не нарушится, даже если эти данные испор#
чены по вине другой программы. Обобщая, можно сказать, что в программах всегда
будут проблемы, программы будут модифицироваться и разумный программист
будет учитывать это при разработке кода.
ГЛАВА 8 Защитное программирование 183
1
«Garbage in, garbage out». Возможно, эту их школьную поговорку следует перевести нашей сту#
денческой: «Каков стол, таков и стул». — Прим. перев.
184 ЧАСТЬ II Высококачественный код
Рис. 8'1. Часть плавучего моста Interstate%90 в Сиэтле затонула во время шторма,
потому что резервуары были оставлены открытыми. Они наполнились водой,
и мост стал слишком тяжел, чтобы держаться на плаву. Обеспечение защиты
от мелочей во время проектирования может значить больше, чем кажется
8.2. Утверждения
Утверждение (assertion) — это код (обычно метод или макрос), используемый во
время разработки, с помощью которого программа проверяет правильность сво#
его выполнения. Если утверждение истинно, то все работает так, как ожидалось.
Если ложно — значит, в коде обнаружена ошибка. Например, если система пред#
полагает, что длина файла с информацией о заказчиках никогда не будет превы#
шать 50 000 записей, программа могла бы содержать утверждение, что число за#
писей меньше или равно 50 000. Пока это число меньше или равно 50 000, утвер#
ГЛАВА 8 Защитное программирование 185
ждение будет хранить молчание. Но как только записей станет больше 50 000, оно
громко провозгласит об ошибке в программе.
Утверждения особенно полезны в больших и сложных программах, а также
в программах, требующих высокой надежности. Они позволяют нам
быстрее выявить несоответствия в интерфейсах, ошибки, вкравшиеся при
изменении кода и т. п.
Обычно утверждение принимает два аргумента: логическое выражение, описыва#
ющее предположение, которое должно быть истинным, и сообщение, выводимое
в противном случае. Вот как будет выглядеть утверждение на языке Java, если пе#
ременная denominator должна быть ненулевой:
’ Предусловия
Debug.Assert ( 90 <= latitude And latitude <= 90 )
Debug.Assert ( 0 <= longitude And longitude < 360 )
Debug.Assert ( 500 <= elevation And elevation <= 75000 )
...
188 ЧАСТЬ II Высококачественный код
’ Постусловия
Debug.Assert ( 0 <= returnVelocity And returnVelocity <= 600 )
’ Возвращаемое значение
Velocity = returnVelocity
End Function
’ Предусловия
Так выглядит код утверждения.
> Debug.Assert ( 90 <= latitude And latitude <= 90 )
Debug.Assert ( 0 <= longitude And longitude < 360 )
Debug.Assert ( 500 <= elevation And elevation <= 75000 )
...
8.4. Исключения
Исключения — это специальное средство, позволяющее передать в вызывающий
код возникшие ошибки или исключительные ситуации. Если код в некотором
методе встречает неожиданную ситуацию и не знает, как ее обработать, то он
генерирует исключение, т. е. фактически умывает руки со словами: «Я не знаю, что
с этим делать, надеюсь, кто#нибудь другой знает, как на это реагировать!» Код, не
имеющий понятия о контексте ошибки, может вернуть управление другой части
системы, которая, возможно, лучше знает, как интерпретировать ошибку и сде#
лать с ней что#то осмысленное.
Кроме того, исключения могут быть полезны для упрощения запутанной логики
участка кода, как в примере «Переписатьс помощью try%finally» в разделе 17.3. Вот
принцип действия исключений: метод, применяя оператор throw, создает объект#
исключение. Код какого#либо другого метода, стоящего выше в иерархии вызо#
вов, перехватит это исключение в блоке try%catch.
Популярные языки программирования по#разному реализуют исключения (табл. 8.1):
Табл. 8-1. Поддержка исключений в популярных языках программирования
Параметры
обработки
исключений C++ Java Visual Basic
Поддержка try%catch Да. Да. Да.
Поддержка Нет. Да. Да.
try%catch%finally
Что генерируется Объект класса Excep% Объект класса Объект класса
tion или производно# Exception или про# Exception или про#
го от него, указатель изводного от него. изводного от него.
на объект, объектная
ссылка, другие типы
данных, например,
строка или целое число.
Эффект при Вызывается функция Если это «проверяе# Программа
не перехваченном std::unexpected(), кото# мое исключение», завершает работу.
исключении рая по умолчанию вы# то прекращается
зывает std::terminate(), работа потока, в ко#
в свою очередь по умол# тором оно возникло.
чанию вызывающая Если это «исключе#
функцию abort(). ние периода выпол#
нения», то оно
игнорируется.
Такой подход говорит о том, что либо код внутри блока try генерирует исключе#
ние без причины, либо код в блоке catch не обрабатывает возможную исключи#
тельную ситуацию. Выясните, в чем суть проблемы, и исправьте блоки try или catch.
Изредка можно столкнуться с ситуацией, когда исключение более низкого уровня
не соответствует уровню абстракции вызывающего метода. В этом случае хотя бы
задокументируйте, почему блок catch должен быть пустым. Это можно сделать в
комментариях или записав сообщение в файл журнала, например:
End Sub
Рис. 8'2. Выделение части кода для работы с непроверенными данными, а части —
для работы с только корректными данными, может быть эффективным способом
освободить большую часть программы от ответственности за проверку
допустимости данных
#endif
DebugCode(
Этот код добавляется или удаляется в зависимости от того, определен ли символ DEBUG.
> statement 1;
statement 2;
...
statement n;
ГЛАВА 8 Защитное программирование 203
);
...
Вопросы безопасности
Действительно ли код, проверяющий некорректные входные данные, конт-
ролирует попытки переполнения буфера, внедрения SQL- и HTML-кода, пе-
реполнения целых чисел и других злонамеренных действий?
Все ли ошибочные коды возврата проверяются?
Все ли исключения перехватываются?
Не содержат ли сообщения об ошибках информацию, которая может помочь
злоумышленнику взломать систему?
Дополнительные ресурсы
Просмотрите следующие ресурсы по защитному програм#
мированию. http://cc2e.com/0875
Безопасность
Howard, Michael, and LeBlanc David. Writing Secure Code, 2d ed. Redmond, WA: Microsoft
Press, 2003. Авторы обсуждают важность вопроса доверия ко входным данным для
безопасности системы. Книга открывает глаза на то, какими многочисленными
способами может быть нарушена работа программы. Некоторые из них связаны
с проектированием, но большинство — нет. Книга охватывает весь диапазон воп#
росов о требованиях к системе, проектировании, кодировании и тестировании.
Утверждения
Maguire, Steve. Writing Solid Code. Redmond, WA: Microsoft Press, 1993. Глава 2 содер#
жит отличное обсуждение вопросов применения утверждений, в том числе несколь#
ко интересных примеров утверждений из широко известных продуктов Microsoft.
Stroustrup, Bjarne. The C++ Programming Language, 3d ed. Reading, MA: Addison#Wesley,
1997. В разделе 24.3.7.2 описано несколько вариантов реализации утверждений в
C++, включая взаимосвязь утверждений и пред# и постусловий.
Meyer, Bertrand. Object%Oriented Software Construction, 2d ed. New York, NY: Prentice Hall
PTR, 1997. Эта книга содержит наиболее полное обсуждение пред# и постусловий.
Исключения
Meyer, Bertrand. Object%Oriented Software Construction, 2d ed. New York, NY: Prentice
Hall PTR, 1997. Глава 12 содержит подробное обсуждение обработки исключений.
Stroustrup, Bjarne. The C++ Programming Language, 3d ed. Reading, MA: Addison#Wesley,
1997. В главе 14 подробно обсуждается обработка исключений на C++. Раздел 14.11
содержит отличное резюме, состоящее из 21 совета по использованию исключе#
ний в C++.
Meyers, Scott. More Effective C++: 35 New Ways to Improve Your Programs and Designs.
Reading, MA: Addison#Wesley, 1996. В темах 9–15 описаны разнообразные нюансы
по обработке исключений в C++.
Arnold, Ken, James Gosling, and David Holmes. The Java Programming Language, 3d
ed. Boston, MA: Addison#Wesley, 2000. Глава 8 содержит обсуждение обработки ис#
ключений на Java.
208 ЧАСТЬ II Высококачественный код
Bloch, Joshua. Effective Java Programming Language Guide. Boston, MA: Addison#Wesley,
2001. В темах 39–47 описаны нюансы обработки исключений на Java.
Foxall, James. Practical Standards for Microsoft Visual Basic .NET. Redmond, WA: Microsoft
Press, 2003. В главе 10 описана обработка исключений на Visual Basic.
Ключевые моменты
Промышленный код должен обрабатывать ошибки более изощренно, чем по
принципу «мусор на входе — мусор на выходе».
С помощью технологии защитного программирования ошибки легче находить,
легче исправлять, и они наносят меньше вреда промышленному коду.
Утверждения позволяют обнаружить ошибки на ранней стадии, особенно в
больших системах, системах повышенной надежности и в системах с часто
изменяемым кодом.
Выбор способа обработки некорректных входных данных — это ключевое
решение обработки ошибок, принимаемое на этапе высокоуровневого проек#
тирования.
Исключения предоставляют возможность обработки ошибок в измерении,
отличном от нормального хода алгоритма. Если они используются с осторож#
ностью, то являются важным дополнением в интеллектуальном инструменталь#
ном наборе программиста. Применять их следует после сравнения с другими
технологиями обработки ошибок.
Ограничения, применяемые к промышленной версии системы, не обязатель#
но должны относиться и ко времени разработки. Пользуясь этим преимуще#
ством, вы можете добавлять в отладочную версию любой код, помогающий
быстро выявлять ошибки.
ГЛАВА 9 Процесс программирования с псевдокодом 209
Г Л А В А 9
Процесс программирования
с псевдокодом
Содержание
http://cc2e.com/0936
9.1. Этапы создания классов и методов
9.2. Псевдокод для профи
9.3. Конструирование процедур с использованием ППП
9.4. Альтернативы ППП
Связанные темы
Создание высококачественных классов: глава 6
Характеристики высококачественных методов: глава 7
Проектирование при конструировании: глава 5
Стиль комментирования: глава 32
Всю эту книгу можно рассматривать как подробное описание процесса програм#
мирования, в результате которого создаются классы и методы, и в данной главе
этапы этого процесса рассматриваются во всех деталях. Здесь описывается «про#
граммирование в малом» — конкретные шаги построения отдельных классов и
составляющих их методов, шаги, неизбежные в проекте любого размера. В этой
главе также рассмотрен процесс программирования с псевдокодом (ППП, Pseu#
docode Programming Process), уменьшающий объем работы по проектированию
и документированию и улучшающий качество и первого, и второго.
Если вы опытный программист, можете пропустить эту главу, но взгляните на обзор
этапов и просмотрите советы по конструированию методов с помощью ППП в
разделе 9.3. Некоторые программисты применяют описываемый процесс на пол#
ную, поскольку он приносит большую выгоду.
ППП — это не единственная процедура создания классов и методов. В разделе 9.4
описаны наиболее популярные альтернативные подходы, включая разработку с
изначальными тестами и проектирование по контракту.
210 ЧАСТЬ II Высококачественный код
1
К сожалению, эти советы хорошо применимы лишь для псевдокода на английском языке или
для случаев использования экзотических языков программирования с конструкциями, осно#
ванными на естественном языке, отличном от английского. — Прим. перев.
ГЛАВА 9 Процесс программирования с псевдокодом 213
Проектирование метода
Определив состав методов класса, приступайте к их проек#
Перекрестная ссылка О других
тированию. Допустим, вы хотите написать метод вывода
аспектах проектирования см.
главы с 5 по 8. сообщения об ошибке, основанного на коде ошибки, и на#
звали этот метод ReportErrorMessage(). Вот неформальная
спецификация ReportErrorMessage():
ReportErrorMessage() принимает в качестве входного параметра код ошибки и
выводит сообщение об ошибке, соответствующее этому коду. Он отвечает за
обработку недопустимых кодов. Если программа интерактивная, ReportError%
Message() выводит сообщение пользователю. Если она работает в режиме ко#
мандной строки, ReportErrorMessage() заносит сообщение в файл. После выво#
да сообщения ReportErrorMessage() возвращает значение статуса, указывающее,
успешно ли он завершился.
Этот метод используется в качестве примера во всей главе, а в оставшейся части
этого раздела описывается его проектирование.
Проверка предварительных условий Прежде чем что#
Перекрестная ссылка О провер-
ке предварительных условий см. либо делать с самой процедурой, убедитесь, что функции
главы 3 и 4. метода четко определены и соответствуют общим проект#
ным решениям.
Определите задачу, решаемую методом Сформулируйте задачу, решаемую
методом настолько детально, чтобы можно было переходить созданию метода. Если
проект высокого уровня достаточно подробен, эта работа уже сделана. Проект
верхнего уровня должен содержать по крайней мере:
информацию, скрываемую методом;
входные данные;
выходные данные;
предусловия, которые гарантированно должны соблюдать#
Перекрестная ссылка О пред- и
ся до вызова метода (входные значения находятся в задан#
постусловиях см. раздел 8.2.
ном диапазоне, потоки инициализированы, файлы открыты
или закрыты, буферы заполнены или очищены и т. д.);
ГЛАВА 9 Процесс программирования с псевдокодом 215
Методы могут обрабатывать ошибки разными способами, и вам нужно четко опре#
делиться с одним из них. Если стратегию обработки ошибок определяет архитек#
тура программы, вы можете просто следовать этой стратегии. В других случаях вам
следует решить, какой подход будет оптимален в данном конкретном случае.
Думайте об эффективности В зависимости от ситуации вы можете подхо#
дить к эффективности одним из двух способов. В первом случае — в подавляю#
щем большинстве систем — эффективность некритична. При этом убедитесь, что
интерфейс метода достаточно абстрагирован, а код читабелен и при необходи#
мости вы сможете его легко усовершенствовать. При хорошей инкапсуляции вы
сможете заменить медленные, ресурсоемкие конструкции языка высокого уров#
ня более эффективным алгоритмом или реализацией на быстром, компактном язы#
ке низкого уровня, не затронув при этом другие методы.
Во втором случае — в незначительном числе систем — про#
Перекрестная ссылка Об эф-
изводительность критична. Проблемы производительности
фективности см. главы 25 и 26.
могут быть связаны с недостатком соединений с базой дан#
ных, ограниченной памятью, малым количеством доступных дескрипторов и дру#
гими ресурсами. Архитектура должна указывать, сколько ресурсов каждому мето#
ду (или классу) может быть предоставлено и как быстро он должен выполнять свои
операции.
Как правило, не стоит тратить много усилий на оптимизацию отдельных мето#
дов. Эффективность в основном определяется конструкцией высокого уровня.
Обычно микрооптимизация выполняется, только когда закончена вся программа
и выясняется, что высокоуровневая конструкция исчерпала свои возможности
обеспечить нужную производительность. Не теряйте время на вылизывание от#
дельных методов, пока не выяснится, что это необходимо.
Исследуйте алгоритмы и типы данных Если доступные стандартные биб#
лиотеки не предоставляют нужной функциональности, имеет смысл исследовать
литературу с описанием алгоритмов. Если вы нашли подходящий готовый алго#
ритм, корректно адаптируйте его к применяемому вами языку программирования.
Пишите псевдокод У вас не будет сложностей, если вы прошли все предыду#
щие этапы, основное назначение которых в том, чтобы у вас сложилось четкое
понимание того, что нужно писать.
Закончив предыдущие этапы, можно приступать к написа#
Перекрестная ссылка Это обсуж-
дение предполагает, что при нию высокоуровневого псевдокода. Запускайте редактор
создании псевдокода метода кода или интегрированную среду разработки и пишите
применялись правильные спосо- псевдокод, который станет основой исходного текста про#
бы проектирования (см. главу 5). граммы.
Начните с основных моментов, а затем детализируйте их.
Самая главная часть метода — заголовок#комментарий, описывающий действия
метода, так что начните с краткой формулировки назначения метода. Написание
этой формулировки поможет вам прояснить ваше понимание метода. Если вы
испытываете затруднения при написании этого обобщенного комментария, вам,
видимо, следует лучше разобраться с ролью этого метода. Вот пример краткого
заголовка#комментария метода:
ГЛАВА 9 Процесс программирования с псевдокодом 217
Вернуть статус
Убедитесь, что вы имеете четкое представление о том, что и как делает метод. Если
вы не понимаете его концептуально, на уровне псевдокода, какой же тогда у вас
шанс разобраться в нем на уровне языка программирования? Если его не пони#
маете вы, кто его поймет?
Опишите несколько идей псевдокодом и выберите луч'
Перекрестная ссылка Об итера-
шую (пройдите по циклу) Прежде чем кодировать, ре#
циях см. раздел 34.8.
ализуйте как можно больше своих идей в псевдокоде. При#
ступив к кодированию, вы эмоционально вовлекаетесь в этот
процесс, и вам труднее отказаться от плохого проекта и начать заново.
Общая идея: раз за разом проходиться по псевдокоду, пока каждое его предложе#
ние не станет настолько простым, что под ним можно будет вставить строку про#
граммы, а псевдокод оставить в качестве документации. Часть псевдокода, напи#
санного при первых проходах, может оказаться достаточно высокоуровневой и
потребовать дальнейшей декомпозиции. Не забывайте это сделать. Если вам не
понятно, как закодировать какой#то фрагмент, продолжайте работать с псевдо#
кодом, пока это не прояснится. Продолжайте уточнение и декомпозицию, пока
это не будет выглядеть как напрасная трата времени по сравнению с написанием
настоящего кода.
Кодирование метода
Спроектировав метод, приступайте к его конструированию. Конструирование
можно производить в стандартном порядке, а при необходимости отступить от
него (рис. 9#3).
Вернуть статус.
Status ReportErrorMessage(
ErrorCode errorToReport
) {
С этого места предложения псевдокода заменены на комментарии C++.
// Вернуть статус.
}
Теперь роль метода очевидна. Проектные работы закончены, и вы без всякого кода
видите, как работает метод.
Напишите код под каждым комментарием Добавьте
Перекрестная ссылка В этом
код под каждой строкой комментария. Это напоминает на#
случае подходит литературная
писание курсовой работы: сначала вы пишете план, а затем,
метафора, о которой см. под-
раздел «Литературная метафо-
под каждым его пунктом, — абзац текста. Каждый коммен#
ра: написание кода» раздела 2.3.
тарий соответствует блоку или абзацу кода. Длина абзаца
кода, как и длина абзаца литературного текста, зависит от
высказываемой мысли, а его качество — от понимания автором сути.
Status ReportErrorMessage(
ErrorCode errorToReport
) {
// Установить статус по умолчанию в “сбой”.
Добавленный код.
// Вернуть статус.
}
ГЛАВА 9 Процесс программирования с псевдокодом 221
Status ReportErrorMessage(
ErrorCode errorToReport
) {
// Установить статус по умолчанию в “сбой”.
Status errorMessageStatus = Status_Failure;
> if ( errorMessage.ValidCode() ) {
// Определяем метод обработки.
ProcessingMethod errorProcessingMethod = CurrentProcessingMethod();
// Вернуть статус.
return errorMessageStatus;
}
К каждому комментарию добавлена одна или несколько строк кода. Каждый блок
кода выражает некоторое намерение, описанное комментариями. Все перемен#
ные объявлены и определены рядом с местом их первого использования. Каждый
комментарий обычно разворачивается в 2–10 строк кода.
Теперь вернемся к спецификации и псевдокоду. Первоначальная спецификация
из пяти предложений превратилась в 15 строк псевдокода, которые в свою оче#
редь развернуты в метод размером в страницу. Хотя спецификация и была доста#
точно подробной, создание метода потребовало проектировочных работ при
написании псевдокода и кодировании. Это низкоуровневое проектирование и есть
одна из причин, по которой «кодирование» является нетривиальной задачей.
Проверьте, не нужна ли дальнейшая декомпозиция кода В некоторых слу#
чаях вы увидите, что код, соответствующий одной изначальной строке псевдоко#
да, существенно разросся. В таких ситуациях следует предпринять одно из следу#
ющих действий.
Преобразуйте код, соответствующий комментарию, в но#
Перекрестная ссылка О рефак-
торинге см. главу 24. вый метод. Дайте методу имя и напишите код вызова этого
метода. Если вы правильно применяли ППП, имя метода вы
легко придумаете на основе псевдокода. Закончив работу с изначальным ко#
дом, переходите к вновь созданным методам и применяйте ППП к ним.
Применяйте ППП рекурсивно. Вместо того чтобы писать несколько десятков
строк кода для одной строки псевдокода, разбейте эту строку псевдокода на
несколько предложений и для каждой из них напишите код.
ГЛАВА 9 Процесс программирования с псевдокодом 223
Проверка кода
Третий шаг после проектирования и реализации метода — его проверка. Все
ошибки, которые вы пропустите на этом этапе, вы сможете обнаружить лишь при
позднейшем тестировании, что обойдется вам дороже.
Ошибка может не проявиться до окончательного кодирова# Перекрестная ссылка О поиске
ния по нескольким причинам. Ошибка в псевдокоде может ошибок при построении архи-
стать заметнее при детальной реализации. Конструкция, тектуры и выработке требований
выглядящая элегантно в псевдокоде, на языке программиро# см. главу 3.
вания может стать топорной. Проработка детальной реали#
зации может выявить ошибку архитектуры, проекта или требований. Наконец, код
может содержать самые банальные ошибки программирования — никто не совер#
шенен! По всем этим причинам пересмотрите код, прежде чем двигаться дальше.
Умозрительно проверьте ошибки в методе Первая формальная проверка
метода — умозрительная. Мысленно выполните все ветви метода. Сделать это не#
просто, что и является одной из причин писать короткие методы. Проверьте все
возможные ветви и исключительные условия. Проделайте это сами и с коллегами.
Одно из основных различий между любителями и профессиональными
программистами — различие, появляющееся при переходе от суеверия к
пониманию. Под суеверием здесь я понимаю не иллюзию, что програм#
ма выдает больше ошибок в полнолуние, а замену «прочувствования» программы
ее пониманием. Если вы часто обнаруживаете, что подозреваете компилятор или
аппаратные средства в ошибке, вы в плену суеверий. Давнишние исследования по#
казали, что только около 5% всех ошибок связано с аппаратурой, компиляторами
или ОС (Ostrand and Weyuker, 1984). Сейчас этот процент, видимо, еще меньше.
Программист, достигший сферы понимания, обращает внимание прежде всего на
свое творение, являющееся потенциальным источником 95% ошибок. Нужно знать
роль каждой строки своей программы. Ничто не может называться верным толь#
ко потому, что выглядит работоспособным. Если вы не знаете, почему это рабо#
тает, вероятно, оно и не работает на самом деле.
Итог: работающий метод — это еще не все. Если вы не знаете, как он ра#
ботает, изучайте его, обсуждайте его, экспериментируйте с альтернатив#
ными вариантами, пока не добьетесь понимания.
Компиляция метода Проверив метод, скомпилируйте его. Может показаться
неэффективным так долго откладывать компиляцию. Вероятно, вы уменьшите себе
работу, скомпилировав метод ранее и позволив компилятору проверить необъяв#
ленные переменные, обнаружить конфликты имен и т. д.
Между тем, отложив трансляцию на более поздний срок, вы получите ряд преиму#
ществ. Основная причина в том, что, когда вы компилируете новый код, в вас
начинают тикать внутренние часики. После первой трансляции появляется мысль:
«Еще всего одна компиляция, и дело сделано». Этот синдром «еще всего одной
компиляции» подвигает вас к скороспелым, чреватым ошибками изменениям,
которые в долгосрочном плане увеличивают общее время работы. Не спешите и
не компилируйте программу, пока не будете уверены, что она верна.
224 ЧАСТЬ II Высококачественный код
Наведение глянца
Проверив код, оцените его с учетом общих критериев, описанных в этой книге.
Чтобы гарантировать соответствие качества метода высоким стандартам, сделай#
те следующее.
Проверьте интерфейс метода. Убедитесь, что применяются все входные и вы#
ходные данные и используются все параметры (см. раздел 7.5).
ГЛАВА 9 Процесс программирования с псевдокодом 225
Ключевые моменты
Конструирование классов и методов — процесс итеративный. Особенности,
замечаемые при конструировании отдельных методов, заставляют возвращаться
к проектированию класса.
Написание хорошего псевдокода предполагает употребление понятного есте#
ственного языка без специфических особенностей конкретного языка програм#
мирования, а также формулировок на уровне намерений (описания сути кон#
струкции, а не способов ее работы).
ГЛАВА 9 Процесс программирования с псевдокодом 227
Часть III
ПЕРЕМЕННЫЕ
Г Л А В А 1 0
Общие принципы
использования переменных
Содержание
http://cc2e.com/1085
10.1. Что вы знаете о данных?
10.2. Грамотное объявление переменных
10.3. Принципы инициализации переменных
10.4. Область видимости
10.5. Персистентность
10.6. Время связывания
10.7. Связь между типами данных и управляющими
структурами
10.8. Единственность цели каждой переменной
Связанные темы
Именование переменных: глава 11
Фундаментальные типы данных: глава 12
Редкие типы данных: глава 13
Размещение объявлений данных: одноименный подраздел раздела 31.5
Документирование переменных: подраздел «Комментирование объявлений
данных» раздела 32.5
Если при конструировании приходится заполнять небольшие пробелы в требо#
ваниях и архитектуре, это нормально и даже желательно. Проектирование про#
граммы вплоть до микроскопических деталей было бы неэффективным. В этой
главе рассматривается один из низкоуровневых вопросов конструирования —
использование переменных.
Эта глава будет особенно полезна опытным программистам. Довольно часто мы
применяем рискованные подходы, не имея полного представления об альтерна#
тивах, а после используем их в силу привычки. Особый интерес для опытных
программистов могут представлять разделы 10.6 и 10.8, посвященные соответствен#
ГЛАВА 10 Общие принципы использования переменных 231
Дополнительные ресурсы
Хорошими источниками информации о типах данных являются следующие книги:
Cormen, H. Thomas, Charles E. Leiserson, Ronald L. Rivest. Introduction to Algorithms.
New York, NY: McGraw Hill. 1990.
Sedgewick, Robert. Algorithms in C++, Parts 1%4, 3d ed. Boston, MA: Addison#Wesley, 1998.
Sedgewick, Robert. Algorithms in C++, Part 5, 3d ed. Boston, MA: Addison#Wesley, 2002.
Неявные объявления
Некоторые языки поддерживают неявное объявление переменных. Так, если, про#
граммируя на Microsoft Visual Basic, вы попытаетесь использовать необъявленную
переменную, компилятор может автоматически объявить ее для вас (это зависит
от параметров компилятора).
Неявное объявление переменных — одна из самых опасных возможностей язы#
ка. Если вы программировали на Visual Basic, то знаете, как жаль времени, потра#
ченного на поиск причины неправильного значения acctNo, если в итоге обнару#
живается, что вы по ошибке вызвали переменную acctNum, которая была иници#
ализирована нулем. Если язык не заставляет объявлять переменные, подобную
ошибку допустить очень легко.
ГЛАВА 10 Общие принципы использования переменных 233
заться область памяти, содержащая код. В этом блоке может находиться фрагмент
ОС. Проблема с указателями может проявляться совершенно неожиданным обра#
зом, изменяющимся от случая к случаю, поэтому найти такие ошибки сложнее,
чем любые другие.
Ниже описаны способы предотвращения проблем, связанных с инициализацией.
Инициализируйте каждую переменную при ее объявлении Инициализация
переменных при их объявлении — простая методика защитного программиро#
вания и хорошая страховка от ошибок инициализации. Так, следующий код по#
зволяет гарантировать, что инициализация массива studentGrades будет выполняться
при каждом вызове метода, содержащего массив:
>total = 0.0
’ использование переменной total
...
1
Букв. «мертвая корова». — Прим. перев.
238 ЧАСТЬ III Переменные
4 done = false;
...
26 while ( recordIndex < recordCount ) {
27 ...
Последнее обращение к переменной recordIndex.
64 while ( !done ) {
...
Последнее обращение к переменной total.
>25 recordIndex = 0;
26 while ( recordIndex < recordCount ) {
27 ...
28 recordIndex = recordIndex + 1;
...
Инициализация переменных total и done ранее выполнялась в строках 4 и 5.
> 62 total = 0;
63 done = false;
64 while ( !done ) {
...
69 if ( total > projectedTotal ) {
70 done = true;
Каждый из двух блоков, полученных при разделении кода, короче, чем первона#
чальный блок, и содержит меньше переменных. Такой код легче понять, а если
вам придется разбить его на отдельные методы, меньшие блоки, содержащие мень#
шее число переменных, позволят выполнять эту задачу эффективнее.
Разбивайте группы связанных команд на отдельные методы При прочих
равных условиях переменная из более короткого метода обычно характеризуется
меньшим интервалом между обращениями и меньшим временем жизни, чем переменная
из более крупного метода. Разбиение группы связанных команд на отдельные методы
позволяет уменьшить область видимости, которую может иметь переменная.
Начинайте с самой ограниченной области видимос'
Перекрестная ссылка О гло-
ти и расширяйте ее только при необходимости Что#
бальных переменных см. раздел
бы минимизировать область видимости переменной, поста# 13.3.
райтесь сделать ее как можно более локальной. Область ви#
244 ЧАСТЬ III Переменные
димости гораздо сложнее сжать, чем расширить — иначе говоря, превратить гло#
бальную переменную в переменную класса сложнее, чем наоборот. Защищенные
данные#члены класса также сложнее превратить в закрытые, чем закрытые в за#
щищенные. Так что, если сомневаетесь, выбирайте наименьшую возможную об#
ласть видимости переменной: попытайтесь сделать переменную локальной для от#
дельного цикла, локальной для конкретного метода, затем — закрытой перемен#
ной класса, затем — защищенной, далее попробуйте включить ее в пакет (если ваш
язык программирования поддерживает пакеты) и лишь в крайнем случае сделай#
те ее глобальной.
10.5. Персистентность
«Персистентность» — это еще одно слово, характеризующее длительность суще#
ствования данных. Персистентность принимает несколько форм. Некоторые пе#
ременные «живут»:
пока выполняется конкретный блок кода или метод: например, это перемен#
ные, объявленные внутри цикла for языка C++ или Java;
столько, сколько вы им позволяете: в Java переменные, созданные при помо#
щи оператора new, «живут» до сборки мусора; в C++ созданные аналогично
переменные существуют, пока не будут уничтожены с помощью оператора delete;
до завершения программы: этому описанию соответствуют глобальные пере#
менные в большинстве языков, а также статические переменные в языках C++
и Java;
всегда: такими переменными могут быть значения, которые вы храните в БД
между запусками программы, — так, например, в случае интерактивной про#
граммы, позволяющей пользователям настраивать цвета экрана, вы могли бы
хранить эти цвета в файле, считывая их при каждой загрузке программы.
Главная проблема персистентности возникает, когда вы предполагаете, что пере#
менная существует дольше, чем есть на самом деле. Переменная похожа на пакет
молока в холодильнике. Предполагается, что он может храниться неделю. Иног#
да он хранится месяц, и с молоком ничего не происходит, а иногда молоко ски#
сает через пять дней. Переменная может быть столь же непредсказуема. Если вы
попытаетесь использовать значение переменной после окончания нормальной дли#
тельности ее существования, получите ли вы ее прежнее значение? Иногда зна#
чение переменной «скисает», и вы получаете ошибку. В других случаях компиля#
тор оставляет старое значение неизменным, позволяя вам воображать, что все
нормально.
Проблем подобного рода можно избежать.
Включайте в программу отладочный код или утвержде#
Перекрестная ссылка Отладоч-
ния для проверки важных переменных на предмет до# ный код легко включать в ме-
пустимости их значений. Если значения недопустимы, тоды доступа (см. подраздел
отображайте предупреждение, рекомендующее присту# «Преимущества методов досту-
пить к поиску кода неверной инициализации. па» раздела 13.3).
Завершив работу с переменными, присваивайте им «не#
допустимые значения». Скажем, после освобождения памяти при помощи опе#
ратора delete вы может установить указатель в null.
Исходите из того, что данные не являются персистентными. Так, если при воз#
врате из метода переменная имеет определенное значение, не предполагайте,
что оно будет таким же при следующем вызове метода. Это правило неакту#
ально, если вы используете специфические возможности языка, гарантирую#
щие неизменность значения переменной, такие как ключевое слово static языков
C++ и Java.
Выработайте привычку объявлять и инициализировать все данные перед их
использованием. Если видите обращение к данным, но не можете найти по#
близости команду их инициализации, отнеситесь к этому с подозрением!
246 ЧАСТЬ III Переменные
читает значение цвета. Так часто делают при разработке интерактивных прило#
жений, предоставляющих возможность настройки их параметров.
Кроме того, время связывания может определяться тем, когда вызывается метод
ReadTitleBarColor(). Его можно вызывать при загрузке программы, при создании
окна или при каждой перерисовке окна: каждый последующий вариант соответ#
ствует все более позднему времени связывания.
Итак, в нашем примере переменная может связываться со значением следующим
образом (в других случаях детали могут быть несколько иными):
при написании кода (с использованием магических чисел);
при компиляции (с использованием именованной константы);
при загрузке программы (путем чтения значения из внешнего источника, та#
кого как реестр Windows или файл свойств Java);
при создании объекта (например, путем чтения значения при каждом созда#
нии окна);
по требованию (например, посредством чтения значения при каждой перери#
совке окна).
В целом чем раньше время связывания, тем ниже гибкость и ниже сложность кода.
Что до первых двух вариантов, то именованные константы по многим причинам
предпочтительнее магических чисел, и вы можете получить гибкость, обеспечи#
ваемую именованными константами, просто используя хорошие методики про#
граммирования. При дальнейшем повышении уровня гибкости повышается и слож#
ность нужного для его поддержания кода, а вместе с ней и вероятность ошибок.
Так как эффективность программирования зависит от минимизации сложности,
опытный программист будет обеспечивать такой уровень гибкости, какой нужен
для удовлетворения требований, но не более того.
Ключевые моменты
Неграмотная инициализация данных часто приводит к ошибкам. Описанные
в этой главе способы инициализации позволят избежать проблем, связанных
с неожиданными первоначальными значениями переменных.
Минимизируйте область видимости каждой переменной. Группируйте обраще#
ния к переменным. Старайтесь делать переменные локальными для методов или
классов. Избегайте глобальных данных.
Располагайте команды, использующие одни и те же переменные, как можно
ближе друг к другу.
Раннее связывание ограничивает гибкость, но минимизирует сложность про#
граммы. Позднее связывание повышает гибкость, но за это приходится распла#
чиваться повышенной сложностью.
Используйте каждую переменную исключительно с одной целью.
252 ЧАСТЬ III Переменные
Г Л А В А 1 1
Содержание
http://cc2e.com/1184
11.1. Общие принципы выбора имен переменных
11.2. Именование конкретных типов данных
11.3. Сила конвенций именования
11.4. Неформальные конвенции именования
11.5. Стандартизированные префиксы
11.6. Грамотное сокращение имен переменных
11.7. Имена, которых следует избегать
Связанные темы
Имена методов: раздел 7.3
Имена классов: раздел 6.2
Общие принципы использования переменных: глава 10
Размещение объявлений данных: одноименный подраздел раздела 31.5
Документирование переменных: подраздел «Комментирование объявлений
данных» раздела 32.5
Несмотря на всю важность выбора удачных имен для эффективного программи#
рования, я не знаю ни одной книги, в которой эта тема обсуждается хотя бы с ми#
нимально приемлемым уровнем детальности. Многие авторы посвящают пару
абзацев выбору аббревиатур, приводят несколько банальных примеров и ожида#
ют, что вы сами о себе позаботитесь. Я рискую быть обвиненным в противопо#
ложном: вы получите настолько подробные сведения об именовании переменных,
что никогда не сможете использовать их в полном объеме!
Советы, рассматриваемые в этой главе, касаются преимущественно именования
переменных: объектов и элементарных типов данных. Однако их следует учиты#
вать и при именовании классов, пакетов, файлов и других сущностей из мира
программирования. Об именовании методов см. также раздел 7.3.
ГЛАВА 11 Сила имен переменных 253
Что происходит в этом фрагменте кода? Что означают имена x1, xx и xxx? А fido?
Допустим, кто#то сказал вам, что этот код подсчитывает общую сумму предъявля#
емого клиенту счета, опираясь на его долг и стоимость новых покупок. Какую
переменную вы использовали бы для распечатки общей стоимости только новых
покупок?
Взглянув на исправленный вариант того же кода, ответить на этот вопрос куда
проще:
Из сравнения этих фрагментов можно сделать вывод, что хорошее имя перемен#
ной адекватно ее характеризует, легко читается и хорошо запоминается. Чтобы
облегчить себе достижение этих целей, соблюдайте несколько общих правил.
Ориентация на проблему
Хорошее мнемоническое имя чаще всего описывает проблему, а не ее решение.
Хорошее имя в большей степени выражает что, а не как. Если же имя описывает
некоторый аспект вычислений, а не проблемы, имеет место обратное. Предпочи#
тайте таким именам переменных имена, характеризующие саму проблему.
Запись данных о сотруднике можно было бы назвать inputRec или employeeData.
Имя inputRec — компьютерный термин, выражающий идеи ввода данных и запи#
си. Имя employeeData относится к проблемной области, а не к миру компьюте#
ров. В случае битового поля, определяющего статус принтера, имя bitFlag более
ГЛАВА 11 Сила имен переменных 255
namespace DatabaseSubsystem {
...
// объявления переменных
...
}
Если цикл длиннее нескольких строк, смысл переменной i легко забыть, поэтому
в подобной ситуации лучше присвоить индексу цикла более выразительное имя.
Так как код очень часто приходится изменять, модернизировать и копировать в
другие программы, многие опытные программисты вообще не используют име#
на вроде i.
Одна из частых причин увеличения циклов — их вложение в другие циклы. Если
у вас есть несколько вложенных циклов, присвойте индексам более длинные имена,
чтобы сделать код более понятным:
if ( flag ) ...
if ( statusFlag & 0x0F ) ...
if ( printFlag == 16 ) ...
if ( computeFlag == 0 ) ...
flag = 0x1;
statusFlag = 0x80;
printFlag = 16;
computeFlag = 0;
dataReady = true;
characterType = CONTROL_CHARACTER;
reportType = ReportType_Annual;
recalcNeeded = false;
По сути это тот же код, только в нем использована переменная с точным описа#
тельным именем.
ГЛАВА 11 Сила имен переменных 261
Именование перечислений
Принадлежность переменных к тому или иному перечисле#
Перекрестная ссылка О пере-
числениях см. раздел 12.6.
нию можно пояснить, дополнив их имена префиксами, та#
кими как Color_, Planet_ или Month_:
Кроме того, сами перечисления (Color, Planet или Month) можно идентифициро#
вать разными способами: например, используя в их именах только заглавные буквы
или дополняя их имена префиксами (e_Color, e_Planet или e_Month). Кое#кто мог
бы сказать, что перечисление по сути является типом, определяемым пользовате#
лем, поэтому имена перечислений надо форматировать так же, как имена клас#
сов и других пользовательских типов. С другой стороны, члены перечислений
являются константами, поэтому имена перечислений следует форматировать как
имена констант. В этой книге я придерживаюсь конвенции, предусматривающей
применение в именах перечислений букв обоих регистров.
В некоторых языках перечисления рассматриваются скорее как классы, а именам
членов перечисления всегда предшествует имя самого перечисления, например,
Color.Color_Red или Planet.Planet_Earth. Если вы используете подобный язык, по#
вторять префикс не имеет смысла, так что вы можете считать префиксом само
имя перечисления и сократить имена до Color.Red и Planet.Earth.
ГЛАВА 11 Сила имен переменных 263
Именование констант
Имя константы должно характеризовать абстрактную сущ#
Перекрестная ссылка Об имено-
ность, представляемую константой, а не конкретное значе# ванных константах см. раздел
ние. Имя FIVE — плохое имя константы (независимо от того, 12.7.
имеет ли она значение 5.0). CYCLES_NEEDED — хорошее имя.
CYCLES_NEEDED может иметь значение 5.0, 6.0 и любое другое. Выражение FIVE =
6.0 было бы странным. Аналогично BAKERS_DOZEN — плохое имя константы, а
DONUTS_MAX — вполне подходящее.
Суть сказанного в том, что наличие хоть какой#то конвенции обычно пред#
почтительнее, чем ее отсутствие. Конвенция может быть произвольной.
Сила конвенций именования объясняется не конкретными аспектами, а
самим фактом их использования, обеспечивающим структурирование кода и умень#
шающим количество поводов для беспокойства.
Каждый из этих вариантов имеет свои плюсы и минусы. Вариант 1 часто использу#
ется при программировании на C++, Java и других языках, чувствительных к регист#
ру букв, но некоторые программисты считают, что различать имена только по реги#
стру первой буквы неудобно. Действительно, имена, отличающиеся только регист#
ром первой буквы, имеют слишком малое психологическое и визуальное различие.
Вариант 1 не удастся согласованно использовать при программировании на не#
скольких языках, если хотя бы в одном из них регистр букв не имеет значения.
Так, при компиляции команды Dim widget as Widget компилятор Microsoft Visual
Basic сообщит о синтаксической ошибке, потому что widget и Widget покажутся
ему одним и тем же элементом.
Вариант 2 проводит более очевидное различие между именами типов и перемен#
ных. Однако по историческим причинам в C++ и Java верхний регистр служит для
определения констант, к тому же при разработке программы с использованием
нескольких языков этот подход приводит к тем же проблемам, что и вариант 1.
Вариант 3 поддерживается всеми языками, но некоторым программистам префиксы
не нравятся по эстетическим причинам.
Вариант 4 иногда используют как альтернативу варианту 3, но вместо изменения
имени одного класса он требует модификации имени каждого экземпляра класса.
266 ЧАСТЬ III Переменные
Конвенции C
Конвенции именования, используемые при программировании на C, предпола#
гают, что:
имена символьных переменных дополняются префиксом c или ch;
целочисленным индексам присваиваются имена i и j;
1
Значением передается ссылка на объект, который и может быть изменен. — Прим. перев.
268 ЧАСТЬ III Переменные
Конвенции C++
Дополнительные сведения О
С программированием на C++ связаны такие конвенции:
стиле программирования на C++ целочисленным индексам присваиваются имена i и j;
см. книгу «The Elements of C++ имена указателей дополняются префиксом p;
Style» (Misfeldt, Bumgardner, and
имена констант, типов, определяемых с помощью дирек#
Gray, 2004).
тивы typedef, и макросов препроцессора включают ТОЛЬКО_%
ЗАГЛАВНЫЕ_БУКВЫ;
имена классов и других типов содержат БуквыОбоихРегистров;
первое слово в именах переменных и методов начинается со строчной буквы,
а все последующие слова — с заглавной: имяПеременнойИлиМетода;
символ подчеркивания используется только в именах, состоящих полностью
из заглавных букв, и после некоторых префиксов (например, после префикса,
служащего для идентификации глобальных переменных).
Как и в случае языка C, некоторые аспекты этой конвенции могут зависеть от
конкретной среды.
Конвенции Java
В отличие от C и C++ конвенции стиля программирования
Дополнительные сведения О сти-
ле программирования на Java см. на Java были сформулированы уже на ранних этапах раз#
книгу «The Elements of Java Style, вития этого языка:
2d ed.» (Vermeulen et al., 2000). i и j — имена целочисленных индексов;
имена констант включают ТОЛЬКО_ЗАГЛАВНЫЕ_БУКВЫ, а
слова разделяются символами подчеркивания;
ГЛАВА 11 Сила имен переменных 269
Так как Visual Basic безразличен к регистру букв, для различения имен типов и
переменных приходится применять специфические правила (табл. 11#5).
Табл. 11-5. Пример конвенции именования для языка Visual Basic
Сущность Описание
C_ClassName Имена классов дополняются префиксом C_, начинаются
с заглавной буквы и включают буквы обоих регистров.
T_TypeName Имена типов дополняются префиксом T_, начинаются
с заглавной буквы и включают буквы обоих регистров.
T_EnumeratedTypes Кроме предыдущего правила, имена перечислений всегда
имеют форму множественного числа.
localVariable Имена локальных переменных начинаются со строчной буквы
и включают буквы обоих регистров. Имя должно характеризо#
вать сущность, представляемую переменной, и не должно зави#
сеть от фактического типа переменной.
routineParameter Имена параметров методов форматируются так же, как имена
локальных переменных.
RoutineName() Имена методов включают буквы обоих регистров (об удачных
именах методов см. раздел 7.3).
m_ClassVariable Имена переменных#членов, доступных только методам класса,
дополняются префиксом m_.
g_GlobalVariable Имена глобальных переменных дополняются префиксом g_.
CONSTANT Имена именованных констант включают
ТОЛЬКО_ЗАГЛАВНЫЕ_БУКВЫ.
Base_EnumeratedType Имена элементов перечислений дополняются именем самого
перечисления в единственном числе: Color_Red, Color_Blue.
272 ЧАСТЬ III Переменные
При работе с UDT следует также определить типы данных с именами, соответству#
ющими аббревиатурам UDT. Таким образом, при использовании UDT из табл.
11#6 у вас получились бы подобные объявления данных:
CH chCursorPosition;
SCR scrUserWorkspace;
DOC docActive
PA firstPaActiveDocument;
PA lastPaActiveDocument;
WN wnMain;
Разумеется, аббревиатуры следует создавать для тех UDT, которые чаще всего встре#
чаются в конкретной среде.
ГЛАВА 11 Сила имен переменных 273
Семантические префиксы
Семантические префиксы дополняют аббревиатуры UDT, характеризуя использо#
вание переменной или объекта. В отличие от аббревиатур UDT, зависимых от
конкретного проекта, семантические префиксы являются в некотором смысле
стандартными (табл. 11#7).
Табл. 11-7. Семантические префиксы
Семантический
префикс Значение
c Количество (записей, символов и т. д.).
first Элемент массива, обрабатываемый первым. Префикс first
аналогичен префиксу min, но связан с текущей операцией,
а не с самим массивом.
g Глобальная переменная.
i Индекс массива.
last Элемент массива, обрабатываемый последним. Префикс last
дополняет префикс first.
lim Верхняя граница обрабатываемого массива. Значение с пре#
фиксом lim уже не является допустимым индексом. Как и last,
префикс lim дополняет префикс first. В отличие от префикса
last, используемого для представления последнего допустимого
элемента, значение с lim выходит за пределы массива.
В общем, lim равняется last + 1.
m Переменная уровня класса.
max Индекс последнего элемента массива или другого списка.
Префикс max связан с самим массивом, а не с выполняемыми
над массивом операциями.
min Индекс первого элемента массива или другого списка.
p Указатель.
Фонетические аббревиатуры
Некоторые люди сокращают слова, опираясь на их звучание, а не написание.
В результате skating превращается в sk8ing, highlight — в hilite, before — в b4, execute
— в xqt и т. д. Этот способ не отличается понятностью, поэтому я рекомендую забыть
про него. Попробуйте, например, догадаться, что означают имена:
ILV2SK8 XMEQWK S2DTM8O NXTC TRMN8R
Может показаться, что этот способ устарел, но не далее чем в середине 2003 г. я
работал с клиентом, который использовал программу на языке RPG, включавшую
несколько сотен тысяч строк кода. Длина имен переменных в ней была ограни#
чена 6 символами. Подобные проблемы все еще всплывают время от времени.
Указывайте все сокращения в проектном документе «Стандартные аб'
бревиатуры» Применение аббревиатур сопряжено с двумя распространенны#
ми факторами риска:
аббревиатура может оказаться непонятной программисту, читающему код;
программисты могут сократить одно и то же имя по#разному, что вызывает
ненужное замешательство.
Для предотвращения этих потенциальных проблем вы можете создать документ
«Стандартные аббревиатуры», описывающий все аббревиатуры конкретного про#
екта. Сам документ может быть файлом текстового редактора или электронной
таблицей. При работе над очень крупным проектом им может быть база данных.
Этот документ следует зарегистрировать в системе управления версиями и изме#
нять его каждый раз, когда кто#то создает новую аббревиатуру. Элементы доку#
мента должны быть сортированы по полным словам, а не по аббревиатурам.
Может показаться, что эта методика связана с большим объемом дополнительной
работы, но на самом деле она требует небольших усилий на начальном этапе,
предоставляя взамен механизм, помогающий эффективно использовать аббреви#
атуры. Она устраняет первый из двух факторов риска, заставляя документировать
все используемые аббревиатуры. Если программист не может создать новую аб#
бревиатуру, не проверив документ «Стандартные аббревиатуры», не изменив его
и снова не зарегистрировав в системе управления версиями, — это хорошо. Это
значит, что программисты будут создавать аббревиатуры, только когда будут чув#
ствовать, что выгода от их создания стоит того, чтобы преодолеть все барьеры,
связанные с их документированием.
ГЛАВА 11 Сила имен переменных 277
1
Что буквально переводится как «донор цели» и «владелец золота». В экстремальном програм#
мировании так называют роли людей, соответственно ставящих перед разработчиками задачу
и финансирующих проект. — Прим. перев.
278 ЧАСТЬ III Переменные
лайте стандартом тот или иной вариант английского языка, чтобы вам не при#
шлось вспоминать, как называется конкретная переменная: «color» или «colour»,
«check» или «cheque» и т. д.
Избегайте имен стандартных типов, переменных и методов Все языки
программирования имеют зарезервированные и предопределенные имена. Про#
сматривайте время от времени списки таких имен, чтобы не вторгаться во владе#
ния используемого языка. Так, следующий фрагмент вполне допустим при про#
граммировании на PL/I, но написать ТАКОЕ может только идиот со справкой:
if if = then then
then = else;
else else = if;
В число пар символов, которые трудно различить, входят пары (1 и l), (1 и I),
(. и ,), (0 и O), (2 и Z), (; и :), (S и 5) и (G и 6).
Действительно ли важны подобные детали? Да! Джеральд
Перекрестная ссылка Вопросы,
Вайнберг пишет, что в 1970#х из#за того, что в команде касающиеся использования дан-
FORMAT языка Fortran вместо точки была использована за# ных, приведены в контрольном
пятая, ученые неверно рассчитали траекторию космичес# списке в главе 10.
кого корабля и потеряли космический зонд стоимостью 1,6
млрд долларов (Weinberg, 1983).
Имеет ли имя длину, достаточную для того, чтобы над ним не нужно было
ломать голову?
Спецификаторы вычисляемых значений находятся в конце имен?
Используются ли в именах спецификаторы Count или Index вместо Num?
Именование конкретных видов данных
Выразительные ли имена присвоены индексам циклов (более ясные, чем i,
j и k, если цикл содержит более одной-двух строк или является вложенным)?
Всем ли «временным» переменным присвоены выразительные имена?
Можно ли по именам булевых переменных понять, какой смысл имеют зна-
чения «истина» и «ложь»?
Включают ли имена элементов перечислений префикс или суффикс, опре-
деляющий принадлежность элемента к перечислению — например, префикс
Color_ в случае элементов Color_Red, Color_Green, Color_Blue и т. д.?
Именованные константы названы в соответствии с представляемыми абст-
рактными сущностями, а не конкретными числами?
Конвенции именования
Проводит ли конвенция различие между локальными данными, данными
класса и глобальными данными?
Проводит ли конвенция различие между именами типов, именованных кон-
стант, перечислений и переменных?
Идентифицировали ли вы исключительно входные параметры методов, если
язык не навязывает их идентификацию?
Постарались ли вы сделать конвенцию как можно более совместимой со
стандартными конвенциями конкретного языка?
Способствует ли форматирование имен удобству их чтения?
Короткие имена
Стараетесь ли вы не сокращать имена без необходимости?
Избегаете ли вы сокращения имен только на одну букву?
Все ли слова вы сокращаете согласованно?
Легко ли произнести выбранные имена?
Избегаете ли вы имен, допускающих неверное прочтение или произношение?
Документируете ли вы короткие имена при помощи таблиц преобразования?
Распространенные проблемы именования. Избежали ли вы…
…имен, которые вводят в заблуждение?
…имен с похожими значениями?
…имен, различающихся только одним или двумя символами?
…имен, имеющих похожее звучание?
…имен, включающих цифры?
…имен, намеренно написанных с ошибками с целью сокращения?
…имен, при написании которых люди часто допускают ошибки?
…имен, конфликтующих с именами методов из стандартных библиотек или
предопределенными именами переменных?
…совершенно произвольных имен?
…символов, которые можно спутать с другими символами?
ГЛАВА 11 Сила имен переменных 281
Ключевые моменты
Выбор хороших имен переменных — одно из главных условий понятности
программы. С отдельными типами переменных — например, с индексами цик#
лов и переменными статуса — связаны свои принципы именования.
Имена должны быть максимально конкретны. Имена, которые из#за невыра#
зительности или обобщенности можно использовать более чем с одной целью,
обычно являются плохими.
Конвенции именования позволяют провести различие между локальными дан#
ными, данными класса и глобальными данными, а также между именами ти#
пов, именованных констант, перечислений и переменных.
Над каким бы проектом вы ни работали, вам следует принять конвенцию име#
нования переменных. При выборе типа конвенции следует учитывать размер
программы и число работающих над ней программистов.
Современные языки программирования позволяют отказаться от сокращения
имен. Если вы все#таки сокращаете имена, регистрируйте аббревиатуры в сло#
варе проекта или используйте стандартизированные префиксы.
За чтением кода программисты проводят гораздо больше времени, чем за его
написанием. Выбирайте имена так, чтобы они облегчали чтение кода, пусть даже
за счет удобства его написания.
282 ЧАСТЬ III Переменные
Г Л А В А 1 2
Содержание
http://cc2e.com/1278
12.1. Числа в общем
12.2. Целые числа
12.3. Числа с плавающей запятой
12.4. Символы и строки
12.5. Логические переменные
12.6. Перечислимые типы
12.7. Именованные константы
12.8. Массивы
12.9. Создание собственных типов данных (псевдонимы)
Связанные темы
Присвоение имен данным: глава 11
Нестандартные типы данных: глава 13
Общие вопросы использования переменных: глава 10
Описание форматов данных: подраздел «Размещение объявлений данных»
раздела 31.5
Документирование переменных: подраздел «Комментирование объявлений дан#
ных» раздела 32.5
Создание классов: глава 6
Основные типы данных являются базовыми блоками для построения остальных
типов данных. Эта глава содержит советы по применению чисел вообще, целых
чисел, чисел с плавающей запятой, символов и строк, логических переменных,
перечислимых типов, именованных констант и массивов. В заключительном раз#
деле этой главы рассказано о создании собственных типов данных.
Эта глава содержит рекомендации по предупреждению главных ошибок в приме#
нении основных типов данных. Если вы уже ознакомились с основами, перехо#
дите к концу главы, к обзору перечня допускаемых ошибок, а также к обсужде#
нию нестандартных типов данных в главе 13.
ГЛАВА 12 Основные типы данных 283
for i = 0 to 99 do ...
можно предположить, что 99 определяет максимальное число элементов. А вот
выражение:
for i = 0 to MAX_ENTRIES1 do ...
не оставляет на этот счет сомнений. Даже если вы уверены, что это число ни#
когда не изменится, применяя именованные константы, вы получите более
читабельную программу.
Применяйте жестко заданные нули и единицы по необходимости Зна#
чения 0 и 1 используются для инкремента, декремента, а также в начале циклов
при нумерации первого элемента массива. 0 в конструкции:
for i = 0 to CONSTANT do ...
Вы можете подумать, что значение Product вычисляется как (100 000*100 000) /
100 000 и поэтому равно 100 000. Но программе приходится вычислять проме#
жуточное значение 100 000*100 000 до того, как будет выполнено деление на
100 000, а это значит, что нужно хранить такое большое число, как
1 000 000 000 000. Угадайте, что получится? Вот результат:
( 1000000 * 1000000 ) / 1000000 = 727
286 ЧАСТЬ III Переменные
Если значение целых чисел в вашей системе не превышает 2 147 483 647, проме#
жуточный результат слишком велик для целого типа данных. В такой ситуации
промежуточный результат, который должен быть равен 1 000 000 000 000, на са#
мом деле равен 727 379 968, поэтому, когда вы делите его на 100 000, вы получа#
ете #727 вместо 100 000.
Вы можете решить проблему переполнения промежуточных результатов так же,
как и в случае целочисленного переполнения: изменив тип на длинное целое или
число с плавающей запятой.
изводителей. Если часть строк не будет поддерживать Unicode (скажем, в C или C++),
как можно раньше решите, стоит ли вообще использовать символы Unicode. Если
вы решились на это, подумайте, где и когда будете это делать.
Разработайте стратегию интернационализации/локализации в ранний
период жизни программы Вопросы, связанные с интернационализацией, от#
носятся к разряду ключевых. Решите, будут ли все строки храниться во внешних
ресурсах и будет ли создаваться отдельный вариант программы для каждого язы#
ка или конкретный язык будет определяться во время выполнения.
Если вам известно, что нужно поддерживать толь'
http://cc2e.com/1292ко один алфавит, рассмотрите вариант использова'
ния набора символов ISO 8859 Для приложений, исполь#
зующих только один алфавит (например, английский), которым не надо поддер#
живать несколько языков или какой#либо идеографический язык (такой как пись#
менный китайский), расширенный ASCII#набор стандарта ISO 8859 — хорошая
альтернатива символам Unicode.
Если вам необходимо поддерживать несколько языков, используйте Uni'
code Unicode обеспечивает более полную поддержку международных наборов
символов, чем ISO 8859 или другие стандарты.
Выберите целостную стратегию преобразования строковых типов Если
вы используете несколько строковых типов, общим подходом, помогающим хра#
нить строковые типы в порядке, будет хранение всех строк программы в одном
формате и преобразование их в другой формат как можно ближе к операциям
ввода и вывода.
Строки в языке C
Строковый класс в стандартной библиотеке шаблонов C++ решил большинство
проблем со строками языка C. А тот, кто напрямую работает с C#строками, ниже
узнает о способах избежать часто встречающихся ошибок.
Различайте строковые указатели и символьные массивы Проблемы со
строковыми указателями и символьными массивами возникают из#за способа об#
работки строк в C. Учитывайте различия между ними в двух случаях.
Относитесь с недоверием к строковым выражениям, содержащим знак равен#
ства. Строковые операции в C практически всегда выполняются с помощью
strcmp(), strcpy(), strlen() и аналогичных функций. Знаки равенства часто сигна#
лизируют о каких#то ошибках в указателях. Присваивание в C не копирует стро#
ковые константы в строковые переменные. Допустим, у нас есть выражение:
StringPtr = “Some Text String”;
В этом случае ”Some Text String” — указатель на литеральную текстовую строку,
и это присваивание просто присвоит указателю StringPtr адрес данной стро#
ки. Операция присваивания не копирует содержимое в StringPtr.
Используйте соглашения по именованию, чтобы различать переменные —
массивы символов и указатели на строки. Одно из общепринятых соглашений —
использование ps как префикса для обозначения указателя на строку, и ach —
как префикса для символьного массива. И хотя они не всегда ошибочны, от#
ГЛАВА 12 Основные типы данных 291
чения не имеет: C никогда не найдет ее конец, если не найдет нулевой байт. Если
вы забыли поместить нулевой байт в конец строки, строковые операции могут ра#
ботать не так, как вы ожидаете.
Вы можете предупредить появление бесконечных строк двумя способами. Во#пер#
вых, при объявлении инициализируйте символьные массивы 0:
Output_File
End Enum
Country_Usa = 6
Country_Last = 6
End Enum
class Output {
private Output() {}
public static final Output Screen = new Output();
public static final Output Printer = new Output();
public static final Output File = new Output();
}
ГЛАВА 12 Основные типы данных 299
Это уже лучше, но для завершения примера индекс цикла тоже нужно назвать более
информативно:
ГЛАВА 12 Основные типы данных 301
Этот пример выглядит весьма неплохо, но мы можем сделать еще один шаг впе#
ред, применив перечислимый тип:
12.8. Массивы
Массивы — простейшие и наиболее часто используемые типы структурирован#
ных данных. В некоторых языках это единственный вид структурированных дан#
ных. Массивы состоят из группы элементов одинакового типа, доступ к которым
осуществляется напрямую по индексу.
Убедитесь, что все значения индексов массива не выходят за его
границы Все проблемы с массивами так или иначе связаны с тем, что
доступ к их элементам может осуществляться произвольно. Наиболее часто
302 ЧАСТЬ III Переменные
При выполнении операций над массивами для указания верхней границы исполь#
зуйте макрос ARRAY_LENGTH() вместо именованной константы. Например:
Этот способ особенно полезен для массивов неопределенного размера, как в этом
примере. Если вы добавляете или удаляете элементы, вам не надо помнить об
изменении именованной константы, определяющей размер массива. Разумеется,
эта технология работает и с массивами заданного размера, но, используя этот
подход, вам не всегда надо будет создавать дополнительные именованные кон#
станты для объявления массивов.
Routine2( ... ) {
Coordinate x; // координата x в метрах
Coordinate y; // координата y в метрах
Coordinate z; // координата z в метрах
...
}
Когда речь идет о строке или массиве, обычно разумно определить именованную
константу, содержащую длину строки или массива, а затем задействовать ее в
определении типа. Вы найдете в своей программе много мест, в которых стоит
использовать константу, и это — первое из них. Вот как это выглядит:
Вот как тип Coordinate будет выглядеть в другом модуле, где он используется:
Заметьте: тип Coordinate объявлен в модуле как private. Это значит, что единственная
часть программы, которая знает определение типа Coordinate, — это закрытая часть
модуля Transformation. При групповой разработке проекта вы можете распрост#
ранить только спецификацию модуля, что затруднит программисту, работающе#
му с другим модулем, просмотр исходного типа Coordinate. Информация будет
306 ЧАСТЬ III Переменные
Перечислимые типы
Используются ли в программе перечислимые типы вместо именованных
констант ради их улучшенной читабельности, надежности и модифицируе-
мости?
Используются ли перечислимые типы вместо логических переменных, если
все значения переменной не могут быть переданы с помощью true и false?
Проверяются ли некорректные значения перечислимых типов в условных
операторах?
Зарезервирован ли первый элемент перечислимого типа как недопустимый?
Перечислимые константы
Используются ли в программе именованные константы вместо магических
чисел для объявления данных и границ циклов?
Используются ли именованные константы последовательно, чтобы одно
значение не представлялось в одном месте константой, а в другом — лите-
ралом?
Массивы
Находятся ли все индексы массива в его границах?
Свободны ли ссылки на массив от ошибок потери единицы?
Указаны ли все индексы многомерных массивов в правильном порядке?
В правильном ли порядке используются переменные-индексы во вложенных
циклах, не происходит ли пересечения индексов?
Создание типов
Используются ли в программе отдельные типы для каждого вида данных,
который может измениться?
Ориентируются ли имена типов на реальные сущности, которые эти типы
представляют, а не на типы языка программирования?
Достаточно ли наглядны имена типов, чтобы помочь документированию
объявлений данных?
Не произошло ли переопределение предопределенных типов?
Рассматривался ли вопрос создания нового класса вместо простого пере-
определения типа?
Ключевые моменты
Работа с определенными типами данных требует запоминания множества
правил для каждого из них. Используйте список контрольных вопросов из этой
главы, чтобы убедиться, что вы учли основные проблемы с ними.
Создание собственных типов, если ваш язык это позволяет, упрощает модифи#
кацию вашей программы и делает ее более самодокументируемой.
Прежде чем создавать простой тип с помощью typedef или его эквивалента,
подумайте, не следует ли создать вместо него новый класс.
310 ЧАСТЬ III Переменные
Г Л А В А 1 3
Содержание
http://cc2e.com/1378
13.1. Структуры
13.2. Указатели
13.3. Глобальные данные
Связанные темы
Фундаментальные типы данных: глава 12
Защитное программирование: глава 8
Нестандартные управляющие структуры: глава 17
Сложность в разработке ПО: раздел 5.2
Некоторые языки программирования поддерживают экзотические виды данных
в дополнение к типам, обсуждавшимся в главе 12. В разделе 13.1 рассказывается,
при каких обстоятельствах вы могли бы использовать структуры вместо классов.
В разделе 13.2 описываются детали использования указателей. Если у вас возни#
кали проблемы с использованием глобальных данных, из раздела 13.3 вы узнае#
те, как их избежать. Если вы думаете, что типы данных, описанные в этой главе,
— это не те типы, о которых вы обычно читаете в современных книгах по объек#
тно#ориентированному программированию, то вы абсолютно правы. Поэтому эта
глава и называется «Нестандартные типы данных».
13.1. Структуры
Термин «структура» относится к типу данных, построенному на основе других
типов. Так как массивы — особый случай, они рассматриваются отдельно в главе
12. В этом разделе обсуждаются структурированные данные, созданные пользо#
вателем: structs в C/C++ и Structures в Microsoft Visual Basic. В Java и C++ классы тоже
иногда выглядят, как структуры (когда они состоят только из открытых членов
данных и не содержат открытые методы).
Чаще всего вы предпочтете создавать классы, а не структуры, чтобы задейство#
вать преимущества закрытости и функциональности, предлагаемой классами, в
ГЛАВА 13 Нестандартные типы данных 311
supervisor.title = inputTitle
supervisor.department = inputDepartment
supervisor.bonus = inputBonus
Каждый раз, желая передать сведения о работнике, вам приходится иметь дело со
всеми этими операторами. Если вам нужно добавить новый элемент данных, на#
пример numWithholdings , вам придется найти все места, где написаны эти при#
сваивания, и добавить еще одно: newNumWithholdings = oldNumWithholdings.
Представьте, как ужасно будет менять местами данные о двух работниках. Вам не
надо напрягать воображение — вот пример:
oldName = newName
oldAddress = newAddress
oldPhone = newPhone
oldSsn = newSsn
oldGender = newGender
oldSalary = newSalary
newName = previousOldName
newAddress = previousOldAddress
newPhone = previousOldPhone
newSsn = previousOldSsn
newGender = previousOldGender
newSalary = previousOldSalary
End Structure
Dim newEmployee As Employee
Dim oldEmployee As Employee
Dim previousOldEmployee As Employee
Пример более легкого способа обмена двух групп данных (Visual Basic)
previousOldEmployee = oldEmployee
oldEmployee = newEmployee
newEmployee = previousOldEmployee
13.2. Указатели
Использование указателей — одна из наиболее подверженных ошибкам
областей программирования. Это привело к тому, что современные язы#
ки, такие как Java, C# и Visual Basic, не предоставляют указатель в каче#
стве типа данных. Применять указатели и так сложно, а правильное применение
требует от вас отличного понимания того, как ваш компилятор управляет распре#
делением памяти. Многие общие проблемы с безопасностью, особенно случаи пе#
реполнения буфера, могут быть сведены к ошибочному использованию указате#
лей. (Howard and LeBlanc, 2003).
Даже если в вашем языке не нужны указатели, их хорошее понимание поможет
вам разобраться, как работает ваш язык программирования. А щедрая доза защит#
ного программирования будет еще полезнее.
Область памяти
Область памяти — это адрес, часто представленный в шестнадцатеричном виде.
В 32#разрядном процессоре адрес будет 32#битным числом, например 0x0001EA40.
Сам по себе указатель содержит только этот адрес. Чтобы обратиться к данным,
на которые этот указатель указывает, надо пойти по этому адресу и как#то интер#
претировать содержимое памяти в этой области. Сам по себе этот участок памя#
ти — просто набор битов. Чтобы он обрел смысл, его надо как#то истолковать.
ГЛАВА 13 Нестандартные типы данных 315
Даже если этот код изначально работает правильно, при дальнейших модифика#
циях он подвержен ошибкам, так как существует шанс, что кто#нибудь попробует
ГЛАВА 13 Нестандартные типы данных 317
Этот традиционный код добавления нового узла в связный список излишне сло#
жен для понимания. В добавлении элемента задействованы три объекта: текущий
узел, узел, в данный момент следующий за текущим, и узел, который надо вста#
вить между ними. Однако в коде явно упомянуты только два объекта: insertNode и
currentNode. Из#за этого вам придется запомнить, что currentNode%>next тоже уча#
ствует в алгоритме. Если вы попробуете изобразить диаграммой, что происходит,
не используя элемент, изначально следующий за currentNode, у вас получится что#
то вроде этого:
Гораздо лучшая диаграмма содержит все три объекта. Она может выглядеть так:
Вот пример кода, который явно упоминает все три объекта, участвующих в алго#
ритме:
Этот код содержит одну дополнительную строку, но без участия выражения current%
Node%>next%>previous из первого фрагмента этот пример легче для понимания.
320 ЧАСТЬ III Переменные
Указатели в C++
Язык C++ добавил специфические тонкости при работе с указателями и ссылка#
ми. В следующих подразделах описаны основные принципы, применяемые в ра#
боте с указателями на C++.
324 ЧАСТЬ III Переменные
Указатели в C
Вот несколько советов по применению указателей, которые в особенности име#
ют отношение к языку C.
Используйте явный тип указателя вместо типа по умолчанию Язык C
позволяет использовать указатели на char или void для любого типа переменной.
Главное, что указатель куда#то указывает, и языку, в общем, не важно, на что именно.
Но если вы используете явные типы для указателей, компилятор может выдавать
предупреждение о несовпадающих типах указателей и некорректных преобразо#
ваниях. Если же явные типы не используются, он этого сделать не сможет. Ста#
райтесь применять конкретные типы где только можно.
Из этого правила следует необходимость явного преобразования типа в тех слу#
чаях, когда нужно его изменить. Так, в этом фрагменте очевидно, что выделяется
память для переменной типа NODE_ PTR:
globalStatus . Get() и globalStatus . Set(), каждый из которых делает то, что сказано в
ее названии. Эти методы обращаются к переменной, спрятанной внутри класса,
заменяющего g_globalStatus. Остальная часть программы может получать все пре#
имущества бывшей глобальной переменной, вызывая globalStatus.Get() и global%
Status.Set().
Если язык не поддерживает классы, вы все равно можете
Перекрестная ссылка Ограниче-
ние доступа к глобальным пе-
создавать методы доступа для манипуляции глобальными
ременным, даже если ваш язык данными. Однако вам придется устанавливать ограничения
не поддерживает это напрямую, доступа к глобальным данным с помощью стандартов ко#
— пример программирования дирования, а не встроенных средств языка.
с использованием языка, а не
Вот несколько основных принципов применения методов
на языке (см. раздел 34.4).
доступа для сокрытия глобальных переменных в языках, не
имеющих встроенной поддержки этого.
Требуйте, чтобы весь код обращался к данным через методы доступа Хо#
рошим соглашением будет начинать имена всех глобальных переменных с пре#
фикса g_, и в дальнейшем требовать, чтобы никакой код не обращался к перемен#
ным с префиксом g_ напрямую, кроме методов доступа к этим переменным. Весь
остальной код работает с этими данными через методы доступа.
Не валите все глобальные данные в одну кучу Если вы сложите все глобаль#
ные данные в одну большую кучу и напишете для них методы доступа, вы решите
проблему глобальных данных, но утратите некоторые преимущества абстрактных
типов данных и сокрытия информации. Раз уж вы пишете методы доступа, обду#
майте, к каким классам принадлежит каждая глобальная переменная, и затем упа#
куйте данные и методы доступа к ним в этот класс.
Управляйте доступом к глобальным переменным с помощьюблокировок
По аналогии с управлением параллельным доступом к многопользовательской ба#
зой данных, блокировка требует, чтобы перед вызовом или обновлением значения
глобальной переменной ее помечали для изменений (check out). После использо#
вания переменную можно освободить (check in). Если пока она занята (т. е. поме#
чена для изменений), другая часть программы попытается к ней обратиться, про#
цедура блокировки выводит сообщение об ошибке или генерирует исключение.
Такое описание механизма блокировок опускает многие
Перекрестная ссылка О плани-
тонкости в написании кода, полностью поддерживающего
ровании различий между рабо-
параллельное выполнение. По этой причине упрощенные
чей и промышленной версиями
схемы блокировок вроде этой наиболее полезны на стадии
программы см. подраздел «За-
планируйте удаление отладоч-
разработки. Пока схема тщательно не продумана, она ско#
ных средств» раздела 8.6, а так-
рее всего не будет достаточно надежна для работы в про#
же раздел 8.7.
мышленной версии. При вводе программы в эксплуатацию
такой код должен быть заменен на более безопасный и вы#
полняющий более элегантные действия, чем вывод сообщений об ошибках. Так,
при обнаружении ситуации, когда несколько частей программы пытаются забло#
кировать одну и ту же глобальную переменную, он мог бы записать сообщение
об ошибке в файл.
ГЛАВА 13 Нестандартные типы данных 333
Смысл первых трех примеров в том, что абстрактный метод доступа гораздо ин#
формативнее общей структуры. Если вы используете структуры напрямую, вы
одновременно делаете слишком многое: во#первых, показываете, что выполняет
структура (переход к следующему элементу в связном списке), а во#вторых — что
происходит по отношению к сущности, которую она представляет (выбор номе#
ра счета, следующего работника или процентной ставки). Это слишком тяжелая
ноша для простой операции присваивания в структуре данных. Сокрытие инфор#
мации за абстрактными методами доступа позволяет коду самому говорить за себя
и заставляет читать программу на уровне области определения задачи, а не на
уровне деталей реализации.
Выполняйте доступ к данным на одном и том же уровне абстракции Если
вы используете метод доступа для выполнения какого#то действия со структурой,
все остальные действия должны производиться с помощью таких методов. Если вы
считываете данные с помощью метода доступа, то и записывайте их с помощью
метода. Если вы вызываете InitStack() для инициализации стека и PushStack() для
добавления в него элементов, то вы создали целостное представление данных. Если
же вы извлекаете элементы с помощью выражения value = array[ stack.top ], то это
представление данных противоречиво. Противоречивость усложняет код для по#
нимания. Создайте метод PopStack() и используйте вместо value = array[ stack.top ].
В примерах выражений в табл. 13#1. две операции с очере#
Перекрестная ссылка Примене-
дями событий происходят параллельно. Добавление в оче# ние методов доступа для оче-
редь — наиболее сложная из этих двух операций в таблице реди событий предполагает не-
и потребует нескольких строк кода для поиска места вставки обходимость создания класса
события, сдвига остальных элементов очереди для выделе# (см. главу 6).
ния места новому событию, и установки нового начала или
конца очереди. Удаление события из очереди по сложности будет примерно та#
ким же. Если во время кодирования сложные операции будут помещены в мето#
334 ЧАСТЬ III Переменные
ды, а в остальных будет применяться прямой доступ к данным, это создаст безоб#
разное, нераспараллеливаемое использование структуры. Теперь сравните пары
выражений в табл. 13#2:
Табл. 13-2. Распараллеливаемое и нераспараллеливаемое
применение сложных данных
Нераспараллеливаемое Распараллеливаемое использование
использование сложных данных сложных данных
event = EventQueue[ queueFront ] event = HighestPriorityEvent()
event = EventQueue[ queueBack ] event = LowestPriorityEvent()
AddEvent( event ) AddEvent( event )
eventCount = eventCount % 1 RemoveEvent( event )
Может показаться, что эти принципы стоит применять только в больших програм#
мах, однако методы доступа показали себя как эффективный способ решения
проблем с глобальными данными. В качестве бонуса они делают код более чита#
бельным и добавляют гибкость.
Дополнительные ресурсы
Далее указаны дополнительные ресурсы, в которых освеща#
ются необычные типы данных: http://cc2e.com/1385
Структуры
Используете ли вы структуры вместо отдельных переменных для организа-
ции и манипуляции группами взаимосвязанных данных?
Рассматривали ли вы создание класса как альтернативу использованию
структуры?
Глобальные данные
Действительно ли все переменные объявлены локально или в области види-
мости класса, если только они не обязательно должны быть глобальными?
Различаются ли в соглашениях по именованию переменных локальные,
классовые и глобальные данные?
Документированы ли все глобальные переменные?
Свободен ли код от псевдоглобальных данных — мамонтообразных объек-
тов, содержащих мешанину из данных, передающихся в каждый метод?
Используются ли методы доступа вместо глобальных данных?
Организованы ли данные и методы доступа к ним в классы?
Предоставляют ли методы доступа уровень абстракции, независимый от
реализации используемого типа данных?
Находятся ли все методы доступа на одном уровне абстракции?
Указатели
Изолированы ли операции с указателями в методах?
Корректны ли обращения к указателям или они могут быть «висячими»?
336 ЧАСТЬ III Переменные
Ключевые моменты
Структуры могут помочь сделать программы менее сложными, упростить их
понимание и сопровождение.
Принимая решение использовать структуру, подумайте, не будет ли класс под#
ходить лучше.
Работа с указателями чревата ошибками. Обезопасьте себя, используя методы
или классы для доступа к ним и практику защитного программирования.
Избегайте глобальных переменных не только потому, что они опасны, но и
потому что их можно заменить чем#то лучшим.
Если вы не можете отказаться от глобальных переменных, работайте с ними
через методы доступа. Эти методы предоставляют все то же и даже больше, что
и глобальные переменные.
ГЛАВА 13 Нестандартные типы данных 337
Часть IV
ОПЕРАТОРЫ
Г Л А В А 1 4
Организация
последовательного кода
Содержание
http://cc2e.com/1465
14.1. Операторы, следующие в определенном порядке
14.2. Операторы, следующие в произвольном порядке
Связанные темы
Общие вопросы управления: глава 19
Код с условными операторами: глава 15
Код с операторами цикла: глава 16
Область видимости переменных и объектов: раздел 10.4
В этой главе мы начнем рассматривать программирование не с точки зрения дан#
ных, а с точки зрения выражений. Глава представляет самую простую управляю#
щую логику программы: размещение выражений и их блоков в последовательном
порядке.
Хотя размещение последовательного кода относительно простая задача, некото#
рые организационные тонкости влияют на качество, корректность, читабельность
и управляемость кода.
Поскольку все методы используют expenseData, это наводит на мысль, что они могут
работать с одними и теми же данными и что порядок выражений может быть важен.
В этом примере лучшим подходом может быть преобразование процедур в функ#
ции, которые принимают expenseData на входе и возвращают обновленное зна#
чение expenseData. Это сделает наличие зависимостей в коде еще более явным.
Данные могут также указывать, что порядок выполнения не имеет значения, как в
этом случае:
ГЛАВА 14 Организация последовательного кода 341
Так как методы в первых четырех строках не имеют общих данных, код подразу#
мевает, что порядок их вызова значения не имеет. Поскольку метод в пятой стро#
ке использует данные каждого из первых четырех методов, вы можете предполо#
жить, что его надо выполнять после всех этих методов.
Документируйте неявные зависимости с помощью коммента'
риев Попробуйте, во#первых, написать код без порядковых зависимо#
стей, во#вторых — написать код, который делает зависимости очевидными.
Если вам все еще кажется, что зависимости видны недостаточно ясно, задокумен#
тируйте их. Документирование неявных зависимостей — один из аспектов доку#
ментирования допущений, сделанных при кодировании, что необходимо для на#
писания систем, пригодных для сопровождения и модификации. В примере на
Visual Basic будет полезно поместить такие комментарии:
travelData.ComputeQuarterly();
salesData.ComputeQuarterly();
marketingData.ComputeQuarterly();
salesData.ComputeAnnual();
marketingData.ComputeAnnual();
travelData.ComputeAnnual();
salesData.Print();
travelData.Print();
marketingData.Print();
SalesData salesData;
salesData.ComputeQuarterly();
salesData.ComputeAnnual();
salesData.Print();
TravelData travelData;
travelData.ComputeQuarterly();
travelData.ComputeAnnual();
travelData.Print();
Этот код лучше по нескольким причинам. Упоминания каж# Перекрестная ссылка Более
дой переменной располагаются вместе — они «локализова# формальное определение «жи-
ны». Число строк кода, в которых объекты являются «живы# вых» переменных см. в подраз-
ми», невелико. И, возможно, самое важное: код теперь вы# деле «Измерение времени жиз-
ни переменной» раздела 10.4.
глядит так, что его можно разбить на отдельные методы для
данных по маркетингу, продажам и поездкам. Первый фраг#
мент не содержал подсказки, что эта декомпозиция возможна.
Ключевые моменты
Главный принцип организации последовательного кода — упорядочение за#
висимостей.
Зависимости должны быть сделаны явными с помощью хороших имен мето#
дов, списков параметров, комментариев и — если последовательность кода
достаточно критична — с помощью вспомогательных переменных.
Если порядковые зависимости в коде отсутствуют, старайтесь размещать вза#
имосвязанные выражения как можно ближе друг к другу.
346 ЧАСТЬ IV Операторы
Г Л А В А 1 5
Условные операторы
Содержание
http://cc2e.com/1538
15.1. Операторы if
15.2. Операторы case
Связанные темы
Укрощение глубокой вложенности: раздел 19.4
Общие вопросы управления: глава 19
Код с операторами цикла: глава 16
Последовательный код: глава 14
Отношения между типами данных и управляющими структурами: раздел 10.7
Условный оператор управляет выполнением других операторов. Их выполнение
«обусловливают» такие операторы, как if, else, case и switch. Хотя операторы цик#
ла, например while и for, по смыслу тоже можно отнести к условным, обычно их
рассматривают отдельно. Операторы while и for мы обсудим в главе 16.
15.1. Операторы if
В зависимости от выбранного языка программирования вы можете использовать
несколько видов if#операторов. Простейшие из них — if или if%then. Оператор if%
then%else немного сложнее, а наибольшую сложность представляют последователь#
ности if%then%else%if.
> UpdateAllAccounts()
EraseUndoFile()
errorType = ErrorType_None
End If
End If
Else
348 ЧАСТЬ IV Операторы
errorType = ErrorType_FileReadError
End If
End If
Этот код сложен для понимания, так как в нем перемешаны нормальные и оши#
бочные ситуации. Тяжело проследить путь, проходимый в коде при нормальных
обстоятельствах. Кроме того, так как ошибки иногда обрабатываются в блоке if, а
не else, тяжело найти, в каких же ветвях if обрабатываются нормальные ситуации.
В переписанном примере нормальный путь последовательно кодируется первым,
а ошибочные ситуации — последними. Это упрощает поиск и чтение номиналь#
ного варианта алгоритма.
> UpdateAllAccounts()
EraseUndoFile()
errorType = ErrorType_None
Else
Ошибочная ситуация.
Ошибочная ситуация.
> errorType = ErrorType_FileOpenError
End If
if ( SomeTest )
;
else {
// делаем чтото
...
}
Опытные программисты не станут писать такой код хотя бы Перекрестная ссылка Один из
затем, чтобы избежать лишней работы по вводу пустой стро# ключей к конструированию эф-
ки и оператора else. Этот код выглядит глупо и может быть фективного оператора if- — со-
легко улучшен путем отрицания предиката в выражении if, здание правильного управляюще-
перемещения кода из блока else в блок if и удаления блока го логического выражения. Об эф-
фективном применении логичес-
else. Вот как будет выглядеть код после таких изменений: ких выражений см. раздел 19.1.
Case “ “, “,”, “.”, “!”, “(“, “)”, “:”, “;”, “?”, “”
characterType = CharacterType_Punctuation
Case “0” To “9”
characterType = CharacterType_Digit
Case FIRST_CONTROL_CHARACTER To LAST_CONTROL_CHARACTER
characterType = CharacterType_Control
Case Else
DisplayInternalError( “Unexpected type of character detected.” )
End Select
ким. Краткость позволяет сделать структуру оператора case прозрачнее. Если дей#
ствия, предпринимаемые для какого#то варианта слишком сложны, напишите метод
и вызывайте его, а не размещайте весь этот код прямо в блоке case.
Не конструируйте искусственные переменные с целью получить возмож'
ность использовать оператор case Оператор case следует применять для про#
стых данных, которые легко разбить на категории. Если ваши данные нельзя на#
звать простыми, используйте цепочки if%then%else. Фальшивые переменные сбива#
ют с толки, и их следует избегать. Например, не делайте так:
case ‘s’:
PrintSummaryReport();
break;
default:
DisplayInternalError( “Internal Error 905: Call customer support.” );
}
case DocumentationLevel_Summary:
DisplayErrorSummary( errorNumber );
// СКВОЗНОЙ ПЕРЕХОД — Суммарная документация
// также печатает номер ошибки.
case DocumentationLevel_NumberOnly:
DisplayErrorNumber( errorNumber );
break;
default:
DisplayInternalError( “Internal Error 905: Call customer support.” );
}
Эта методика встречается так же часто, как и люди, которые предпочитают по#
держанный «Понтиак Ацтек» новенькому «Корвету». Обычно код, переходящий
сквозь один блок case к другому, просто напрашивается на ошибки при модифи#
кации и его следует избегать.
Операторы if-then
Очевиден ли номинальный путь выполнения кода?
Правильно ли выполняется ветвление при проверке if-then на равенство?
Присутствует и задокументирован ли блок else?
Корректен ли блок else?
Правильно ли расположены блоки if и else — нет ли инверсии?
Следует ли нормальный вариант после if, а не после else?
Последовательности if-then-else-if
Преобразуются ли сложные проверки в вызовы логических функций?
Проверяются ли наиболее вероятные случаи первыми?
Все ли варианты учитываются?
Является ли последовательность if-then-else-if лучшей реализацией или лучше
использовать оператор case?
358 ЧАСТЬ IV Операторы
Операторы case
Разумно ли отсортированы варианты в операторе case?
Сделаны ли действия, выполняемые для каждого варианта, простыми, на-
пример, с помощью преобразования в методы в случае необходимости?
Проверяет ли оператор case реальную переменную, а не искусственно со-
зданную, приводящую к неправильному использованию оператора case?
Корректны ли значения, обрабатываемые в блоке по умолчанию?
Используется ли блок по умолчанию для выявления ошибок и сообщения о
непредвиденных ситуациях?
В языках C, C++ или Java содержит ли каждый блок case команды для выхода?
Ключевые моменты
В простых выражениях if%else обращайте внимание на порядок блоков if и else,
особенно если они обрабатывают множество ошибок. Убедитесь, что номиналь#
ный вариант прослеживается ясно.
Для последовательностей if%then%else и операторов case выбирайте порядок,
позволяющий улучшить читабельность.
Для перехвата ошибок используйте блок по умолчанию в операторе case или
последний блок else в цепочке операторов if%then%else.
Управляющие конструкции не равнозначны. Выбирайте конструкцию, наибо#
лее подходящую для данного участка кода.
ГЛАВА 16 Циклы 359
Г Л А В А 1 6
Циклы
Содержание
http://cc2e.com/1609
16.1. Выбор типа цикла
16.2. Управление циклом
16.3. Простое создание цикла — изнутри наружу
16.4. Соответствие между циклами и массивами
Связанные темы
Укрощение глубокой вложенности: раздел 19.4
Общие вопросы управления: глава 19
Код с условными операторами: глава 15
Последовательный код: глава 14
Отношения между управляющими структурами и типами данных: раздел 10.7
Цикл — это неформальное обозначение любой структуры итеративного типа,
т. е. такой, которая заставляет программу повторно выполнять некий блок кода.
Наиболее распространенными видами циклов являются for, while и do%while в C++
и Java, For%Next, While%Wend и Do%Loop%While — в Microsoft Visual Basic. Использо#
вание циклов — один из наиболее сложных аспектов программирования. Знание,
как и когда применять каждый тип цикла, — это решающий фактор в конструи#
ровании высококачественного ПО.
Бесконечный цикл выполняется все время с момента старта. Такие циклы можно
встретить во встроенных системах, таких как кардиостимуляторы, микровол#
новые печи и автопилоты.
Цикл с итератором выполняет некоторые действия однократно для каждого
элемента контейнерного класса.
Первое, чем отличаются эти циклы друг от друга, — это гибкость в определении
числа итераций: выполняется ли цикл указанное количество раз или проверяет
необходимость завершения на каждом шаге.
Кроме того, эти варианты отличаются расположением проверки завершения. Вы
можете поместить проверку в начало, середину или конец цикла. Эта характери#
стика определяет, будет ли цикл выполняться хоть раз. Если условие цикла про#
веряется в начале, то его тело не обязательно будет выполняться. Если цикл про#
веряется в конце, то его тело выполняется минимум один раз. Если же проверка
находится в середине, то часть цикла, предшествующая ей, выполняется не менее
раза, а код, следующий за проверкой, не обязательно будет выполняться.
Гибкость и расположение проверки условия определяют тип цикла, выбираемый
в качестве управляющей структуры. В табл. 16#1 показаны разновидности циклов
в нескольких языках программирования и характеризуются их гибкость и распо#
ложение проверки.
Табл. 16-1. Типы циклов
Язык Тип цикла Гибкость Место проверки
Visual Basic For%Next Нет Начало
While%Wend Да Начало
Do%Loop%While Да Начало или конец
For%Each Нет Начало
C, C++, C#, Java for Да Начало
while Да Начало
do%while Да Конец
foreach1 Нет Начало
1
Реализован только в C#. На момент написания книги планируется в других языках, включая Java.
ГЛАВА 16 Циклы 361
> ...
If ( some exit condition ) Then Exit Do
Еще операторы.
> ...
Loop
Обычно цикл с выходом нужен, когда проверка условия в начале или конце цик#
ла требует кодирования полутора циклов. Вот пример на C++, в котором нужен
цикл с выходом, но он не используется:
…и повторяются здесь.
> GetNextRating( &ratingIncrement );
rating = rating + ratingIncrement;
}
Первые две строки в начале примера повторяются в последних двух строках цикла
while. При модификации вы легко можете забыть о поддержании двух параллель#
ных наборов строк. Другой программист, изменяющий код, возможно, и не дога#
дается, что эти строки должны сохраняться одинаковыми. В любом случае резуль#
татом будут ошибки, возникшие из#за неполной модификации. Вот как можно
переписать код более ясно:
GetNextScore( &scoreIncrement );
score = score + scoreIncrement;
}
GetNextScore( ScoreIncrement )
score = score + scoreIncrement
Loop
Start:
В C++ вы можете добиться того же эффекта без использования goto, как показано
в следующем примере. Если язык не поддерживает команду break, вы можете эму#
лировать ее, применив goto.
if ( !( expression ) ) {
break;
}
// Делаем чтото.
...
}
Здесь тело цикла пустое, потому что само выражение while делает две вещи: вы#
полняет циклические действия (inputChar = dataFile.GetChar()) и проверяет, завер#
шить ли работу цикла (inputChar != CharType_Eof). Цикл будет яснее, если его пе#
рекодировать так, чтобы его работа была более очевидной читателю:
Новый код занимает три полных строки по сравнению с одной строкой и точ#
кой с запятой. Но это допустимо, так как он и выполняет работу для трех строк, а
не для одной.
Располагайте служебные операции либо в начале, либо в конце цикла
Служебные операции цикла — это выражения вроде i = i + 1 или j++, чье основ#
ное назначение не выполнять работу в цикле, а управлять циклом. В этом приме#
ре показаны служебные действия, выполняемые в конце цикла:
Завершение цикла
Следующие подразделы описывают обработку конца цикла.
Убедитесь, что выполнение цикла закончилось Это основной принцип. Мыс#
ленно моделируйте выполнение цикла до тех пор, пока не будете уверены, что
при любых обстоятельствах он завершен. Продумайте номинальные варианты,
граничные точки и каждый из исключительных случаев.
Сделайте условие завершения цикла очевидным Если вы используете цикл
for, не забавляетесь с индексом цикла и не применяете операторы goto или break
для выхода из него, то условие завершения будет очевидным. Аналогично, если
вы используете циклы while или repeat%until и поместили все управление в выра#
жение while или repeat%until, условие завершения также будет очевидным. Смысл
в том, чтобы размещать управление в одном месте.
Не играйте с индексом цикла for для завершения цикла Некоторые про#
граммисты взламывают значение индекса цикла for для более раннего заверше#
ния цикла. Вот пример:
> i = 100;
}
// Еще код
...
}
знак любительского подхода. Когда вы задаете цикл for, манипуляции с его счет#
чиком должны быть под запретом. Для получения большей управляемости усло#
виями выхода используйте цикл while.
Избегайте писать код, зависящий от последнего значения индекса цикла
Использование значения индекса цикла после его завершения — дурной тон.
Конечное значение индекса меняется от языка к языку и от реализации к реали#
зации. Значения различаются, когда цикл завершается нормально или аномаль#
но. Даже если вы не задумываясь можете назвать это конечное значение, следую#
щему читателю кода, возможно, придется о нем задуматься. Более правильным
вариантом, к тому же более самодокументируемым, будет присвоение последне#
го значения какой#либо переменной в подходящем месте внутри цикла.
Этот код некорректно использует конечное значение индекса:
// Много кода
...
return( found );
мин для оператора break в C++, C и Java; выражений Exit%Do и Exit%For в Visual Basic
и подобных конструкций, включая имитации с помощью goto, в языках, не под#
держивающих break напрямую. Оператор break (или его эквивалент) приводит к
завершению цикла через нормальный канал выхода. Программа продолжает вы#
полнение с первого оператора, расположенного после цикла.
Оператор continue похож на break в том смысле, что это вспомогательное сред#
ство для управления циклом. Однако вместо выхода из цикла, continue заставляет
программу пропустить тело цикла и продолжить выполнение со следующей ите#
рации. Оператор continue — это сокращенный вариант блока if%then, предотвра#
щающего выполнение остальной части цикла.
Рассмотрите использование операторов break вместо логических фла'
гов в цикле while Порой добавление логических флагов в цикл while с целью
имитации выхода из тела цикла усложняет чтение кода. Иногда вы можете убрать
несколько уровней отступа в цикле и упростить его управление, просто исполь#
зуя break вместо группы проверок if. Размещение нескольких отдельных условий
break рядом с кодом, приводящим к их выполнению, может уменьшить вложен#
ность и сделать цикл читабельнее.
Остерегайтесь цикла с множеством операторов break, разбросанных по
всему коду Цикл, содержащий большое количество операторов break, может сиг#
нализировать о нечетком представлении структуры цикла или его роли в окру#
жающем коде. Рост числа break увеличивает вероятность, что цикл может быть более
ясно представлен в виде набора нескольких циклов вместо одного цикла с мно#
жеством выходов.
Согласно статье в «Software Engineering Notes» программная ошибка, которая 15
января 1990 года на 9 часов вывела из строя телефонную сеть Нью#Йорка, воз#
никла благодаря лишнему оператору break.(SEN, 1990):
> break;
...
}
...
} while ( ... );
Как вы думаете, что означают индексы в элементе transaction на этот раз? В этом
случае ответ получить проще, потому что имена переменных payCodeIdx, month
и divisionIdx гораздо красноречивее, чем i, j и k. Компьютер с одинаковой легко#
стью прочитает обе версии цикла. Однако людям легче будет читать вторую вер#
сию, чем первую, поэтому второй вариант лучше, поскольку ваша основная ауди#
тория состоит из людей, а не из компьютеров.
Используйте смысловые имена во избежание пересечения индексов При#
вычное использование переменных i, j и k приводит к увеличению риска пересе#
чения индексов — использованию одного и того же имени индекса для разных
целей. Взгляните:
376 ЧАСТЬ IV Операторы
// Промежуточный код.
for ( int recordCount = 0; recordCount < MAX_RECORDS; recordCount++ ) {
// Дополнительный циклический код, использующий другую переменную recordCount.
}
>rate = table[ ]
totalRate = totalRate + rate
Все, что вы должны сделать, — это поместить цикл for вокруг существующего кода
и добавить к нему пару begin%end. Напоследок убедитесь, что переменные, исполь#
зующие индекс цикла person, написаны правильно. В данном случае переменная
census изменяется вместе с person, поэтому ее следует корректно проиндексировать.
Если вы хотите добавить еще один цикл вокруг цикла person, продолжайте таким
же образом. Вы не должны жестко придерживаться этого порядка. Идея в том, чтобы
начать с чего#то определенного, думать только об одной задаче в каждый момент
времени и строить цикл из простых компонентов. Предпринимайте маленькие,
понятные шаги, постепенно обобщая и усложняя цикл. Таким образом, вы мини#
мизируете количество кода, на котором необходимо одновременно сосредоточи#
ваться и, следовательно, уменьшите вероятность ошибки.
В языке Java цикл для таких операций с массивами необходим. Но стоит заметить,
что циклические структуры и массивы не обязательно должны использоваться
вместе. Некоторые языки, особенно APL и Fortran 90 и более поздние, предостав#
ляют операции с массивами, исключающие необходимость применять такие циклы,
как только что продемонстрированные. Вот так выглядит фрагмент кода на APL,
выполняющий ту же операцию:
Ключевые моменты
Циклы сложны для понимания. Сохраняя их простыми, вы помогаете читате#
лям вашего кода.
К способам упрощения циклов относятся: избегание экзотических видов цик#
лов, минимизация вложенности, создание очевидных входов и выходов цикла
и хранение служебного кода в одном месте.
Индексы цикла часто употребляются неправильно. Называйте их понятно и
используйте только с одной целью.
Аккуратно продумайте весь цикл, чтобы убедиться, что он работает правиль#
но во всех случаях и завершается при любых возможных обстоятельствах.
382 ЧАСТЬ IV Операторы
Г Л А В А 1 7
Нестандартные
управляющие структуры
Содержание
http://cc2e.com/1778
17.1. Множественные возвраты из метода
17.2. Рекурсия
17.3. Оператор goto
17.4. Перспективы нестандартных управляющих структур
Связанные темы
Общие вопросы управления: глава 19
Последовательный код: глава 14
Код с условными операторами: глава 15
Код с циклами: глава 16
Обработка исключений: раздел 8.4
Несколько управляющих структур существует в сумрачной зоне между передовым
краем технологии и полной дискредитацией и несостоятельностью, и часто в одно
и то же время! Эти конструкции доступны не во всех языках, но там, где они есть,
они могут быть полезны при аккуратном применении.
’ Много кода.
...
17.2. Рекурсия
При рекурсии метод решает небольшую часть задачи, разбивает задачу на мень#
шие порции и вызывает сам себя для решения каждой из этих порций. Обычно
рекурсию применяют, когда небольшую часть задачи легко решить, а саму задачу
просто разложить на составные части.
Рекурсия не часто бывает необходима, но при аккуратном использова#
нии она позволяет создавать элегантные решения, как в этом примере,
где алгоритм сортировки иллюстрирует отличное применение рекурсии:
Примеры рекурсии
Допустим, у вас есть тип данных, представляющий лабиринт. Лабиринт — это
обычно некая сетка, в узлах которой вы можете повернуть направо, налево, пере#
меститься вверх или вниз. Часто существует возможность двигаться в нескольких
направлениях.
Как вы будете разрабатывать программу для поиска пути через лабиринт (рис. 17#1)?
Если вы используете рекурсию, ответ довольно прост. Вы начинаете от входа и
пробуете все возможные повороты, пока не найдете выхода. Попадая в точку в
первый раз, вы пробуете повернуть налево, если это невозможно, то пробуете пойти
вверх или вниз. В конце концов вы пытаетесь пойти направо. Вам не надо боять#
386 ЧАСТЬ IV Операторы
Первая строка кода проверяет, исследована ли уже данная точка. Одна из ключе#
вых задач рекурсивного метода — предотвращение бесконечной рекурсии. В дан#
ном случае, если вы не будете проверять, что эта развилка уже исследовалась, вы
можете бесконечно обследовать ее.
Второе выражение проверяет, не является ли эта позиция выходом из лабиринта.
Если ThisIsTheExit() возвращает true, метод тоже возвращает true.
Третье выражение запоминает, что вы посетили данную точку. Это предотвраща#
ет бесконечную рекурсию, которая может возникнуть в результате замкнутого пути.
Остальные строки пытаются найти выход при движении налево, вверх, вниз и
направо. Рекурсия прекратится, если метод когда#нибудь вернет true, т. е. будет
найден выход из лабиринта.
Логика, используемая в этом примере, довольно прямолинейна. Большинство людей
испытывают некоторый дискомфорт при виде рекурсивных методов, ссылающихся
сами на себя. Однако в данном случае альтернативное решение было бы гораздо
более трудоемким, и поэтому рекурсия отлично подходит.
Этот так называемый «простой» пример содержит ошибку. В случае, когда пере#
менная data равна %1, преобразованный код отслеживает %1 и выходит из цикла
до выполнения DoSomething(). Исходный код выполняет DoSomething() до того, как
%1 обнаружена. Автор книги по программированию, пытаясь показать, как легко
можно кодировать без goto, преобразовал собственный же пример некорректно.
Но ему не стоит расстраиваться — другие книги содержат похожие ошибки. Даже
профессионалы сталкиваются с трудностями при преобразовании кода, исполь#
зующего goto.
Вот более точная реорганизация кода без goto:
рых программист, полностью представляя себе альтернативы без goto, все же ре#
шает использовать его для улучшения читабельности и качества сопровождения.
Следующие разделы представляют случаи, в которых некоторые опытные програм#
мисты приводят доводы в пользу goto. В обсуждении рассматриваются примеры
кода с операторами goto и кода, переписанного без их использования, и оцени#
ваются достоинства и недостатки этих версий.
errorState = FileStatus_Success
fileIndex = 0
While ( fileIndex < numFilesToPurge )
fileIndex = fileIndex + 1
If Not ( FindFile( fileList( fileIndex ), fileToPurge ) ) Then
errorState = FileStatus_FileFindError
Здесь используется GoTo.
> END_PROC:
DeletePurgeFileList( fileList, numFilesToPurge )
End Sub
errorState = FileStatus_Success
fileIndex = 0
ГЛАВА 17 Нестандартные управляющие структуры 395
fileIndex = fileIndex + 1
Эта строка расположена через 13 строк после условия If, к которому она относится.
Тому, кто привык программировать без goto, возможно, будет легче читать этот
код, чем первоначальную версию. И если вы используете данный вариант, вам не
придется предстать перед судом противников goto.
Основной недостаток этого подхода с вложенными if в том,
Перекрестная ссылка Об отсту-
что уровень вложенности глубок, даже слишком. Для пони# пах и других вопросах размет-
мания кода вам нужно держать в голове весь набор вложен# ки кода см. главу 31. Об уров-
ных if одновременно. Более того, расстояние между кодом нях вложенности см. раздел 19.4.
обработки ошибок и кодом, ее инициирующим, слишком
велико: например, выражение, присваивающее переменной errorState значение
FileStatus_FileFindError, на 13 строк отстоит от соответствующей проверки if.
В варианте с goto ни одно выражение не отстоит более чем на четыре строки от
условия, которое его вызывает. И вам нет нужды держать в голове всю структуру
одновременно. По сути вы можете игнорировать все предыдущие условия, выпол#
ненные успешно, и сосредоточиться на следующей операции. В этом случае вер#
сия с goto гораздо удобнее для чтения и сопровождения, чем с вложенными if.
Переписать код с использованием статусной переменной Чтобы перепи#
сать код с использованием статусной переменной (также называемой перемен#
ной состояния), создайте переменную, которая будет показывать, не находится ли
метод в состоянии ошибки. В нашем случае метод уже содержит статусную пере#
менную errorState, так что вы можете использовать ее.
396 ЧАСТЬ IV Операторы
errorState = FileStatus_Success
fileIndex = 0
Условие While изменено — добавлена проверка errorState.
fileIndex = fileIndex + 1
Этот подход предполагает, что все вызовы функций в случае ошибки генерируют
исключения, а не возвращают коды ошибок.
398 ЧАСТЬ IV Операторы
else {
importantVariable = GetValue();
MID_LOOP:
// Много кода.
...
}
В общем случае написание нового метода — лучший подход. Однако иногда вы#
носить дублированный код в отдельный метод непрактично. В этом случае как
обходной маневр можно предложить реструктурирование условных выражений
так, чтобы оставить код в том же методе, а не выносить в отдельный:
Пример совместного использования кода в блоке else без применения goto (C++)
if ( ( statusOk && dataAvailable ) || !statusOk ) {
if ( statusOk && dataAvailable ) {
importantVariable = x;
}
400 ЧАСТЬ IV Операторы
else {
importantVariable = GetValue();
}
// Много кода.
...
}
Дополнительные ресурсы
Следующие материалы позволят расширить ваши представ#
http://cc2e.com/1792
ления о нестандартных управляющих структурах.
Возвраты
Fowler, Martin. Refactoring: Improving the Design of Existing Code. Reading, MA: Addison#
Wesley, 1999. В описании метода рефакторинга под названием «Замените вложенные
402 ЧАСТЬ IV Операторы
Операторы goto
Следующие статьи содержат полное обсуждение goto. Этот спор до сих пор воз#
никает время от времени на рабочих местах, в учебниках и журналах, но вы не
услышите ничего такого, что не было бы полностью исследовано 20 лет назад.
Dijkstra, Edsger. «Go To Statement Considered Harmful». Com%
http://cc2e.com/1799
munications of the ACM 11, no. 3 (March, 1968): 147–148, так#
же доступно по адресу www.cs.utexas.edu/users/EWD/. Это то
самое знаменитое письмо, которым Дейкстра поднес спичку к бумаге и воспла#
менил одну из самых долгих дискуссий в истории разработки ПО.
Wulf, W. A. «A Case Against the GOTO». Proceedings of the 25th National ACM Confer%
ence, August 1972: 791–797. Эта статья — еще один аргумент против беспорядоч#
ного использования goto. Вульф утверждает, что, если языки программирования
будут содержать необходимые управляющие структуры, необходимость в goto
исчезнет. С 1972 г., когда была написана эта статья, такие языки, как C++, Java и
Visual Basic, доказали свою корректность по Вульфу.
Knuth, Donald. «Structured Programming with go to Statements», 1974. Classics in
Software Engineering, edited by Edward Yourdon. Englewood Cliffs, NJ: Yourdon Press,
1979. Эта длинная статья не полностью посвящена goto, но содержит кучу приме#
ров кода, который становится эффективнее после исключения goto, и еще одну
кучу примеров кода, который становится эффективней после добавления goto.
Rubin, Frank. «‘GOTO Considered Harmful’ Considered Harmful». Communications of
the ACM 30, no. 3 (March, 1987): 195–196. В этой несколько резкой статье, обра#
щенной к редактору, Рубин утверждает, что программирование без goto стоило
бизнесу «сотни миллионов долларов». Затем он предлагает краткий фрагмент кода,
использующего goto, и утверждает, что он превосходит свои аналоги без goto.
Ответы, полученные на письмо Рубина, представляют больший интерес, чем само
письмо. Пять месяцев журнал «Communications of the ACM» (CACM) публиковал
письма, предлагающие разные версии программы Рубина из семи строк. Ответы
равномерно распределились между защитниками и хулителями goto. Читатели
предложили приблизительно 17 вариантов преобразования, которые полностью
покрывают все подходы к исключению goto. Редактор CACM заметил, что это письмо
вызвало больше откликов, чем любой другой вопрос, когда#либо обсуждавшийся
на страницах CACM.
Последовавшие письма можно найти в номерах:
Communications of the ACM 30, no. 5 (May, 1987): 351–355;
Communications of the ACM 30, no. 6 (June, 1987): 475–478;
Communications of the ACM 30, no. 7 (July, 1987): 632–634;
Communications of the ACM 30, no. 8 (August, 1987): 659–662;
Communications of the ACM 30, no. 12 (December, 1987): 997, 1085.
ГЛАВА 17 Нестандартные управляющие структуры 403
Возвраты
Используют ли методы операции возврата только при необходимости?
Улучшают ли операторы возврата читабельность?
Рекурсия
Содержит ли рекурсивный метод код для прекращения рекурсии?
Использует ли метод счетчик безопасности для гарантии того, что выпол-
нение будет завершено?
Ограничена ли рекурсия одним методом?
Соответствует ли глубина рекурсии ограничениям, налагаемым размерами
стека программы?
Является ли рекурсия лучшим способом реализации метода? Не лучше ли
использовать простые итерации?
goto
Используются ли операторы goto только как последнее средство и лишь для
того, чтобы сделать код удобнее для чтения и сопровождения?
Если goto используется ради эффективности, был ли прирост эффективно-
сти измерен и задокументирован?
Ограничено ли использование goto одной меткой на метод?
Выполняются ли переходы goto только вперед, а не назад?
Все ли метки goto используются?
Ключевые моменты
Множественные возвраты могут улучшить читабельность и сопровождаемость
метода и помогают избежать глубокой вложенности. Тем не менее использо#
вать их нужно осторожно.
Рекурсия предлагает изящное решение для небольшого набора задач. Ее тоже
нужно использовать аккуратно.
Иногда операторы goto — лучший способ облегчить чтение и сопровождение
кода. Таких случаев очень немного. Используйте goto только как последнее
средство.
404 ЧАСТЬ IV Операторы
Г Л А В А 1 8
Табличные методы
Содержание
http://cc2e.com/1865
18.1. Основные вопросы использования табличных
методов
18.2. Таблицы с прямым доступом
18.3. Таблицы с индексированным доступом
18.4. Таблицы со ступенчатым доступом
18.5. Другие примеры табличного поиска
Связанные темы
Сокрытие информации: подраздел «Скрывайте секреты (к вопросу о сокрытии
информации)» раздела 5.3
Проектирование классов: глава 6
Использование таблиц решений для замены сложной логики: раздел 19.1
Замена сложных выражений табличным поиском: раздел 26.1
Табличный метод — это схема, позволяющая искать информацию в таблице, а не
использовать для этого логические выражения, такие как if и case. Практически
все, что вы можете выбирать посредством логических операторов, можно выби#
рать, применяя таблицы. В простых случаях логические выражения проще и по#
нятней. Но при усложнении логических построений таблицы становятся все при#
влекательнее.
Если вы уже знакомы с табличными методами, считайте эту главу обзором. В этом
случае вы можете изучить «Пример гибкого формата сообщения» (раздел 18.2)
в качестве иллюстрации того факта, что объектно#ориентированный дизайн не
обязательно лучше других вариантов только потому, что он объектно#ориенти#
рованный. После этого можете переходить к обсуждению общих вопросов управ#
ления в главе 19.
ГЛАВА 18 Табличные методы 405
прямой доступ;
индексированный доступ;
ступенчатый доступ.
Каждый из этих вариантов доступа подробно описан ниже.
Второй вопрос, который нужно решить при использовании табличных
методов: что хранить в таблице. Иногда результатом поиска в таблице
являются данные — тогда можно хранить в таблице сами данные. Если
же результатом поиска является действие, код, который описывает это действие,
можно хранить, а в некоторых языках можно хранить ссылку на метод, выпол#
няющий это действие. В каждом из этих случаев таблицы усложняются.
days = 30
ElseIf ( month = 7 ) Then
days = 31
ElseIf ( month = 8 ) Then
days = 31
ElseIf ( month = 9 ) Then
days = 30
ElseIf ( month = 10 ) Then
days = 31
ElseIf ( month = 11 ) Then
days = 30
ElseIf ( month = 12 ) Then
days = 31
End If
А теперь вместо создания длинного выражения if для выяснения числа дней в месяце
можно просто обратиться к элементу массива:
Если вы хотите учитывать високосные годы в этой версии табличного поиска, код
все еще будет простым. Допустим, LeapYearIndex() возвращает 0 или 1:
ния и от того, курит ли страхователь. Если бы вы писали для этих ставок логиче#
скую управляющую структуру, то получилось бы нечто вроде этого:
Однако лучшее решение — создать массив ставок не только для каждого возрас#
та, но вообще для всех факторов. Вот как объявить такой массив на Visual Basic:
Пример объявления данных для заполнения таблицы ставок страхования (Visual Basic)
Public Enum SmokingStatus
SmokingStatus_First = 0
SmokingStatus_Smoking = 0
SmokingStatus_NonSmoking = 1
SmokingStatus_Last = 1
End Enum
Определив массив, необходимо придумать способ его за# Перекрестная ссылка Одно из
полнения. Вы можете использовать операторы присваива# преимуществ табличного подхо-
ния, читать данные из дискового файла, вычислять данные да в том, что можно поместить
или делать что#то еще. После подготовки данные могут при# данные из таблицы в файл и
меняться при расчете ставок. Сложная логическая струк# читать его во время выполне-
ния. Это позволит вам изменять
тура, показанная ранее, заменяется простым выражением, такие параметры, как ставки
например: страхования, не изменяя саму
программу (см. раздел 10.6).
Пример элегантного способа определения
ставки страхования (Visual Basic)
rate = rateTable( smokingStatus, gender, maritalStatus, age )
Логический подход
Используя логический подход, вы, вероятно, прочитали бы каждое сообщение,
проверили его идентификатор, а затем вызвали метод, разработанный для чтения,
преобразования и печати каждого сообщения. Имей вы 20 типов сообщений, вы
создали бы 20 методов. Для поддержки пришлось бы написать неизвестное коли#
чество методов более низкого уровня. Так, вы могли бы создать метод PrintBuoy%
TemperatureMessage() для печати сообщения о температуре. Объектно#ориентиро#
ванный подход не дал бы никаких преимуществ: скорее всего вы задействовали
бы абстрактный объект, представляющий сообщение, и породили от него подклас#
сы для каждого типа сообщения.
При каждом изменении формата какого#нибудь сообщения вам пришлось бы
менять логику в соответствующем классе или методе. Если в приведенном выше
содержимом сообщения поле со средней температурой поменяло бы тип с пла#
вающей запятой на иной, вам пришлось бы изменить логику метода PrintBuoyTempe%
ratureMessage(). (А если бы изменился тип самого бакена, вам бы пришлось раз#
рабатывать класс для нового!)
В логическом подходе метод для чтения сообщений состоит из цикла, читающе#
го каждое сообщение, декодирующего его идентификатор, а затем вызывающего
на основе этого идентификатора один из 20 методов. Вот пример псевдокода
логического подхода:
Пока есть сообщения для чтения
Перекрестная ссылка Этот псев-
Прочитать заголовок сообщения
докод низкого уровня исполь-
Декодировать идентификатор сообщения из заголовка зуется в иных целях, нежели
Если заголовок сообщения соответствует типу 1, псевдокод, предназначенный
то напечатать сообщение 1го типа. для проектирования метода. О
Иначе, если заголовок сообщения соответствует типу 2, разработке с помощью псевдо-
то напечатать сообщение 2го типа. кода см. главу 9.
...
412 ЧАСТЬ IV Операторы
Объектно-ориентированный подход
При использовании механического объектно#ориентированного подхода логи#
ка была бы скрыта в структуре унаследованных объектов, но основная структура
была бы столь же сложной:
Пока есть сообщения для чтения,
прочитать заголовок сообщения.
Декодировать идентификатор сообщения из заголовка.
Если заголовок сообщения соответствует типу 1,
то создать объект сообщения 1го типа.
Иначе, если заголовок сообщения соответствует типу 2,
то создать объект сообщения 2го типа.
...
Иначе, если заголовок сообщения соответствует типу 19,
то создать объект сообщения 19го типа.
Иначе, если заголовок сообщения соответствует типу 20,
то создать объект сообщения 20го типа.
Конец Если
Конец цикла Пока
Это код только для одного типа сообщений. Для каждого из оставшихся 19 типов
нужно реализовать похожий код. И если будет добавлен 21#й тип сообщения,
потребуется добавить 21#й метод или подкласс — в любом случае новый тип со#
общения потребует изменения существующего кода.
Табличный подход
Табличный подход экономичнее предыдущего. Метод чтения сообщений состо#
ит из цикла, который считывает заголовок каждого сообщения, декодирует его
идентификатор, находит описание сообщения в массиве Message, а затем всегда
вызывает один и тот же метод для декодирования сообщения. Этот подход позво#
ляет описать формат каждого сообщения в форме таблицы, а не задавать его же#
стко в логике программы. Это упрощает первоначальное программирование, со#
здает меньше кода и облегчает сопровождение программы без изменения кода.
Применение этого подхода начинается с перечисления типов сообщений и типов
полей. В C++ вы можете определить типы всех возможных полей таким образом:
Эта таблица может быть жестко закодирована в программе (в этом случае значе#
ния всех элементов будут присвоены переменным) или читаться из файла при
запуске программы или позже.
Поскольку определения сообщений поступают в программу извне, то вместо вне#
дрения информации в логику программы мы внедрили ее в данные. Данные обычно
гибче программной логики: их легко изменять, если меняется формат сообщения.
Если нужно добавить новый вид сообщений, вы можете просто добавить еще один
элемент в таблицу данных.
Вот псевдокод цикла верхнего уровня для табличного подхода:
Первые три строки такие же, как и при логическом подходе.
Пока есть сообщения для чтения,
прочитать заголовок сообщения,
декодировать идентификатор сообщения из заголовка,
найти описание сообщения в таблице описаний сообщений,
прочитать поля сообщения и напечатать их, основываясь на описании сообщения.
Конец цикла Пока
Нужно признать, что этот метод с шестью вариантами выбора длиннее, чем от#
дельный метод для печати температуры. Но это единственный метод, который вам
необходим. Вам не нужны остальные 19 функций для остальных 19 типов сооб#
щений. Данный метод обрабатывает шесть типов полей, и обслуживает все виды
сообщений.
Этот метод также иллюстрирует наиболее сложный способ реализации таблич#
ного поиска, так как использует оператор case. Другой подход — создание абст#
рактного класса AbstractField и последующее наследование от него подклассов для
каждого типа поля. Тогда вам не понадобится оператор case, вы сможете вызы#
вать метод#член соответствующего объектного типа.
Вот как можно создать такие объекты на C++:
Это ужасный диапазон для табличного поиска, потому что вы не можете напи#
сать простую функцию преобразования данных для соответствия буквам от A до
F. Индексная схема неудобна, так как используются числа с плавающей запятой.
Вы можете предложить конвертировать числа с плавающей запятой в целые, что
для данного случая вполне допустимо, однако в целях иллюстрации этот пример
будет придерживаться чисел с плавающей запятой.
Применяя ступенчатый метод, вы помещаете верхнюю границу каждого диапазо#
на в таблицу, а затем пишете цикл для сравнения количества баллов с этой верх#
ней границей. Обнаружив точку, в которой сумма баллов в первый раз превысит
заданный предел, вы узнаете оценку. Применяя ступенчатую методику, надо сле#
дить за правильной обработкой граничных точек диапазона. Вот пример кода Visual
Basic, присваивающей оценки группе студентов, основываясь на данных этого
примера:
Хотя это и простой пример, вы легко можете его обобщить для работы с несколь#
кими студентами или несколькими системами оценок (например, введя разные
оценки для различных уровней сложности выполняемых заданий), а также для
изменения системы оценок.
Преимущество этого подхода перед другими табличными методами в том, что он
хорошо работает с нестандартными данными. Пример с оценками прост с той
точки зрения, что, хотя оценки и присваиваются через неодинаковые промежут#
ГЛАВА 18 Табличные методы 421
Такие ужасные числа сводят на нет все попытки создать функцию для их точного
преобразования в табличные ключи. В этом случае следует использовать ступен#
чатый метод.
Этот подход также позволяет оценить главные преимущества табличных методов:
гибкость и модифицируемость. Если диапазоны баллов в примере с оценками надо
изменить, программу легко исправить, изменив элементы массива RangeLimit. Вы
легко можете обобщить метод выставления отметок так, чтобы он принимал из#
вне таблицы с отметками и соответствующими граничными значениями баллов.
При выставлении оценок не обязательно использовать баллы, выраженные в про#
центах, их можно поменять на обычные единицы, и это не потребует больших
изменений в программе.
Вот несколько тонкостей, которые надо принимать во внимание, применяя сту#
пенчатый метод.
Следите за граничными точками Убедитесь, что вы учитываете верхнюю гра#
ницу каждого диапазона. Выполните ступенчатый поиск так, чтобы он находил
значения, соответствующие любому интервалу, кроме самого верхнего, а затем за#
дайте несколько элементов, попадающих в этот верхний интервал. Иногда требу#
ется создать искусственное значение для верхней границы последнего интервала.
Не ошибитесь с операциями < или <=! Убедитесь, что при значениях, попадаю#
щих в верхний диапазон, цикл корректно завершается, а также что границы ин#
тервалов обрабатываются правильно.
Рассмотрите вопрос использования бинарного поиска вместо последова'
тельного В примере с оценками цикл, присваивающий отметки, последователь#
но проходит по списку предельных значений баллов. Если у вас будет более длин#
ный список, затраты на последовательный поиск могут чрезмерно возрасти. В этом
случае можно воспользоваться квази#бинарным поиском. «Квази» он потому, что
цель большинства бинарных поисков — нахождение значения. В данном случае
вы не намерены найти значение — вы ищете правильную категорию для этого
значения. Алгоритм бинарного поиска должен корректно определить, куда это
422 ЧАСТЬ IV Операторы
Ключевые моменты
Таблицы представляют собой альтернативу сложной логике и структурам с
наследованием. Если вы понимаете, что сбиты с толку логикой программы или
деревом наследования, спросите себя, не проще ли использовать таблицу по#
иска.
Основной вопрос при использовании таблиц состоит в выборе способа до#
ступа к таблице. Вы можете использовать прямой, индексный или ступенча#
тый доступ.
Другой основной вопрос состоит в выборе того, что конкретно будет поме#
щено в таблицу.
424 ЧАСТЬ IV Операторы
Г Л А В А 1 9
Содержание
http://cc2e.com/1978
19.1. Логические выражения
19.2. Составные операторы (блоки)
19.3. Пустые выражения
19.4. Укрощение опасно глубокой вложенности
19.5. Основа программирования: структурное програм#
мирование
19.6. Управляющие структуры и сложность
Связанные темы
Последовательный код: глава 14
Код с условными операторами: глава 15
Код с циклами: глава 16
Нестандартные управляющие структуры: глава 17
Сложность в разработке ПО: подраздел «Главный Технический Императив Раз#
работки ПО: управление сложностью» раздела 5.2
Обсуждение способов управления было бы неполным без углубления в несколь#
ко общих вопросов, касающихся управляющих структур. Большая часть этой гла#
вы посвящена подробностям их практического применения. Если вам интерес#
ней читать о теории управляющих структур, а не о мелких деталях, сосредоточь#
тесь на исторической перспективе структурного программирования (раздел 19.5),
а затем на взаимодействии управляющих структур (раздел 19.6).
Если использование флажков 0 и 1 — общая практика, то чем она плоха? При чте#
нии кода неочевидно, когда выполняются вызовы функций: когда проверки усло#
вия истинны или когда они ложны. Ничего в этом фрагменте не говорит о том,
представляет ли 1 «истину», а 0 — «ложь» или же все наоборот. Нельзя даже утвер#
ждать, что 1 и 0 служат в качестве значений «истина» и «ложь». Например, в строке
If reportSelected = 1 число 1 может легко обозначать первый отчет, 2 — второй, 3 —
третий; ничто в коде не наводит на мысль, что 1 означает либо «истину», либо «ложь».
Кроме того, легко можно написать 0, имея в виду 1, и наоборот.
Используйте термины true и false для проверки логических выражений. Если ваш
язык не поддерживает их напрямую, создайте их с помощью макросов препроцес#
сора или глобальных переменных. Вот как можно переписать предыдущий пример,
используя встроенные идентификаторы True и False языка Visual Basic:
Применение констант 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 ) ...
Это условие выглядит ужасно, и вам приходится его читать, даже если оно вам
неинтересно. Поместив его в логическую функцию, вы сможете изолировать эту
проверку и позволите читателю забыть о ней, пока она не понадобится. Вот как
можно поместить условие if в функцию:
End Function
Если вы проверяете условие только раз, вам может показаться, что его
не стоит помещать в отдельный метод. Но вынесение условия в отдель#
ную, хорошо названную функцию улучшит читабельность кода и упрос
тит понимание того, что этот код делает. Это достаточная причина для создания
функции. Имя нового метода привносит в программу абстракцию, которая доку#
ментирует назначение проверки прямо в коде. Это даже лучше, чем документи#
рование условия с помощью комментариев, потому что код будет читаться и мо#
дифицироваться с большей вероятностью, чем комментарии.
Используйте таблицы решений для замены сложных
Перекрестная ссылка Об ис-
условий Иногда нужно проверять сложные условия, со#
пользовании таблиц для заме-
держащие несколько переменных. В этом случае для выпол#
ны сложной логики см. главу 18.
нения проверки удобней применять таблицы решений, а не
операторы if или case. Таблицу решений изначально проще кодировать — она
требует пары строк кода и никаких изощренных управляющих структур. Такая ми#
нимизация сложности уменьшает возможность ошибок. При изменении данных
вы можете изменить таблицу решений, не меняя код: вам всего лишь надо обно#
вить содержимое структуры данных.
> if ( !statusOK ) {
// Делаем чтото.
...
}
else {
// Делаем чтото еще.
...
}
ГЛАВА 19 Общие вопросы управления 429
> if ( statusOK ) {
// Делаем чтото еще.
Код в этом блоке был поменян местами...
> ...
}
else {
...с кодом в этом блоке.
В данном случае вам не надо менять местами блоки if и else — выражения в двух
последних фрагментах кода логически эквивалентны. Для применения теорем
Деморгана к логическому оператору and или or и паре операндов вы инвертиру#
ете оба операнда, меняете местами операторы and и or и инвертируете все выра#
жение целиком. Табл. 19#1 обобщает возможные преобразования в соответствии
с теоремами Деморгана.
430 ЧАСТЬ IV Операторы
Начнем с того, что это выражение слишком запутано. Оно тем более сбивает с толку,
что не ясно, хотел ли кодировщик проверить условие ( a < b ) == ( c == d ) или ( ( a <
b ) == c ) == d. Следующая версия все равно не идеальна, но скобки все же помогают:
Этот вариант корректен, так как item[ i ] будет вычисляться, только когда i мень#
ше, чем maxElements.
Многие современные языки предоставляют средства, которые изначально предот#
вращают возможность возникновения такой ошибки. Так, C++ использует корот#
козамкнутые вычисления: если значение первого операнда в операции and лож#
но, то второй операнд не вычисляется, потому что полное выражение в любом
случае будет ложным. Иначе говоря, в C++ единственный элемент выражения:
if ( SomethingFalse && SomeCondition ) ...
а не:
while ( balance ) ...
ГЛАВА 19 Общие вопросы управления 435
а не:
while ( *charPtr ) ...
а не:
while ( bufferPtr ) ...
Потом:
for ( i = 0; i < maxLines; i++ ) { }
И наконец:
for ( i = 0; i < maxLines; i++ ) {
// Все что угодно ...
}
ГЛАВА 19 Общие вопросы управления 437
Это относится ко всем блоковым структурам, включая операторы if, for и while в
C++ и Java и сочетания If%Then%Else, For%Next и While%Wend в Visual Basic.
Используйте скобки для пояснения условных операторов Довольно слож#
но разобраться в коде условного оператора, не разбираясь в действиях, выполня#
емых в результате проверки условия if. Размещение единственного оператора после
if иногда выглядит эстетически правильным, но в процессе сопровождения такие
выражения часто превращаются в более сложные блоки, и одиночный оператор
в таких случаях может привести к ошибке.
Для пояснения ваших намерений используйте блок независимо от того, сколько
в нем строк кода: 1 или 20.
В C++ после while должно следовать выражение, но оно может быть пустым. От#
дельная точка с запятой и является пустым оператором. Далее приведены прин#
ципы работы с пустыми выражениями в C++:
Привлекайте внимание к пустым выражениям Пус# Перекрестная ссылка Возмож-
тые выражения не являются широко распространенными, но, лучший способ обрабаты-
поэтому сделайте их очевидными. Один из способов — вы# вать пустые операторы — это
делить точке с запятой, представляющей собой пустой опе# избегать их (см. подраздел «Из-
ратор, отдельную строку. Этот подход показан в предыду# бегайте пустых циклов» разде-
ла 16.2).
щем примере. В качестве альтернативы можно использовать
пустые скобки, чтобы подчеркнуть это выражение. Приве#
дем два примера:
Вот как создать собственный пустой оператор в C++ с помощью #define. (Вы так#
же можете создать inline#функцию, которая дает тот же эффект.)
if ( AllocMem( &printData ) ) {
// Много кода.
...
}
}
// Много кода.
...
if ( !SetupPage() ) {
break; // Выходим из блока.
}
// Много кода.
...
if ( !AllocMem( &printData ) ) {
break; // Выходим из блока.
}
// Много кода.
...
} while (FALSE); // Конец блока с выходом
Этот способ довольно необычен, поэтому его следует использовать, только если
вся ваша команда разработчиков с ним знакома и он одобрен в качестве подхо#
дящей практики кодирования.
Преобразуйте вложенные if в набор ifthenelse Если вы критически отно#
ситесь к вложенными условиями if, вам будет интересно узнать, что вы можете ре#
ГЛАВА 19 Общие вопросы управления 441
Этот фрагмент имеет много недостатков, один из которых в том, что проверяе#
мые условия избыточны. Когда вы удостоверились, что значение quantity больше
1000, вам не нужно дополнительно проверять, что оно больше 100 и больше 10.
А значит, вы можете преобразовать этот код таким образом:
Это решение проще, чем могло бы быть, потому что закономерность увеличения
чисел проста. Вот как изменить вложенные if, если бы числа не были так упоря#
дочены:
Пример читается, как стихи. Если вы сравните его с двумя приведенными ранее
— многочисленными отступами, он покажется особенно понятным решением.
Факторизуйте глубоко вложенный код в отдельный метод Если глубокая
вложенность создается внутри цикла, вы зачастую можете улучшить ситуацию, пе#
реместив содержимое цикла в отдельный метод. Это особенно эффективно, если
вложенность является результатом как проверок условий, так и итераций. Оставьте
блоки if%then%else в основном цикле, чтобы показать ветвление решения, а содер#
жимое этих блоков переместите в новые методы. Следующий код нуждается в такой
модификации:
Этот код сложен, но бывает и хуже. Он имеет всего четыре уровня вложенности,
содержит комментарии и логические отступы, а его функциональная декомпози#
ция достаточно адекватна, особенно для типа транзакции TransactionType_Transfer.
И все же вы можете улучшить этот код, вынеся содержимое внутренних if#прове#
рок в отдельные методы.
444 ЧАСТЬ IV Операторы
case ( TransactionType_Withdrawal ):
ProcessWithdrawal(
transaction.AccountType,
transaction.AccountNum,
transaction.Amount
);
break;
case ( TransactionType_Transfer ):
MakeFundsTransfer(
transaction.SourceAccountType,
transaction.TargetAccountType,
transaction.AccountNum,
transaction.Amount
);
break;
default:
// Обрабатываем неизвестный тип транзакции.
LogTransactionError(“Unknown Transaction Type”, transaction );
break;
}
}
while ( !TransactionsComplete() ) {
// Читаем транзакционную запись.
transactionData = ReadTransaction();
case ( TransactionType_Withdrawal ):
transaction = new Withdrawal( transactionData );
break;
case ( TransactionType_Transfer ):
transaction = new Transfer( transactionData );
break;
default:
// Обрабатываем неизвестный тип транзакции.
LogTransactionError(“Unknown Transaction Type”, transactionData );
return;
}
transaction>Complete();
delete transaction;
}
while ( !TransactionsComplete() ) {
// Читаем транзакционную запись и выполняем транзакцию.
transactionData = ReadTransaction();
transaction = TransactionFactory.Create( transactionData );
transaction>Complete();
delete transaction;
}
ГЛАВА 19 Общие вопросы управления 447
case ( TransactionType_Withdrawal ):
return new Withdrawal( transactionData );
break;
case ( TransactionType_Transfer ):
return new Transfer( transactionData );
break;
default:
// Обрабатываем неизвестный тип транзакции.
LogTransactionError( “Unknown Transaction Type”, transactionData );
return NULL;
}
}
Последовательность
Последовательность — это набор операторов, выполняющих#
Перекрестная ссылка Об ис-
ся по порядку. Типичные последовательные операторы содер#
пользовании последовательно-
стей см. главу 14. жат присваивания и вызовы методов. Вот два примера:
ГЛАВА 19 Общие вопросы управления 449
Выбор
Выбор — это такая управляющая конструкция, которая зас#
Перекрестная ссылка Об исполь-
тавляет операторы выполняться избирательно. Наиболее час# зовании выбора см. главу 15.
тый пример — выражение if%then%else. Выполняется либо блок
if%then, либо else, но не оба сразу. Один из блоков «выбирается» для выполнения.
Оператор case — другой пример управляющего элемента выбора. Оператор switch
в C++ и Java, оператор select — все это примеры case. В каждом случае для выпол#
нения выбирается один из вариантов. Концептуально операторы if и case похо#
жи. Если ваш язык не поддерживает операторы case, вы можете эмулировать их с
помощью набора if. Вот два примера выбора:
Итерация
Итерация — это управляющая структура, которая заставля#
Перекрестная ссылка Об ис-
ет группу операторов выполняться несколько раз. Итерацию
пользовании итераций см. гла-
ву 16. обычно называют «циклом». К итерациям относятся струк#
туры For%Next в Visual Basic и while и for в C++ и Java. Этот
фрагмент кода содержит примеры итераций на Visual Basic:
Приведем пример:
if ( ( (status = Success) and done ) or
( not done and ( numLines >= maxLines ) ) ) then ...
Ключевые моменты
Упрощение и облегчение чтения логических выражений вносит существенный
вклад в качество вашего кода.
Глубокая вложенность затрудняет понимание метода. К счастью, вы сравнитель#
но легко можете ее избежать.
Структурное программирование — это простая, но все еще злободневная идея:
вы можете построить любую программу с помощью комбинации последова#
тельностей, выборов и итераций.
Уменьшение сложности — ключ к написанию высококачественного кода.
ГЛАВА 19 Общие вопросы управления 455
Часть V
УСОВЕРШЕНСТВОВАНИЕ
КОДА
Г Л А В А 2 0
Качество ПО
Содержание
http://cc2e.com/2036
20.1. Характеристики качества ПО
20.2. Методики повышения качества ПО
20.3. Относительная эффективность методик контроля
качества ПО
20.4. Когда выполнять контроль качества ПО?
20.5. Главный Закон Контроля Качества ПО
Связанные темы
Совместное конструирование: глава 21
Тестирование, выполняемое разработчиками: глава 22
Отладка: глава 23
Предварительные условия конструирования: главы 3 и 4
Актуальность предварительных условий для современных программных про#
ектов: соответствующий подраздел раздела 3.1
В этой главе мы рассмотрим методики повышения качества ПО в контексте кон#
струирования. Конечно, вся эта книга посвящена повышению качества ПО, но в
данной главе вопросы качества и контроля качества обсуждаются более целена#
правленно. Темой этой главы являются скорее общие вопросы, а не практичес#
кие методики контроля качества. Практические советы, касающиеся совместной
разработки, а также тестирования и отладки, можно найти в трех следующих главах.
Процесс разработки
Каждый из упомянутых элементов явно связан с контролем
Дополнительные сведения Разра-
качества и неявно — с процессом разработки ПО. Методи#
ботка ПО как процесс обсужда-
ки разработки, включающие действия, направленные на
ется в книге «Professional Software
контроль качества, способствуют созданию более качествен#
Development» (McConnell, 1994).
ного ПО. Другие процессы, не являющиеся сами по себе
аспектами контроля качества, также влияют на качество ПО.
Процедуры контроля изменений Повышению качества
Перекрестная ссылка О контро-
ПО часто препятствуют неконтролируемые изменения. Не#
ле изменений см. раздел 28.2.
контролируемые изменения требований могут снижать эф#
фективность проектирования и кодирования. Неконтролируемые изменения про#
екта могут приводить к несоответствию кода и требований, несогласованности
отдельных фрагментов кода и лишней трате времени на вынужденное изменение
кода. Неконтролируемые изменения самого кода могут приводить к появлению в
нем внутренних противоречий и затрудняют слежение за тем, какой код был под#
ГЛАВА 20 Качество ПО 461
Задание целей
Явное задание целевых аспектов качества — простой и очевидный способ повы#
шения качества ПО, но его легко упустить. Будут ли программисты на самом деле
преследовать явные заданные цели? Да, будут, если цели будут им известны и если
цели будут разумны. Программисты не могут стремиться к достижению постоян#
но изменяющихся или недостижимых .
Джеральд Вайнберг и Эдвард Шульман провели один очень интересный экспери#
мент, посвященный изучению влияния задания целевых аспектов качества на
производительность труда программистов (Weinberg and Schulman, 1974). Они
попросили пять групп разработчиков написать одну и ту же программу и указали
одни и те же пять целевых характеристик качества, но каждой группе было ска#
зано оптимизировать разные характеристики: одной — минимизировать объем
используемой программой памяти, второй — обратить максимальное внимание
на ясность выводимых данных, третьей — создать как можно более удобочитае#
мый код, четвертой — сделать программу максимально компактной, а пятой —
написать программу за минимальное время. В табл. 20#1 показано, какое место
заняла каждая группа по каждому целевому показателю.
462 ЧАСТЬ V Усовершенствование кода
ирования скорее всего повлияет только на один метод или класс. Это еще одно
убедительное обоснование как можно более раннего нахождения ошибок.
Дефекты проникают в ПО на всех стадиях разработки, поэтому контро#
лю качества следует уделять должное внимание на всех этапах проекта,
начиная с самых ранних. Контроль качества нужно внести в планы в
начале работы над программой; его следует выполнять по мере прогресса; нако#
нец, он должен подчеркивать удачное завершение работы над проектом.
Рис. 20'2. Ни при самом быстром, ни при самом медленном подходе к разработке
ПО не наблюдается наибольший уровень дефектов
Дополнительные ресурсы
Составить список книг для этой главы несложно, потому что
методики повышения качества ПО и производительности http://cc2e.com/2050
труда описываются почти во всех трудах, посвященных эф#
фективным методологиям разработки ПО. Сложность в том, чтобы выделить книги,
касающиеся непосредственно качества ПО. Ниже я указал две такие работы.
Ginac, Frank P. Customer Oriented Software Quality Assurance. Englewood Cliffs, NJ: Prentice
Hall, 1998. В этой очень краткой книге описаны атрибуты качества, метрики каче#
ства, программы контроля качества, роль тестирования в контроле качества, а так#
же известные программы повышения качества, в том числе модель CMM, разрабо#
танная в институте Software Engineering Institute, и стандарты ISO серии 9000.
Lewis, William E. Software Testing and Continuous Quality Improvement, 2d ed. Auer#
bach Publishing, 2000. В этой книге можно найти подробное обсуждение цикла
контроля качества, а также методик тестирования. Кроме того, в ней вы найдете
много контрольных форм и списков.
Соответствующие стандарты
IEEE Std 730%2002 — стандарт IEEE планирования контроля
http://cc2e.com/2057
качества ПО.
IEEE Std 1061%1998 — стандарт IEEE методологии метрик
качества ПО.
IEEE Std 1028%1997 — стандарт обзоров ПО.
470 ЧАСТЬ V Усовершенствование кода
Ключевые моменты
Высокого качества можно достичь без дополнительных затрат, но для этого
вы должны перераспределить ресурсы и предотвращать дефекты вместо того,
чтобы их исправлять.
Стремление к одним характеристикам качества препятствует достижению дру#
гих. Четко определите цели, имеющие для вас первостепенную важность, и
сообщите об этом всем членам группы.
Никакая методика обнаружения дефектов не является достаточно эффектив#
ной. Тестирование само по себе — не самый лучший способ устранения оши#
бок. Составляя программу контроля качества, предусмотрите применение не#
скольких методик, позволяющих обнаружить разные виды ошибок.
Существуют многие эффективные методики контроля качества, применяемые
как во время конструирования, так и до его начала. Чем раньше вы обнаружи#
те дефект, тем слабее он переплетется с остальным кодом и тем меньше вреда
он успеет принести.
В мире программирования контроль качества ориентирован на процесс.
В отличие от промышленного производства разработка ПО не включает по#
вторяющегося этапа, влияющего на конечный продукт, поэтому качество ре#
зультата определяется процессом, используемым для разработки ПО.
ГЛАВА 20 Качество ПО 471
Г Л А В А 2 1
Совместное
конструирование
Содержание
http://cc2e.com/2185
21.1. Обзор методик совместной разработки ПО
21.2. Парное программирование
21.3. Формальные инспекции
21.4. Другие методики совместной разработки ПО
Связанные темы
Качество ПО: глава 20
Тестирование, выполняемое разработчиками: глава 22
Отладка: глава 23
Предварительные условия конструирования: главы 3 и 4
Вероятно, вам знакома одна довольно распространенная ситуация. Вы подходите
к столу другого программиста и говорите: «Не мог бы ты взглянуть на этот код? Он
не работает». Вы начинаете объяснять: «Причиной не может быть вот это, потому
что я сделал то#то и то#то. Причиной также не может быть это, потому что я сделал
вот это. Кроме того, причиной не может быть… подожди… Это может быть причи#
ной. Спасибо!» Вы решили проблему, хотя ваш «помощник» не произнес ни слова.
Так или иначе все методики совместного конструирования направлены на фор#
мализацию процесса проверки вашей работы другими программистами с целью
устранения ошибок.
Если вы уже читали об инспекциях и парном программировании, вы найдете в
этой главе мало нового. Возможно, вас заинтересуют данные об эффективности
инспекций (раздел 21.3), а также методика чтения кода (раздел 21.4). Вы также
можете взглянуть на табл. 21#1 «Сравнение методик совместного конструирова#
ния» в конце главы. Если ваши знания основаны только на опыте, читайте даль#
ше! Каждый человек имеет собственный опыт, поэтому некоторые идеи окажутся
для вас новыми.
472 ЧАСТЬ V Усовершенствование кода
Регулярно меняйте состав пар и назначаемые парам задачи Как и при дру#
гих методиках совместной разработки, при парном программировании выгода
объясняется тем, что каждый из программистов изучает разные части системы.
Регулярно меняйте состав пар для стимуляции «перекрестного опыления» — не#
которые эксперты рекомендуют выполнять это каждый день (Reifer, 2002).
Объединяйте в пару людей, предпочитающих одинаковый темп работы
Если один партнер работает слишком быстро, парное программирование начи#
нает терять смысл. Более быстрый член пары должен снизить темп, или пару сле#
дует разбить и сформировать в другом составе.
Убедитесь, что оба члена пары видят экран Эффективность парного про#
граммирования могут снижать даже такие, казалось бы, банальные вопросы, как не#
правильное расположение монитора и слишком мелкий шрифт.
Не объединяйте в пару людей, которые не нравятся друг другу Эффек#
тивность работы в паре зависит от соответствия характеров двух программистов.
Бессмысленно объединять в пару людей, которые плохо ладят друг с другом (Beck,
2000; Reifer, 2002).
Не составляйте пару из людей, которые ранее не программировали в паре
Парное программирование приносит максимальную выгоду, если хотя бы один
из партнеров имеет опыт работы в паре (Larman, 2004).
Назначьте лидера группы Если все члены вашей группы хотят выполнить все
программирование в парах, возложите на кого#то ответственность за распреде#
ление задач, контроль результатов и связь с людьми, не участвующими в проекте.
ясных моментов инспектируемого проекта или кода. Как я уже отмечал, проект и
код должны говорить сами за себя.
Подготовка Каждый инспектор сам ищет в проекте или
Перекрестная ссылка Перечень
контрольных списков, помога- коде ошибки, руководствуясь при этом полученным конт#
ющих повысить качество кода, рольным списком.
приведен после содержания
Выполняя обзор прикладного кода, написанного на высо#
книги.
коуровневом языке, инспектор может проанализировать за
час около 500 строк. При обзоре системного кода, также написанного на высо#
коуровневом языке, производительность труда инспектора составляет лишь око#
ло 125 строк в час (Humphrey, 1989). Наиболее эффективный темп подготовки
может колебаться в широком диапазоне, поэтому храните данные о быстроте
подготовки к инспекциям в вашей организации — это поможет вам лучше гото#
виться к будущим инспекциям.
В некоторых организациях было обнаружено, что эффективность инспекций
повышается, если поручить каждому инспектору рассмотреть проблему под
определенным углом. Так, инспектора можно попросить подойти к инспекции с
точки зрения программиста, который будет сопровождать программу, клиента или
проектировщика. Пока что эта методика изучена недостаточно полно, но имею#
щиеся данные говорят о том, что такие обзоры позволяют найти больше ошибок,
чем общие обзоры.
Еще одной разновидностью подготовки к инспекции является выполнение каж#
дым инспектором одного или нескольких сценариев. Сценарии могут включать
конкретные вопросы, на которые инспектор должен дать ответ, например: «Есть
ли требования, которым не удовлетворяет этот проект?» Сценарий может также
ставить перед инспектором определенную задачу, такую как составление списка
требований, которым удовлетворяет конкретный проект. Вы также можете пору#
чить нескольким инспекторам проанализировать материал с начала до конца, в
обратном порядке или «вдоль и поперек».
Инспекционное собрание Координатор поручает одному из участников (не ав#
тору) начать изложение проекта или чтение кода (Wiegers, 2003) с объяснением
всей логики, в том числе всех ветвей каждой логической структуры. Во время этой
презентации секретарь записывает обнаруженные ошибки, но как только участ#
ники приходят к выводу, что они нашли ошибку, ее обсуждение прекращается.
Секретарь регистрирует тип и серьезность ошибки, и инспекция продолжается.
Если инспекция теряет фокус, координатору следует привлечь внимание группы
и вернуть обсуждение в нужное русло.
Темп рассмотрения проекта или кода не должен быть ни слишком медленным, ни
слишком быстрым. Если темп слишком низок, участники инспекции теряют кон#
центрацию, и продуктивность работы снижается. Если темп слишком высок, группа
может упустить ошибки, которые в противном случае были бы обнаружены. Как
и темп подготовки, оптимальный темп инспекции зависит от конкретной среды.
Храните соответствующие данные, чтобы со временем вы могли определить са#
мую эффективную скорость инспекции в своей организации. В некоторых ком#
паниях было обнаружено, что оптимальная скорость инспекции системного кода
равна 90 строкам в час. При инспекции прикладного кода скорость может дости#
ГЛАВА 21 Совместное конструирование 481
гать 500 строк в час (Humphrey, 1989). Если вы только начинаете проводить инс#
пекции, можете ориентироваться на анализ 150–200 непустых и не являющихся
комментариями строк исходного кода в час (Wiegers, 2002).
Не обсуждайте на собраниях способы решения проблем. Группа должна сосредо#
точиться на обнаружении дефектов. В некоторых группах участникам инспекций
даже запрещают обсуждать, действительно ли дефект является дефектом. Эти раз#
работчики исходят из того, что любой аспект проекта, кода или документации,
который хоть кому#то кажется дефектом, нуждается в пояснении.
Как правило, собрание не должно продолжаться более двух часов. Конечно, это
не значит, что по окончании двух часов вы должны подать ложный сигнал пожар#
ной тревоги, но опыт IBM и других компаний показывает, что инспекторы не могут
поддерживать нужную концентрацию более двух часов. По этой же причине не#
разумно проводить более одной инспекции в день.
Отчет об инспекции В день проведения инспекционного собрания коорди#
натор составляет отчет об инспекции (электронное письмо или что#либо подоб#
ное), указывая в нем все найденные дефекты, их тип и серьезность. Отчет об ин#
спекции помогает гарантировать исправление всех дефектов и облегчает созда#
ние контрольного списка, обращающего внимание на проблемы, специфические
для организации. Если вы храните данные о времени, затраченном на инспекции,
и о числе обнаруженных ошибок, вы сможете подтвердить эффективность инс#
пекций достоверными данными. Иначе вы сможете лишь сказать, что инспекции
кажутся оптимальным вариантом. Разумеется, это не убедит сторонников тести#
рования или других методик. Благодаря отчетам вы также сможете узнать, что ин#
спекции в вашей среде не работают. В этом случае вы можете изменить инспек#
ции или отказаться от них. Сбор данных важен и потому, что любая новая мето#
дология должна оправдывать свое использование.
Исправление дефектов Координатор поручает кому#нибудь — обычно авто#
ру — исправить все дефекты, указанные в составленном списке.
Контроль Координатор отвечает за контроль решения всех задач, поставлен#
ных во время инспекции. В зависимости от числа обнаруженных ошибок и их се#
рьезности вы можете поручить инспекторам провести повторную инспекцию в
полном объеме или проинспектировать только исправленные фрагменты. Кроме
того, вы можете позволить автору исправить дефекты без всякого контроля.
Дополнительные собрания Хотя во время инспекции участникам не дозволя#
ется обсуждать решения обнаруженных проблем, некоторые разработчики могут
испытывать такое желание. Вы можете провести неформальное дополнительное
собрание, позволяющее заинтересованным сторонам обсудить решения проблем
по окончании официальной инспекции.
Оптимизация инспекций
Накопив опыт проведения инспекций «по книге», вы скорее всего обнаружите
несколько способов их улучшения. Однако изменять инспекции следует дисцип#
линированно. Адаптируйте процесс выполнения инспекций так, чтобы вы могли
узнать, приносят ли изменения выгоду.
482 ЧАСТЬ V Усовершенствование кода
Инспекции: резюме
Используемые при инспекциях контрольные списки поддерживают концентра#
цию разработчиков на важных задачах. Стандартные контрольные списки и стан#
дартные роли обеспечивают систематичность процесса инспекции. Кроме того,
наличие петли формальной обратной связи, используемой для улучшения конт#
рольных списков и слежения за темпом подготовки к инспекциям и темпом их
проведения, делает процесс инспекции самоорганизующимся. Как бы инспекция
ни начиналась, благодаря постоянной оптимизации и высокой эффективности
управления процессом инспекции она быстро становится эффективным спосо#
бом устранения дефектов и ошибок.
Специалисты Института разработки ПО (Software Enginee#
Дополнительные сведения О
ring Institute, SEI) создали модель зрелости процессов (Capa# разработанной в SEI концепции
bility Maturity Model, CMM), позволяющую оценить эффек# зрелости процесса разработки
тивность процесса разработки ПО (SEI, 1995). Процесс ин# см. работу «Managing the Soft-
спекции соответствует самому высокому уровню эффектив# ware Process» (Humphrey, 1989).
ности. Он является систематичным, повторяется и самооп#
тимизируется на основе обратной связи, поддающейся оценке. Эти идеи вы мо#
жете приспособить ко многим методикам, описываемым в данной книге. При рас#
пространении на всю компанию эти идеи позволят добиться максимально воз#
можного уровня качества и продуктивности.
Чтение кода
Чтение кода — альтернатива инспекции и анализу. Данная методика подразуме#
вает, что вы читаете исходный код в поисках ошибок, обращая при этом внима#
ние и на его качественные аспекты, такие как проект, стиль, удобочитаемость,
удобство сопровождения и эффективность.
Исследование, проведенное в Лаборатории проектирования ПО NASA,
показало, что при чтении кода разработчики находили около 3,3 дефек#
та в час, а при тестировании — около 1,8 ошибки в час (Card, 1987). Кроме
того, на всем протяжении проекта чтение кода позволяло найти на 20–60% боль#
ше ошибок, чем разные виды тестирования.
Как и анализ, чтение кода не имеет точного определения. Обычно при чтении кода
двое или более человек независимо изучают код, после чего обсуждают его вме#
сте с автором. Методика чтения кода описана ниже.
В начале подготовки к собранию автор кода раздает листинги участникам
обзора. Листинги включают от 1000 до 10 000 строк; типичный объем — 4000
строк.
Двое или более разработчиков читают код. Чтобы поддержать дух соревнова#
ния, привлекайте к чтению кода минимум двух человек. Если в чтении кода уча#
ствуют три человека или более, оценивайте вклад каждого из них, чтобы вы
знали, как дополнительные участники влияют на результаты.
Участники обзора читают код независимо друг от друга. Обычно продуктив#
ность составляет примерно 1000 строк в день.
После того как участники обзора завершили чтение кода, автор кода прово#
дит собрание. Собрание продолжается один#два часа и фокусируется на про#
блемах, обнаруженных при чтении кода. Никто не пытается анализировать код
строку за строкой. Строго говоря, собрание даже не является необходимостью.
Автор кода исправляет обнаруженные проблемы.
Различие между чтением кода и инспекцией и анализом в том, что чте#
ние кода в большей степени ориентировано на индивидуальный обзор
кода, а не на собрание. Это позволяет каждому участнику обзора уделять
больше времени непосредственно поиску проблем. Меньше времени тратится на
проведение собраний, предполагающих, что каждый участник активен только часть
времени, и требующих значительных усилий для координации действий группы.
Меньше времени тратится на отсрочку собрания до тех пор, пока каждый член
группы не найдет два свободных часа. Чтение кода особенно полезно, если учас#
тников обзора разделяют большие расстояния.
ГЛАВА 21 Совместное конструирование 487
Презентация
Презентацией (dog#and#pony show) называют обзор, при котором система демон#
стрируется заказчику. Такие обзоры — обычное дело при разработке ПО для пра#
вительственных организаций, которые часто требуют выполнения обзоров тре#
бований, проектов и кода. Цель презентации — показать заказчику, что работа
продвигается успешно, так что это обзор, выполняемый руководителями, а не
технический обзор.
Не рассматривайте презентации как способ повышения технического качества
продукции. Подготовка к ним может оказывать косвенное влияние на техниче#
ское качество, но обычно при этом больше времени тратится на подготовку эф#
фектных слайдов, а не на повышение качества ПО. Для повышения технического
качества ПО используйте инспекции, анализ или чтение кода.
Дополнительные ресурсы
Ниже я указал ряд работ, посвященных методикам совмес#
http://cc2e.com/2106 тного конструирования.
Парное программирование
Williams, Laurie and Robert Kessler. Pair Programming Illuminated. Boston, MA: Add#
ison Wesley, 2002. В этой книге подробно рассматриваются все аспекты парного
программирования, в том числе соответствие личностей (например, эксперт и
новичок, интроверт и экстраверт) и другие вопросы реализации.
Beck, Kent. Extreme Programming Explained: Embrace Change. Reading, MA: Addison
Wesley, 2000. Кент Бек вкратце рассматривает парное программирование и пока#
зывает, как его улучшить, дополнив другими методиками, такими как определение
стандартов кодирования, частая интеграция и регрессивное тестирование.
Reifer, Donald. «How to Get the Most Out of Extreme Programming/Agile Methods», Pro%
ceedings, XP/Agile Universe 2002. New York, NY: Springer; pp. 185–196. В этой статье
обобщен опыт использования экстремального программирования и методик гибкой
разработки, а также указаны условия успешности парного программирования.
ГЛАВА 21 Совместное конструирование 489
Инспекции
Wiegers, Karl. Peer Reviews in Software: A Practical Guide. Boston, MA: Addison Wesley,
2002. В этой книге подробно рассматриваются разные виды обзоров, в том числе
формальные инспекции и менее формальные методики. Она основана на тщатель#
ных исследованиях, имеет практическую направленность и легко читается.
Gilb, Tom and Dorothy Graham. Software Inspection. Wokingham, England: Addison#
Wesley, 1993. В этой книге подробно обсуждаются инспекции, выполнявшиеся в
начале 1990#х. Она имеет практическую направленность и включает исследова#
ния конкретных инспекционных программ, проводившихся в нескольких орга#
низациях.
Fagan, Michael E. «Design and Code Inspections to Reduce Errors in Program Develop#
ment». IBM Systems Journal, 15, no. 3 (1976): 182–211.
Fagan, Michael E. «Advances in Software Inspections». IEEE Transactions on Software
Engineering, SE#12, no. 7 (July 1986): 744–51. Две этих статьи написаны разработ#
чиком методики инспекций. В них вы найдете суть того, что нужно знать для
проведения инспекций, в том числе описание всех стандартных форм инспекций.
Соответствующие стандарты
IEEE Std 1028%1997 — стандарт обзоров ПО.
IEEE Std 730%2002 — стандарт планирования контроля качества ПО.
Ключевые моменты
Как правило, методики совместной разработки позволяют находить больше
дефектов, чем тестирование, и делать это более эффективно.
Методики совместной разработки и тестирование приводят к обнаружению
разных типов ошибок, поэтому программа контроля качества ПО должна вклю#
чать и обзоры, и тестирование.
Главными аспектами формальной инспекции, обеспечивающими максималь#
ную эффективность обнаружения дефектов, являются использование конт#
рольных списков, подготовка, назначение хорошо определенных ролей и по#
стоянное улучшение процесса. Формальная инспекция — более эффективный
способ обнаружения дефектов, чем анализ проекта или кода.
Парное программирование и инспекции примерно эквивалентны по показа#
телям расходов и качества итогового кода. Парное программирование может
оказаться особенно полезным, если вы хотите сократить срок разработки си#
стемы. Некоторые разработчики предпочитают работать в паре, а не самосто#
ятельно.
Формальные инспекции можно использовать для проверки не только кода, но
и других результатов труда, таких как требования, проекты и тесты.
Анализ и чтение кода — альтернативы инспекциям. Чтение кода позволяет
каждому участнику более эффективно использовать свое время.
490 ЧАСТЬ V Усовершенствование кода
Г Л А В А 2 2
Тестирование, выполняемое
разработчиками
Содержание
http://cc2e.com/2261
22.1. Тестирование, выполняемое разработчиками,
и качество ПО
22.2. Рекомендуемый подход к тестированию, выполня#
емому разработчиками
22.3. Приемы тестирования
22.4. Типичные ошибки
22.5. Инструменты тестирования
22.6. Оптимизация процесса тестирования
22.7. Протоколы тестирования
Связанные темы
Качество ПО: глава 20
Методики совместного конструирования: глава 21
Отладка: глава 23
Интеграция: глава 29
Предварительные условия конструирования: глава 3
Тестирование — самая популярная методика повышения качества, подкрепленная
многими исследованиями и богатым опытом разработки коммерческих прило#
жений. Существует множество видов тестирования: одни обычно выполняют сами
разработчики, а другие — специализированные группы. Виды тестирования пе#
речислены ниже.
Блочным тестированием называют тестирование полного класса, метода или
небольшого приложения, написанного одним программистом или группой,
выполняемое отдельно от прочих частей системы.
Тестирование компонента — это тестирование класса, пакета, небольшого при#
ложения или другого элемента системы, разработанного несколькими програм#
мистами или группами, выполняемое в изоляции от остальных частей системы.
ГЛАВА 22 Тестирование, выполняемое разработчиками 491
классы следует так, чтобы они казались черными ящиками: пользователь класса
должен обращаться к нему через интерфейс, не зная о деталях внутреннего уст#
ройства класса. Однако при тестировании класса с ним выгодно обращаться как
с прозрачным ящиком, анализируя внутренний исходный код, а также входные и
выходные данные класса. Зная, что происходит внутри ящика, вы можете проте#
стировать класс более тщательно. Конечно, при тестировании класса вы будете
рассматривать его код под тем же углом зрения, что и при написании, поэтому
тестирование методом черного ящика также имеет достоинства.
Во время конструирования вы обычно пишете метод или класс, проверяете его в
уме, после чего выполняете его обзор или тестирование. Какой бы ни была ваша
стратегия интеграционного тестирования или тестирования системы, вы долж#
ны тщательно тестировать каждый блок до его объединения с другими блоками.
Если вы пишете несколько методов, тестируйте их по одному за раз. На самом деле
так их тестировать не легче — зато гораздо легче отлаживать. Если вы объедини#
те несколько непротестированных методов и получите ошибку, вы не сможете
определить, какой из методов в ней повинен. Если же вы добавляете в набор про#
тестированных методов по одному методу за раз, при возникновении ошибки вы
будете знать, что она содержится в добавленном методе или обусловлена взаимо#
действием старых методов с новым. Это облегчит отладку.
Методики совместного конструирования имеют много достоинств, не характер#
ных для тестирования. Однако частично это объясняется тем, что тестирование
часто выполняют не так хорошо, как было бы можно. Разработчик может выпол#
нить сотни тестов и все же достигнуть лишь частичного покрытия кода тестами.
Чувство хорошего покрытия кода тестами не означает, что покрытие на самом
деле адекватно. Понимание базовых концепций тестирования может повысить его
эффективность.
Даже при таком относительно небольшом объеме входных данных вам пришлось
бы выполнить 1066 тестов. Если бы Ной, высадившись из ковчега, начал тестиро#
вать эту программу со скоростью триллион тестов в секунду, на текущий момент
он был бы далек от выполнения даже 1% тестов. Очевидно, что при вводе более
реалистичного объема данных исчерпывающее тестирование всех комбинаций
стало бы еще менее «осуществимым».
ГЛАВА 22 Тестирование, выполняемое разработчиками 497
Неполное тестирование
Если уж исчерпывающее тестирование невозможно, на прак#
Перекрестная ссылка Опреде-
тике искусство тестирования заключается в выборе тестов, лить, покрыт ли тестами весь
способных обеспечить максимальную вероятность обнару# код, позволяет монитор покры-
жения ошибок. В нашем случае из 1066 возможных тестов толь# тия (см. соответствующий под-
ко несколько скорее всего позволили бы найти ошибки, от# раздел раздела 22.5).
личающиеся от ошибок, обнаруживаемых другими тестами.
Вам нужно выбирать несколько тестов, позволяющих найти разные ошибки, а не
использовать множество тестов, раз за разом приводящих к одному результату.
Планируя тестирование, исключите тесты, которые не могут сообщить ничего
нового, например, тесты новых данных, похожих на уже протестированные дан#
ные. Существует ряд методов эффективного покрытия базисных элементов; не#
которые из них мы и обсудим.
>Statement1;
Statement2;
498 ЧАСТЬ V Усовершенствование кода
> if ( x < 10 ) {
Statement3;
}
Statement4;
12 companyRetirement = 0;
13
14 // Вычисление суммы вклада сотрудника в пенсионный фонд компании.
«4» — оператор if; «5» — операция &&.
В этом примере нам нужен один первоначальный тест и один тест для каждого
из пяти ключевых слов — всего шесть. Это не значит, что шестью любыми теста#
ми будут покрыты все базисные элементы. Это значит, что нужно минимум шесть
тестов. Если тесты не будут разработаны должным образом, они почти наверняка
не покроют все базисные элементы. Хитрость в том, что нужно обращать внима#
ние на те же ключевые слова, которые вы использовали при подсчете числа нуж#
ных тестов. Все эти ключевые слова представляют нечто, что может быть или
истинным, или ложным; убедитесь, что вы разработали хотя бы по одному тесту
для каждого истинного и каждого ложного условия.
Вот набор тестов, покрывающий все базисные элементы в этом примере:
500 ЧАСТЬ V Усовершенствование кода
При возрастании сложности метода число тестов, нужных только для покрытия
всех путей, быстро увеличивается. Более короткие методы обычно включают мень#
ше путей, которые нужно протестировать. Булевы выражения, не содержащие
большого числа операций И и ИЛИ, также имеют меньше вариантов, подлежащих
тестированию. Легкость тестирования — еще один хороший повод сокращать
методы и упрощать булевы выражения.
Итак, мы разработали для нашего метода шесть тестов, выполнив требования струк#
турированного базисного тестирования. Можно ли считать, что этот метод пол#
ностью протестирован? Наверное, нет. Этот тип тестирования гарантирует толь#
ко выполнение всего кода. Он не учитывает вариации данных.
затель, соответствующая ему область памяти может быть освобождена. Если это
индекс цикла for, после выполнения цикла он может остаться за пределами
текущей области видимости. Если это указатель на запись в файле, он может
стать недействительным в результате закрытия файла.
В дополнение к терминам «определение», «использование» и «уничтожение» удобно
иметь термины, описывающие выполнение какого#то действия над переменной
сразу после входа в метод или непосредственно перед выходом из него.
Вход — поток управления входит в метод, после чего над переменной сразу
выполняется какое#то действие. Пример — инициализация рабочей перемен#
ной в начале метода.
Выход — поток управления покидает метод сразу после выполнения опера#
ции над переменной. Пример? Присвоение возвращаемого значения перемен#
ной статуса в самом конце метода.
Для покрытия каждого пути выполнения этой программы нам нужен один тест,
при котором условие Condition 1 истинно, и один — при котором оно ложно, а
также аналогичные тесты для условия Condition 2. Эти ситуации можно охватить
двумя тестами: (Condition 1=True, Condition 2=True) и (Condition 1=False, Condition
2=False). Два этих теста — все, что нужно для выполнения требований структури#
рованного базисного тестирования, а также для тестирования всех определений
переменных; эти тесты автоматически обеспечивают слабую форму тестирования,
основанного на потоках данных.
Однако для покрытия всех комбинаций «определение — использование» этого мало.
На текущий момент у нас есть тесты тех случаев, когда условия Condition 1 и
Condition 2 истинны и когда оба они ложны:
ГЛАВА 22 Тестирование, выполняемое разработчиками 503
x = a
...
y = x + 1
и
x = b
...
y = x 1
Угадывание ошибок
Формальные методики тестирования хорошие программи#
Перекрестная ссылка Об эвристи-
сты дополняют менее формальными эвристическими мето#
ческих методиках см. раздел 2.2.
диками. Одна из них — угадывание ошибок (error guessing).
Термин «угадывание ошибок» — довольно примитивное название вполне разум#
ной идеи, подразумевающей создание тестов на основе обдуманных предположе#
ний о вероятных источниках ошибок.
Предположения можно выдвигать, опираясь на интуицию или накопленный опыт.
Так, в главе 21 в числе прочих достоинств инспекций были указаны создание и
обновление списка частых ошибок, используемого при проверке нового кода.
Поддерживая списки ранее допущенных ошибок, вы повысите эффективность
своих догадок.
Ниже рассматриваются конкретные виды ошибок, угадать которые легче всего.
Как видите, в этой ситуации мы имеем три граничных случая: максимальное зна#
чение, которое меньше max, само значение max и минимальное значение, пре#
вышающее max. Для исключения распространенных ошибок нужны три теста.
Фрагмент кода, для которого мы подсчитывали число тестов, содержит проверку
m_employee[ ID ].governmentRetirementWithheld < MAX_GOVT_RETIREMENT. Согласно
принципам анализа граничных условий следует изучить три случая:
ГЛАВА 22 Тестирование, выполняемое разработчиками 505
Классификация ошибок
Некоторые ученые попытались классифицировать ошибки
Перекрестная ссылка Список
по типу и определить распространенность ошибок каждо# всех контрольных списков при-
го типа. У каждого программиста есть свой список наибо# веден после содержания книги.
лее неприятных ошибок: ошибки занижения или завыше#
ния на 1, невыполнение повторной инициализации переменной цикла и т. д.
В контрольных списках я указал и многие другие типы ошибок.
Борис Бейзер объединил данные нескольких исследований и пришел к исключи#
тельно подробной классификации ошибок по распространенности (Beizer, 1990).
Вот резюме его результатов:
25,18% Структурные ошибки
22,44% Ошибки в данных
16,19% Ошибки в реализации функциональности
9,88% Ошибки конструирования
8,98% Ошибки интеграции
8,12% Ошибки в функциональных требованиях
2,76% Ошибки в определении или выполнении тестов
1,74% Системные ошибки, ошибки в архитектуре ПО
4,71% Другие ошибки
вые данные. Эврика! Ошибка в тестовых данных! Как глупо тратить часы на по#
иск ошибки, если она кроется в тестовых данных, а не в коде!
Это довольно распространенная ситуация. Зачастую сами тесты содер#
жат не меньше, а то и больше ошибок, чем тестируемый код (Weiland, 1983;
Jones, 1986a; Johnson, 1994). Объяснить это легко, особенно если разра#
ботчики сами пишут тесты. Тесты обычно создают на ходу, не уделяя должного
внимания проектированию и конструированию. Их часто считают одноразовы#
ми и разрабатывают с соответствующей тщательностью.
Ниже дано несколько советов по снижению числа ошибок в тестах.
Проверяйте свою работу Разрабатывайте тесты так же тщательно, как и код,
и внимательно проверяйте их. Анализируйте код каждого теста строка за стро#
кой при помощи отладчика, как вы проверяли бы код готового приложения. Про#
водите анализ и инспекцию тестовых данных.
Планируйте тестирование программы так же, как и ее разработку
Планирование тестирования следует начинать на этапе выработки требований или
сразу после получения задания. Это поможет избавиться от тестов, основанных
на неверных предположениях.
Храните тесты Посвятите немного времени проверке качества тестов. Сохра#
ните их для регрессивного тестирования и для работы над будущими версиями
программы. Связанные с этим проблемы легко оправдать, если вы знаете, что
собираетесь сохранить тесты, а не выбросить.
Встраивайте блочные тесты в среду тестирования Пишите сначала код
блочных тестов, но затем интегрируйте их в системную среду тестирования (та#
кую как JUnit). Использование интегрированной среды тестирования предотвра#
щает только что упомянутую тенденцию выбрасывать тесты.
будут в итоговой программе работать сами по себе. Метод main() может читать
параметры командной строки и передавать их в тестируемый метод, позволяя
проверить его до интеграции с остальной частью программы. Выполняя инте#
грацию, оставьте методы и предназначенные для их тестирования леса в файле,
и заблокируйте леса при помощи директив препроцессора или комментариев. В
итоге код лесов будет проигнорирован при компиляции, а если вы разместите его
в конце файла, он и на глаза не будет попадаться. Оставленный в файле, он не
причинит никакого вреда. Вам не придется тратить время на его удаление и ар#
хивирование, а в случае чего он всегда будет под рукой.
ровав и дешифровав около 100 000 файлов без каких бы то ни было ошибок.
Учитывая то, что я протестировал файлы и пароли самого разного объема и со#
держания, я мог быть уверен, что с программой все в порядке.
Из этой истории можно извлечь ряд уроков, в том числе следующие.
Правильно спроектированные генераторы случайных данных способны гене#
рировать комбинации тестовых данных, о которых вы могли бы не подумать.
Генераторы случайных данных могут протестировать программу тщательнее,
чем вы сами.
Со временем вы можете улучшить случайные тесты, обратив повышенное вни#
мание на реалистичный диапазон входных значений. Это позволяет сконцен#
трировать тестирование на тех областях, которые будут использоваться чаще
всего, и максимально повысить их надежность.
Модульное проектирование сполна окупается во время тестирования. Я смог
отделить код шифрования и дешифрования и задействовать его независимо
от кода пользовательского интерфейса, что упростило задачу написания тес#
тового драйвера.
Вы можете повторно использовать тестовый драйвер даже после изменения
тестируемого им кода. Как только я исправил две ошибки, я смог сразу же на#
чать повторное тестирование.
Регистраторы данных
Некоторые инструменты могут следить за программой и
http://cc2e.com/2282
собирать информацию о ее состоянии в случае краха по#
добно «черному ящику», устанавливаемому в самолетах для
определения причин крушения. Эффективная регистрация данных облегчает ди#
агностику ошибок и сопровождение программы после ее выпуска.
Вы можете создать собственный регистратор данных, записывающий сведения о
важных событиях в файл. Записывайте в файл состояние системы до ошибки и
подробные сведения об условиях возникновения ошибки. Можете использовать
эту функциональность в предварительных версиях программ и блокировать в
окончательных версиях. Если вы используете для хранения зарегистрированных
данных самоочищающееся хранилище и тщательно продумали размещение и
содержание сообщений об ошибках, можете оставлять функции регистрации дан#
ных и в итоговых версиях программ.
ГЛАВА 22 Тестирование, выполняемое разработчиками 517
Символические отладчики
Символический отладчик — это технологическое дополне#
Перекрестная ссылка Наличие
ние анализа и инспекций кода. Отладчик может выполнять отладчиков зависит от зрелос-
код строка за строкой, позволяет следить за значениями ти технологической среды (см.
переменных и всегда интерпретирует код так же, как ком# раздел 4.3).
пьютер. Возможность пошагового выполнения кода и наблю#
дения за его работой трудно переоценить.
Анализ кода при помощи отладчика во многом напоминает процесс обзора ва#
шего кода другими программистами. Ни ваши коллеги, ни отладчик не имеют тех
же слабых сторон, которые имеете вы. Дополнительное преимущество отладчика
в том, что анализ кода с его помощью менее трудоемок, чем групповой обзор.
Наблюдение за выполнением кода при разных комбинациях входных данных
позволяет убедиться в том, что вы написали то, что хотели.
Кроме того, использование хорошего отладчика является эффективным способом
изучения языка, поскольку вы можете получить точную информацию о выполне#
нии кода. Переключаясь между высокоуровневым языком и ассемблером, вы мо#
жете изучить соответствие высокоуровневых и низкоуровневых конструкций.
Наблюдение за регистрами и стеком позволяет лучше разобраться в передаче
аргументов в методы. Вы можете увидеть, как компилятор оптимизирует код. Ни#
какое из этих преимуществ не имеет прямого отношения к главной роли отлад#
чика — диагностике уже обнаруженных ошибок, но при творческом подходе из
отладчика можно извлечь гораздо большую выгоду.
Планирование тестирования
Эффективность тестирования заметно повышается, если его
Перекрестная ссылка Одним из
элементов планирования тести- спланировать в самом начале работы над проектом. При#
рования является формализа- своение тестированию той же степени важности, что и про#
ция планов в письменной фор- ектированию с кодированием, подразумевает, что на тес#
ме. О документировании тести- тирование будет выделено время, что оно будет считаться
рования см. работы, указанных
не менее важным и что процесс тестирования будет высо#
в разделе «Дополнительные
ресурсы» главы 32. кокачественным. Планирование тестирования нужно и для
обеспечения повторяемости процесса тестирования. Если
процесс нельзя повторить, его нельзя улучшить.
Автоматизированное тестирование
Единственный практичный способ управления регрессивным тестиро#
ванием — его автоматизация. Многократное выполнение одних и тех же
тестов, приводящих к тем же результатам, вводит людей в транс. Они
утрачивают концентрацию и начинают упускать ошибки, что сводит на нет эф#
фективность регрессивного тестирования. Гуру тестирования Борис Бейзер сооб#
щает, что уровень ошибок, допускаемых при тестировании вручную, сравним с
уровнем ошибок в самом тестируемом коде. По его оценкам, при тестировании,
проводимом вручную, должным образом выполняется лишь около половины всех
тестов (Johnson, 1994).
Преимущества автоматизированного тестирования таковы.
При автоматизированном тестировании вероятность допустить ошибку ниже,
чем при тестировании вручную.
Автоматизировав тестирование, вы сможете легко адаптировать его к другим
компонентам системы.
Автоматизация тестирования позволяет выполнять тесты гораздо чаще. Авто#
матизация тестирования — один из базовых элементов интенсивных методик
тестирования, таких как ежедневная сборка программы, дымовое тестирова#
ние и экстремальное программирование.
Автоматизированное тестирование способствует максимально раннему обна#
ружению проблем, что обычно минимизирует объем работы, нужной для ди#
агностики и исправления проблемы.
Способствуя более быстрому обнаружению дефектов, внесенных в код при его
изменении, автоматизированное тестирование снижает вероятность того, что
вам придется позднее заняться крупномасштабным исправлением кода.
Автоматизированное тестирование особенно полезно в
Перекрестная ссылка О связи
новых изменчивых технологических средах, поскольку между зрелостью технологий и
оно позволяет быстрее узнавать об изменениях среды. методами разработки см. раз-
Многие инструменты, используемые для автоматизации дел 4.3.
тестирования, позволяют создавать тестовые леса, генери#
ровать входные и регистрировать выходные данные и сравнивать действитель#
ные выходные данные с ожидаемыми. Для выполнения этих функций можно ис#
пользовать инструменты, обсуждавшиеся в предыдущем разделе.
520 ЧАСТЬ V Усовершенствование кода
Дополнительные ресурсы
Федеральные законы об объективности информации застав#
ляют меня признаться в том, что в нескольких других кни# http://cc2e.com/2203
гах тестирование рассматривается подробнее, чем в этой
главе. Например, в них можно найти материалы о тестировании системы и тес#
тировании методом «черного ящика», которых мы не касались. Кроме того, в этих
книгах более глубоко освещаются вопросы тестирования, выполняемого разра#
ботчиками. В них обсуждаются формальные подходы к тестированию (такие как
создание причинно#следственных диаграмм), а также детали создания независи#
мой организации, занимающейся тестированием.
Тестирование
Kaner, Cem, Jack Falk, and Hung Q. Nguyen. Testing Computer Software, 2d ed. New York,
NY: John Wiley & Sons, 1999. Наверное, это лучшая книга по тестированию ПО.
Приведенная в ней информация касается в первую очередь тестирования программ,
которые будут использоваться большим числом людей (например, крупных Web#
сайтов и приложений, продаваемых в магазинах), но полезна и в общем.
Kaner, Cem, James Bach, and Bret Pettichord. Lessons Learned in Software Testing. New
York, NY: John Wiley & Sons, 2002. Эта книга хорошо дополняет предыдущую. Она
разделена на 11 глав, включающих 250 уроков, изученных самими авторами.
Tamre, Louise. Introducing Software Testing. Boston, MA: Addison#Wesley, 2002. Эта
несложная книга ориентирована на разработчиков, которым нужно понять тес#
тирование. Несмотря на название («Введение в тестирование ПО»), некоторые из
приведенных в книге сведений будут полезны и опытным тестировщикам.
Whittaker, James A. How to Break Software: A Practical Guide to Testing. Boston, MA:
Addison#Wesley, 2002. В этой книге описаны 23 вида атак, которые тестировщики
могут использовать для нарушения работы ПО, и приведены примеры каждой атаки
с применением популярных программных пакетов. Из#за оригинального подхо#
да эту книгу можно рассматривать и как основной источник информации о тес#
тировании, и как дополнение других книг.
Whittaker, James A. «What Is Software Testing? And Why Is It So Hard?» IEEE Software,
January 2000, pp. 70–79. В этой статье можно найти хорошее введение в вопросы
тестирования ПО и объяснение некоторых проблем, связанных с эффективным
тестированием.
Myers, Glenford J. The Art of Software Testing. New York, NY: John Wiley, 1979. Эта клас#
сическая книга по тестированию ПО издается до сих пор (хотя и стоит немало).
Ее содержание довольно простое: тестирование, основанное на самооценке; пси#
хология и экономика тестирования программ; анализ и обзоры программ; про#
ектирование тестов; тестирование классов; тестирование более высокого поряд#
ка; отладка; инструменты тестирования и другие методики. Книга лаконична (177
страниц) и легко читается. Приведенный в ее начале тест поможет вам начать
думать, как тестировщик, и продемонстрирует все разнообразие способов нару#
шения работы кода.
522 ЧАСТЬ V Усовершенствование кода
Тестовые леса
Bentley, Jon. «A Small Matter of Programming» in Programming Pearls, 2d ed. Boston, MA:
Addison#Wesley, 2000. В этом эссе приведены хорошие примеры тестовых лесов.
Mackinnon, Tim, Steve Freeman, and Philip Craig. «Endo#Testing: Unit Testing with Mock
Objects», eXtreme Programming and Flexible Processes Software Engineering % XP2000
Conference, 2000. Первая работа, посвященная использованию поддельных объек#
тов при тестировании, выполняемом разработчиками.
Thomas, Dave and Andy Hunt. «Mock Objects» IEEE Software, May/June 2002. Эта ста#
тья представляет собой удобочитаемое введение в использование поддельных
объектов.
www.junit.org. Это сайт поддержки разработчиков, использу#
http://cc2e.com/2217 ющих среду JUnit. Аналогичные сайты см. по адресам cpp%
unit.sourceforge.net и nunit.sourceforge.net.
Соответствующие стандарты
IEEE Std 1008%1987 (R1993) — стандарт блочного тестирования ПО.
IEEE Std 829%1998 — стандарт документирования тестов ПО.
IEEE Std 730%2002 — стандарт планирования контроля качества ПО.
Ключевые моменты
Тестирование, выполняемое разработчиками, — один из важнейших элемен#
тов полной стратегии тестирования. Независимое тестирование не менее важно,
но оно не является предметом этой книги.
Написание тестов до написания кода требует примерно того же времени и тех
же усилий, что и создание тестов после кода, но сокращает циклы регистра#
ции — отладки — исправления дефектов.
Даже если учесть, что тестирование имеет массу разновидностей, его все рав#
но следует считать лишь одним из элементов хорошей программы контроля
качества. Высококачественные методики разработки, позволяющие свести к
минимуму число дефектов в требованиях и проектах, играют не менее, а то и
более важную роль. Методики совместной разработки характеризуются не
меньшей эффективностью обнаружения ошибок, чем тестирование, к тому же
они позволяют находить другие ошибки.
Опираясь на базисное тестирование, анализ потоков данных, анализ гранич#
ных условий, классы плохих и классы хороших данных, вы можете создать много
тестов детерминированным образом. Методика угадывания ошибок укажет вам
на некоторые дополнительные тесты.
Обычно ошибки концентрируются в нескольких наиболее дефектных классах
и методах. Найдите такой код, перепроектируйте его и перепишите.
Для тестовых данных обычно характерна более высокая плотность ошибок, чем
для тестируемого кода. Так как поиск ошибок в тестовых данных требует вре#
мени, не приводя к какому бы то ни было улучшению кода, эти ошибки более
досадны, чем ошибки программирования. Избегайте их, разрабатывая тесты
столь же тщательно, что и код.
Автоматизация тестирования полезна вообще и практически необходима в
случае регрессивного тестирования.
Чтобы процесс тестирования был максимально эффективным, сделайте его
регулярным, выполняйте его оценку и используйте получаемую информацию
для его улучшения.
524 ЧАСТЬ V Усовершенствование кода
Г Л А В А 2 3
Отладка
Содержание
http://cc2e.com/2361
23.1. Общие вопросы отладки
23.2. Поиск дефекта
23.3. Устранение дефекта
23.4. Психологические аспекты отладки
23.5. Инструменты отладки — очевидные и не очень
Связанные темы
Качество ПО: глава 20
Тестирование, выполняемое разработчиками: глава 22
Рефакторинг: глава 24
Отладка — это процесс определения и устранения причин
Отлаживать код вдвое сложнее,
чем писать. Поэтому, если при ошибок. Этим она отличается от тестирования, направлен#
написании программы вы ис- ного на обнаружение ошибок. В некоторых проектах отладка
пользуете весь свой интеллект, занимает до 50% общего времени разработки. Многие про#
вы по определению недостаточно граммисты считают отладку самым трудным аспектом про#
умны, чтобы ее отладить.
граммирования.
Брайан Керниган
Но отладка не обязана быть таковой. Если вы будете следо#
(Brian W. Kernighan)
вать советам, приведенным в этой книге, вам придется отла#
живать меньше ошибок. Большинство дефектов будет тривиальными недосмотра#
ми и опечатками, которые вы сможете легко находить, читая исходный код или вы#
полняя его в отладчике. Что до остальных, более сложных ошибок, то, прочитав эту
главу, вы сможете сделать их отладку гораздо более легкой.
Отладка и качество ПО
Как и тестирование, сама по себе отладка не служит для повышения качества ПО
— это способ диагностики дефектов. О качестве нужно заботиться с самого нача#
ла работы над программой. Лучший способ создания качественной программы
заключается в тщательной выработке требований, грамотном проектировании и
применении высококачественных методик кодирования. Отладка — это средство
для крайних случаев
Неэффективный подход
К сожалению, в вузах почти никогда не рассматриваются принципы отладки.
Возможно, вы прослушали пару специальных лекций, но скорее всего на этом все
и кончилось. Я никак не могу пожаловаться на качество полученного образова#
ния, но и в моем случае рассмотрение отладки ограничилось советом «для нахож#
дения дефектов включайте в код команды печати». Это неадекватный уровень. Если
528 ЧАСТЬ V Усовершенствование кода
Суеверная отладка
Сатана выделил часть ада программистам, которые отлаживают программы, опи#
раясь на суеверия. В каждой группе есть хоть один программист, бесконечно сра#
жающийся с демоническими компьютерами, таинственными дефектами компи#
лятора, скрытыми дефектами языка, которые проявляются только в полнолуние,
плохими данными, утратой важных изменений, заколдованным текстовым редак#
тором, который неправильно сохраняет программы… дополните этот список сами.
Это и есть «суеверное программирование».
Если написанная вами программа не работает, ошибка лежит на вашей совести.
Компьютер и компилятор ни в чем не виноваты. Программа не делает каждый раз
что#то иное. Она не писала сама себя — ее написали вы, так что не уходите от
ответственности.
ГЛАВА 23 Отладка 529
1. Стабилизация ошибки.
2. Определение источника ошибки.
a. Сбор данных, приводящих к дефекту.
b. Анализ собранных данных и формулирование гипотезы, объясняющей дефект.
c. Определение способа подтверждения или опровержения гипотезы, осно#
ванного или на тестировании программы, или на изучении кода.
530 ЧАСТЬ V Усовершенствование кода
Стабилизация ошибки
Если дефект проявляется не всегда, его почти невозможно диагностировать. Пе#
ревод несистематического дефекта в разряд предсказуемых — один из самых слож#
ных аспектов отладки.
Непредсказуемые ошибки обычно связаны с инициализаци#
Перекрестная ссылка О безо-
ей, расчетом времени или зависшими указателями. Если
пасном использовании указате-
лей см. раздел 13.2. сумма иногда вычисляется правильно, а иногда неправиль#
но, это, вероятно, объясняется тем, что одна из переменных
не инициализируется и просто получает в большинстве случаев нулевое значе#
ние. Если вы столкнулись со странной непредсказуемой проблемой при работе с
указателями, почти наверняка ее причина — неинициализированный указатель
или использование указателя после освобождения соответствующей ему области
памяти.
Стабилизация ошибки обычно не ограничивается нахождением теста, приводя#
щего к ошибке. Она предполагает упрощение теста до тех пор, пока не будет по#
лучен самый простой тест, все еще приводящий к ошибке. Иначе говоря, тест
следует сделать таким простым, чтобы изменение любого его аспекта изменяло
ГЛАВА 23 Отладка 531
Итак, можно предположить, что проблема как#то связана с вводом одной новой
записи. Если это так, то при следующем запуске программы запись Fruit%Loop, Frita
должна занять правильное место. Ну#ка, проверим:
Синтаксические ошибки
Синтаксические ошибки ждет судьба мамонтов и саблезубых тигров. Диагности#
ческие модули компиляторов постоянно улучшаются, и времена, когда поиск не#
правильно расположенной точки с запятой иногда занимал несколько часов, по#
чти ушли. Соблюдение следующих принципов поможет вам ускорить вымирание
подобных ошибок.
Не полагайтесь на номера строк в сообщениях компилятора Если ком#
пилятор сообщил о загадочной синтаксической ошибке, изучите фрагменты, рас#
положенные прямо перед ошибкой и сразу после нее: возможно, компилятор
неправильно понял проблему или просто включает плохой диагностический
модуль. Обнаружив истинный дефект, попробуйте определить, почему компиля#
тор указал не на ту команду. Понимание особенностей компилятора поможет
находить дефекты в будущем.
Не доверяйте сообщениям компилятора Компиляторы пытаются сообщить
вам точную причину ошибки, но они сами нередко ошибаются, и, чтобы понять
смысл их сообщений, иногда приходится читать между строк. Так, при целочис#
ленном делении на 0 компилятор UNIX C может вывести сообщение «floating
exception» (исключение при выполнении операции над числом с плавающей точ#
кой). Используя Standard Template Library C++, можно получить пару сообщений
об ошибке: первое — о действительной ошибке в использовании STL, а второе —
«Error message too long for printer to print; message truncated» («Сообщение об ошибке
сокращено, так как оно слишком велико для печати»). Наверное, вы и сами може#
те привести массу примеров неверных сообщений об ошибках.
Не доверяйте второму сообщению компилятора Одни компиляторы нахо#
дят множественные ошибки лучше, а другие хуже. Обнаружив первую ошибку,
некоторые компиляторы приходят в такое возбуждение, что выдают десятки бес#
смысленных сообщений о других ошибках. Другим компиляторам, более рассу#
дительным, тоже нравится находить ошибки, но они воздерживаются от вывода
неверных сообщений. Если ваш компилятор сгенерировал ряд сообщений об
ошибках и вы не можете быстро найти причину второго или третьего сообще#
ния, не волнуйтесь — исправьте первую ошибку и перекомпилируйте программу.
Разделяй и властвуй Разделение программы на части особенно эффективно при
поиске синтаксических ошибок. Если вы столкнулись с неуловимой синтаксичес#
кой ошибкой, удалите часть кода и перекомпилируйте программу. Ошибка или ис#
чезнет (следовательно, она содержится в удаленном коде), или снова появится во
всей своей красе (в этом случае удалите другую часть кода). Кроме того, вы можете
получить другое сообщение об ошибке (это значит, что вы перехитрили компиля#
тор и заставили его сгенерировать более разумное сообщение).
Грамотно ищите неверно размещенные комментарии
Перекрестная ссылка Наличие
редакторов с проверкой синтак- и кавычки Многие текстовые редакторы для программи#
сиса зависит от зрелости сре- рования автоматически форматируют комментарии, стро#
ды программирования (см. раз- ковые литералы и другие синтаксические элементы. В бо#
дел 4.3). лее примитивных средах неправильное размещение симво#
лов комментария или кавычек может запутать компилятор.
ГЛАВА 23 Отладка 539
Для нахождения лишних символов комментария или кавычек в коде C, C++ или
Java вставьте в него последовательность:
/*”/**/
Этот фрагмент завершит комментарий или строку, что поможет сузить область, в
которой скрываются лишние символы.
«Исправление».
> if ( client == 45 ) {
sum[ 45 ] = sum[ 45 ] + 3.45;
}
Теперь допустим, что при нулевом числе исков (number of claims) со стороны
клиента 37 вы не получаете 0. Плохое решение этой проблемы могло бы быть таким:
if ( client == 45 ) {
sum[ 45 ] = sum[ 45 ] + 3.45;
}
Второе «исправление».
> else if ( ( client == 37 ) && ( numClaims[ client ] == 0 ) ) {
sum[ 37 ] = 0.0;
}
Если уж и это не заставляет вас содрогнуться от ужаса, вы зря читаете мою книгу:
ничто в ней вас не тронет. Перечислить все недостатки этого подхода в книге,
объемом лишь около 1000 страниц, невозможно, поэтому ниже я указал только
три главных.
В большинстве случаев эти исправления работать не будут. Проблемы в этом
примере очень похожи на дефекты инициализации. Дефекты инициализации
по определению непредсказуемы, поэтому тот факт, что сумма для клиента 45
отличается сегодня от верного значения на 3,45 доллара, ничего не говорит о
том, что будет завтра. Завтра она может отличаться на 10 000,02 доллара, а может
быть верной. Таковы ошибки инициализации.
Такой код трудно сопровождать. Если для исправления ошибок создаются ча#
стные случаи, они становятся самой заметной особенностью кода. Значение
3,45 доллара не всегда будет таким, и позднее возникнет другая ошибка. В код
придется включить новый частный случай, а частный случай для 3,45 доллара
удален не будет. К коду будут прилипать все новые частные случаи. В конце
концов эти «прилипалы» станут слишком тяжелыми для кода, и он пойдет на
дно, где ему самое место.
542 ЧАСТЬ V Усовершенствование кода
Этот казус уже стал классическим: люди часто замечают только один артикль «the».
Они видят то, что ожидают увидеть. Ниже описаны другие похожие факты.
Студенты, изучающие циклы while, часто считают, что условие цикла оцени#
вается непрерывно, т. е. ожидают, что цикл завершится сразу, как только усло#
вие станет ложным, а не после проверки условия (Curtis et al., 1986). Они ду#
мают, что цикл while должен соответствовать слову «пока» в естественном языке.
Программист, который неумышленно использовал и переменную
SYSTSTS, и переменную SYSSTSTS, думал, что имеет дело с одной перемен
ной. Он не обнаружил проблему, пока программа не была выполнена
сотни раз и не была написана книга, содержащая ошибочные результаты (Wein#
berg, 1998).
Программисты, сталкивающиеся с кодом:
if ( x < y )
swap = x;
x = y;
y = swap;
if ( x < y ) {
swap = x;
x = y;
y = swap;
}
«Психологическая дистанция»
Психологическую дистанцию можно определить как лег#
Перекрестная ссылка Советы по
кость различения двух элементов. Если дать кому#нибудь
выбору ясных имен переменных
см. в разделе 11.7. длинный список слов и сказать, что все они имеют отно#
шение к уткам, человек может с легкостью перепутать сло#
ва «Queck» и «Quack», потому что они кажутся похожими. Психологическая дис#
танция между ними мала. Гораздо труднее перепутать «Tuack» и «Quack», хотя они
тоже различаются только одной буквой. Слово «Tuack» похоже на «Quack» мень#
ше, чем «Queck», потому что первая буква слова сильнее бросается в глаза, чем буква
в середине.
Вот примеры психологических дистанций между именами переменных (табл. 23#1):
ГЛАВА 23 Отладка 545
Утилиты сравнения исходного кода (такие как Diff) полезны при исправлении
ошибок. Если после внесения нескольких изменений некоторые из них нужно
отменить, но вы плохо помните, что именно вы изменяли, утилита сравнения
исходного кода освежит вашу память, указав на различия кода. Если вы обнару#
жили в новой версии кода дефект, которого не было в предыдущей версии, про#
сто сравните соответствующие файлы.
функции в том, что она повышает важность предупреждений. Как перевод часов
на пять минут вперед заставляет думать, что сейчас на пять минут больше, чем на
самом деле, так и указание компилятору считать предупреждения ошибками зас#
тавляет воспринимать их более серьезно. Кроме того, эта функция часто влияет
на процесс компиляции программы. При компиляции и компоновке программы
предупреждения в отличие от ошибок обычно не предотвращают компоновку. Если
вы хотите проверить предупреждения перед компоновкой, прикажите компиля#
тору рассматривать предупреждения как ошибки.
Стандартизуйте параметры компиляции в масштабе всего проекта
Разработайте стандарт, требующий, чтобы все участники проекта компилирова#
ли код, используя одинаковые параметры. Иначе при интеграции кода, скомпи#
лированного с использованием разных параметров, вы утонете в сообщениях об
ошибках и сами узнаете, что такое интеграционный кошмар. Стандарт легко на#
вязать, создав один общий make#файл или сценарий сборки программы.
Отладчики
Коммерческие отладчики непрерывно совершенствуются и могут значительно
изменить ваш способ программирования. Хорошие отладчики позволяют преры#
вать выполнение программы на конкретной строке, при достижении конкретной
строки в n#й раз, при изменении глобальной переменной или при присвоении
переменной конкретного значения. Они позволяют выполнять код строка за стро#
кой с «перешагиванием» через методы или с их «посещением», возвращаться к
началу дефектного фрагмента, а также регистрировать выполнение отдельных
операторов в журнале, что похоже на разбрасывание по всей программе команд
печати «Я здесь!».
Хорошие отладчики позволяют полностью исследовать данные, в том числе струк#
турированные и динамически выделенные. С их помощью легко просмотреть со#
держание связного списка указателей или динамически выделенного массива. Они
поддерживают типы данных, определенные пользователем. Они позволяют выпол#
нить нерегламентированный запрос данных, присвоить им новые значения и про#
должить выполнение программы.
Вы можете работать с высокоуровневым языком или ассемблерным кодом, сге#
нерированным компилятором. Если вы используете несколько языков, отладчик
автоматически выводит любой фрагмент кода на соответствующем языке. Вы
можете изучить цепочку вызовов методов и быстро увидеть исходный код любо#
го метода. В среде отладчика вы можете изменять параметры программы.
Лучшие отладчики запоминают параметры отладки (точки прерывания, отслежи#
ваемые переменные и т. д.) каждой отдельной программы, чтобы их не нужно было
задавать заново.
Системные отладчики работают на системном, а не прикладном уровне, не влияя
на выполнение отлаживаемой программы. Это важно, если вы отлаживаете про#
грамму, чувствительную ко времени выполнения или объему доступной памяти.
Если учесть все достоинства отладчиков, может показаться
Интерактивный отладчик — ве-
странным, что кто#то их критикует. И все же некоторые из ликолепный пример инструмен-
самых уважаемых людей в мире компьютерных наук реко# та, который не нужен: он поощ-
мендуют их не использовать. Они советуют полагаться на ряет хакерство методом проб и
свой ум и избегать инструментов отладки вообще. Они ошибок, а не систематичное
проектирование и позволяет
утверждают, что инструменты отладки — это костыли и что
непрофессионалам скрыть свою
вы решите проблемы быстрее и лучше, размышляя о них, а некомпетентность.
не опираясь на инструменты. Они утверждают, что при по# Харлан Миллз
иске дефектов программу нужно выполнять в уме, а не в (Harlan Mills)
отладчике.
Какими бы ни были эмпирические данные, суть нападок на отладчики некоррек#
тна. Если инструмент допускает неграмотное применение, это не значит, что от
него надо отказаться. Было бы глупо запретить аспирин из#за возможности пере#
дозировки. Риск пораниться не заставил бы вас прекратить подстригать лужайку
перед домом. Любой эффективный инструмент можно использовать правильно
и неправильно. Отладчик — не исключение.
548 ЧАСТЬ V Усовершенствование кода
Дополнительные ресурсы
Ниже я привел список книг, посвященных отладке.
http://cc2e.com/2375
Agans, David J. Debugging: The Nine Indispensable Rules for
Finding Even the Most Elusive Software and Hardware Problems.
Amacom, 2003. В этой книге рассматриваются общие принципы отладки, не зави#
сящие ни от языка, ни от среды.
Myers, Glenford J. The Art of Software Testing. New York, NY: John Wiley & Sons, 1979.
Седьмая глава этой классической книги посвящена отладке.
Allen, Eric. Bug Patterns In Java. Berkeley, CA: Apress, 2002. В данной книге описывает#
ся методика отладки программ Java, концептуально очень похожая на описанную
в этой главе. Как и здесь, в ней рассмотрен «Научный метод отладки», проведено
различие между отладкой и тестированием и определены частые ошибки.
Названия двух следующих книг могут навести на мысль о том, что они адресова#
ны только разработчикам программ для Microsoft Windows и .NET, однако это не
так: помимо всего прочего, в них вы найдете обсуждение отладки в целом, сове#
ты по использованию утверждений, а также описания методик кодирования, по#
могающих предотвращать ошибки.
Robbins, John. Debugging Applications for Microsoft .NET and Microsoft Windows. Red#
mond, WA: Microsoft Press, 20031 .
McKay, Everett N. and Mike Woodring. Debugging Windows Programs: Strategies, Tools,
and Techniques for Visual C++ Programmers. Boston, MA: Addison#Wesley, 2000.
1
Роббинс Дж. Отладка приложений для Microsoft .NET и Microsoft Windows. — М.: Русская Редак#
ция, 2004. — Прим. перев.
550 ЧАСТЬ V Усовершенствование кода
Ключевые моменты
Отладка — это тот этап разработки программы, от которого зависит возмож#
ность ее выпуска. Конечно, лучше всего вообще избегать ошибок, используя другие
методики, описанные в этой книге. Однако потратить время на улучшение на#
выков отладки все же стоит, потому что эффективность отладки, выполняемой
лучшими и худшими программистами, различается минимум в 10 раз.
Систематичный подход к поиску и исправлению ошибок — непременное ус#
ловие успешности отладки. Организуйте отладку так, чтобы каждый тест при#
ближал вас к цели. Используйте Научный Метод Отладки.
Прежде чем приступать к исправлению программы, поймите суть проблемы.
Случайные предположения о причинах ошибок и случайные исправления толь#
ко ухудшат программу.
Установите в настройках компилятора самый строгий уровень диагностики и
устраняйте причины всех ошибок и предупреждений. Как вы исправите неуло#
вимые ошибки, если будете игнорировать явные?
Инструменты отладки значительно облегчают разработку ПО. Найдите их и
используйте, но не забывайте, что у вас есть еще и голова.
ГЛАВА 23 Отладка 551
Г Л А В А 2 4
Рефакторинг
Содержание
http://cc2e.com/2436
24.1. Виды эволюции ПО
24.2. Введение в рефакторинг
24.3. Отдельные виды рефакторинга
24.4. Безопасный рефакторинг
24.5. Стратегии рефакторинга
Связанные темы
Советы по устранению дефектов: раздел 23.3
Подход к оптимизации кода: раздел 25.6
Проектирование при конструировании: глава 5
Классы: глава 6
Высококачественные методы: глава 7
Совместное конструирование: глава 21
Тестирование, выполняемое разработчиками: глава 22
Области вероятных изменений: подраздел «Определите области вероятных
изменений» раздела 5.3
Миф: в начале реализации программного проекта проводит#
Все удачные программы изме-
ся методичная выработка требований, и составляется устой# няются.
чивый список аспектов ответственности программы. Про# Фред Брукс (Fred Brooks)
ектирование соответствует требованиям и выполняется со
всей тщательностью, благодаря чему кодирование от начала до конца протекает
линейно: вы пишете код, тестируете его и оставляете в покое. Согласно этому мифу
значительные изменения кода возможны только при сопровождении ПО, т. е. после
выпуска первоначальной версии системы.
Реальность: во время разработки первоначальной версии системы в код
вносятся значительные изменения. Многие из изменений во время ко#
дирования не менее масштабны, чем изменения, характерные для стадии
сопровождения программы. В зависимости от размера проекта кодирование, от#
ладка и блочное тестирование обычно составляют от 30 до 65% общего объема
552 ЧАСТЬ V Усовершенствование кода
работы над проектом (см. главу 27). Если бы кодирование и блочное тестирова#
ние были линейными однократными процессами, они составляли бы не более 20–
30% общего объема работы. Однако даже в хорошо управляемых проектах требо#
вания изменяются примерно на 1–4% в месяц (Jones, 2000). Изменения требова#
ний неизбежно приводят к изменениям кода, порой весьма существенным.
Другая реальность: современные методики разработки предполагают
больший масштаб изменений кода во время конструирования. Целью
более старых методик (достигалась она или нет — другой вопрос) было
предотвращение изменений кода. Современные подходы снижают предсказуемость
кодирования. Они в большей степени ориентированы на код, поэтому вы вполне
можете ожидать, что на протяжении жизненного цикла проекта код будет изме#
няться сильнее, чем когда бы то ни было.
Философия эволюции ПО
Слабость многих подходов к эволюции ПО объясняется тем, что она пускается на
самотек. Осознав, что эволюция ПО во время его разработки — неизбежный и
важный процесс и спланировав его, вы сможете извлечь из него выгоду.
ГЛАВА 24 Рефакторинг 553
Пример кода подготовки к вызову метода и кода уборки — плохой код (C++)
Подготовительный код — дурной знак.
> WithdrawalTransaction withdrawal;
withdrawal.SetCustomerId( customerId );
withdrawal.SetBalance( balance );
withdrawal.SetWithdrawalAmount( withdrawalAmount );
withdrawal.SetWithdrawalDate( withdrawalDate );
ProcessWithdrawal( withdrawal );
Код уборки — еще один дурной знак.
Пример кода подготовки к вызову метода и кода уборки — плохой код (C++)
withdrawal = new WithdrawalTransaction( customerId, balance,
withdrawalAmount, withdrawalDate );
withdrawal.ProcessWithdrawal();
delete withdrawal;
Заметьте, что похожая проблема возникает и в обратном случае. Так, если у вас
обычно есть объект WithdrawalTransaction, но в метод ProcessWithdrawal переда#
ются только несколько значений объекта, вам следует подумать о рефакторинге
интерфейса метода, чтобы он принимал объект WithdrawalTransaction, а не его
отдельные поля:
Каждый из этих подходов может быть как верным, так и неверным: все зависит
от того, какую абстракцию формирует интерфейс метода ProcessWithdrawal(). Если
абстракция подразумевает, что метод ожидает четыре отдельных элемента дан#
ных, используйте один подход; если метод ожидает сразу весь объект Withdrawa%
lTransaction, выберите другой.
Программа содержит код, который может когда'нибудь понадобиться
Программисты очень плохо угадывают, какая функциональность может потребо#
ваться позднее. «Проектирование впрок» связано со многими предсказуемыми
проблемами, описанными ниже.
Требования к коду, «проектируемому впрок», не разрабатываются в полном
объеме, поэтому программист скорее всего не угадает эти будущие требова#
ния. Код, «написанный в расчете на будущее», в итоге придется выбросить.
Если догадка программиста по поводу будущих требований близка к истине,
он все же не сможет предвосхитить все их тонкости. Эти тонкости опроверг#
нут исходные предпосылки проектирования, и «проект, разработанный в рас#
чете на будущее», также придется выбросить.
Программисты, использующие код, который в свое время был «спроектирован
впрок», не знают об этом или предполагают, что код лучше, чем есть на самом
деле. Они думают, что этот код был написан, протестирован и подвергнут об#
зору с той же тщательностью, что и остальной. Они могут потратить много
времени на создание кода, использующего «спроектированное впрок», только
для того, чтобы обнаружить в итоге, что «спроектированное впрок» на самом
деле не работает.
558 ЧАСТЬ V Усовершенствование кода
Вместо определения таких отдельных констант лучше было бы создать класс Output%
Type: это обеспечило бы вам достоинства более строгого контроля типов и по#
560 ЧАСТЬ V Усовершенствование кода
Рис. 24'2. Ваш код не обязан быть грязным только потому, что таков реальный
мир. Рассматривайте систему как комбинацию идеального кода, грязного кода
и интерфейса между идеальным и грязным кодом
570 ЧАСТЬ V Усовершенствование кода
Дополнительные ресурсы
Процесс рефакторинга имеет много общего с процессом
устранения дефектов (см. раздел 23.3). Факторы риска, свя# http://cc2e.com/2464
Ключевые моменты
Изменения программы неизбежны как во время первоначальной разработки,
так и после выпуска первой версии.
Изменения могут приводить как к улучшению, так и к ухудшению ПО. Главное
Правило Эволюции ПО заключается в том, что при эволюции кода внутрен#
нее качество программы должно повышаться.
Одним из условий успешности рефакторинга является пристальное внимание
к многочисленным предупреждающим знакам — «запахам», указывающим на
необходимость рефакторинга.
Другое условие — изучение многих конкретных видов рефакторинга.
Заключительным условием успешности рефакторинга является следование
стратегии безопасного рефакторинга. Одни подходы к рефакторингу лучше,
а другие хуже.
Рефакторинг во время разработки — самая благоприятная возможность улуч#
шения программы и внесения в нее всех изменений, которые вам так или иначе
захочется внести позднее. Используйте эту возможность!
572 ЧАСТЬ V Усовершенствование кода
Г Л А В А 2 5
Стратегии
оптимизации кода
Содержание
http://cc2e.com/2578
25.1. Общее обсуждение производительности ПО
25.2. Введение в оптимизацию кода
25.3. Где искать жир и патоку?
25.4. Оценка производительности
25.5. Итерация
25.6. Подход к оптимизации кода: резюме
Связанные темы
Методики оптимизации кода: глава 26
Архитектура ПО: раздел 3.5
В этой главе обсуждается исторически противоречивая проблема — повышение
производительности кода. В 1960#х годах ресурсы компьютеров были крайне
ограниченны, поэтому эффективность их использования была вопросом перво#
степенной важности. По мере роста производительности компьютеров в 70#х
программисты начали понимать, насколько упор на производительность вредит
удобочитаемости и легкости сопровождения кода, и оптимизация кода отошла на
задний план. Вместе с микрокомпьютерной революцией, свидетелями которой мы
стали в 80#х, проблема эффективного использования ресурсов вернулась, но в 90#х
ее важность постепенно уменьшилась. В 2000#х мы опять столкнулись с этой про#
блемой, только теперь она связана с ограниченной памятью мобильных телефо#
нов, карманных ПК и подобных устройств, а также со временем выполнения ин#
терпретируемого кода.
Проблемы с производительностью можно решать на двух уровнях: стратегическом
и тактическом. В этой главе рассматриваются стратегические вопросы произво#
дительности: мы выясним, что такое производительность, насколько она важна,
и обсудим общий подход к ее повышению. Если стратегии достижения нужной
производительности вам уже хорошо известны и вы хотите узнать конкретные
ГЛАВА 25 Стратегии оптимизации кода 573
методики ее повышения на уровне кода, можете перейти к главе 26. Однако прежде
чем приступать к серьезной работе над повышением производительности, хотя
бы просмотрите эту главу, чтобы не потратить время на оптимизацию кода тог#
да, когда следовало бы заняться чем#то другим.
Характеристики качества
и производительность
Некоторые люди смотрят на мир через розовые очки. Про#
Во имя эффективности — при-
граммисты склонны воспринимать мир через кодовые очки. чем достигается она далеко не
Мы полагаем, что чем лучше будет наш код, тем сильнее наше всегда — совершается больше
ПО понравится клиентам. компьютерных грехов, чем по
любой другой причине, включая
Эта точка зрения верна лишь отчасти. Пользователей боль#
банальную глупость.
ше интересуют явные характеристики программы, а не ка#
У. А. Вульф (W. A. Wulf)
чество кода. Производительность обычно привлекает их
внимание, только когда она сказывается на их работе. Боль#
шее значение для пользователей имеет не грубая производительность приложе#
ния, а объем информации, который оно позволяет обработать за конкретный срок,
а также такие факторы, как своевременное получение программы, ясный пользо#
вательский интерфейс и предотвращение простоев.
Приведу пример. Я делаю цифровой камерой минимум 50 снимков в неделю. Чтобы
скопировать снимки на компьютер при помощи ПО, поставляемого с камерой, я
должен выбрать каждый снимок по очереди, причем в окне программы отобра#
жаются только 6 снимков сразу. В результате копирование 50 изображений пре#
вращается в долгий и нудный процесс, требующий десятков щелчков и массы
переключений между окнами. Устав от всего этого, я купил карту памяти, подклю#
чаемую прямо к компьютеру и воспринимаемую им как диск. Теперь для копиро#
вания изображений на диск компьютера я могу использовать Проводник Windows.
Я делаю пару щелчков, нажимаю Ctrl+A и перетаскиваю все файлы в нужное мес#
то. Меня не волнует, передает ли карта памяти каждый файл вдвое медленнее или
быстрее, чем другое ПО, потому что сейчас я могу обработать больший объем
информации за меньшее время. Каким бы быстрым или медленным ни был код
драйвера карты памяти, его производительность выше.
Производительность только косвенно связана с быстродействием кода.
Чем больше вы работаете над скоростью кода, тем меньше внимания
уделяете другим характеристикам его качества. Не приносите их в жер#
тву быстродействию. Стремление к повышению быстродействия может снизить
общую производительность программы, а не повысить ее.
574 ЧАСТЬ V Усовершенствование кода
Требования к программе
Высокая производительность считается требованием гораздо чаще, чем на самом
деле им является. Барри Бом рассказывает, что при разработке одной системы в
компании TRW сначала решили, что время реакции системы не должно превы#
шать 1 секунды. Это требование привело к разработке очень сложного проекта,
на реализацию которого пришлось бы потратить примерно 100 млн долларов. Даль#
нейший анализ показал, что в 90% случаев пользователей устроит время реакции,
равное 4 секундам. Изменение требования позволило снизить общую стоимость
системы примерно на 70 млн долларов (Boehm, 2000b).
Прежде чем тратить время на решение проблемы с производительностью, убеди#
тесь, что она действительно требует решения.
Проект программы
Проект программы определяет ее основные черты — глав#
Перекрестная ссылка Проекти-
рование высокопроизводитель- ным образом, способ ее разделения на классы. Проект мо#
ных программ рассматривается жет как облегчить, так и затруднить создание высокопро#
в работах, указанных в разде- изводительной системы.
ле «Дополнительные ресурсы»
в конце главы. Например, при высокоуровневом проектировании одной
реальной программы сбора и обработки данных в качестве
ключевого атрибута была определена пропускная способность обработки резуль#
татов измерений. Каждое измерение включало определение значения электриче#
ской величины, калибровку значения, масштабирование значения и преобразо#
вание исходных единиц измерения (таких как милливольты) в единицы прикладной
области (такие как градусы Цельсия).
Если бы при высокоуровневом проектировании программисты не оценили все
факторы риска, им в итоге пришлось бы оптимизировать алгоритмы вычисления
многочленов 13 степени, т. е. многочленов, содержащих 14 переменных с макси#
мальной степенью 13. Вместо этого они решили проблему, выбрав другое обору#
дование и разработав высокоуровневый проект, предусматривающий использо#
вание десятков многочленов 3 степени. Оптимизировав код, они вряд ли получи#
ли бы нужные результаты. Это пример проблемы, которую нужно было решать на
уровне проектирования.
ГЛАВА 25 Стратегии оптимизации кода 575
Взаимодействие программы с ОС
Если ваша программа работает с внешними файлами, дина#
Перекрестная ссылка О страте-
мической памятью или устройствами вывода, она скорее
гиях борьбы со слишком мед-
всего взаимодействует с ОС. Низкая производительность в ленными или объемными мето-
этом случае может объясняться большим объемом или мед# дами ОС на уровне кода см.
ленным выполнением методов ОС. Вы можете даже не знать, главу 26.
что программа взаимодействует с ОС: иногда вызовы сис#
темных методов генерируются компилятором или содержатся в коде библиотек.
Компиляция кода
Хорошие компиляторы преобразуют ясный высокоуровневый код в оптимизиро#
ванный машинный код. Иногда правильный выбор компилятора позволяет вооб#
ще забыть о дальнейшей оптимизации кода.
576 ЧАСТЬ V Усовершенствование кода
Оборудование
Иногда самым выгодным и эффективным способом повышения производитель#
ности программы является покупка нового оборудования. Конечно, если программа
предназначен для сотнен или тысяч компьютеров по всей стране, этот вариант
нереалистичен. Но если вы разрабатываете специализированное ПО для нескольких
пользователей, обновление оборудования на самом деле может оказаться самым
дешевым вариантом. Это позволит сократить расходы, связанные с улучшением
производительности ПО и проблемами, которые могут из#за этого улучшения воз#
никнуть при сопровождении. Кроме того, это повысит производительность всех
программ, выполняемых на новых системах.
Оптимизация кода
Оптимизацией кода (code tuning), которой посвящена оставшаяся часть этой главы,
называют изменение корректного кода, направленное на повышение его эффек#
тивности. «Оптимизация» подразумевает внесение небольших изменений, затра#
гивающих один класс, один метод, а чаще всего — несколько строк кода. Крупно#
масштабные изменения проекта или другие высокоуровневые способы повыше#
ния производительности оптимизацией не считаются.
Каждый из уровней от проектирования системы до оптимизации кода допускает
существенное повышение производительности ПО. Джон Бентли утверждает, что в
некоторых случаях формула общего повышения производительности системы имеет
мультипликативный характер (Bentley, 1982). Так как каждый из шести уровней
допускает десятикратный рост производительности, значит, что теоретически про#
изводительность программы может быть повышена в миллион раз. Хотя для этого
необходимо, чтобы результаты, получаемые на каждом из уровней, были незави#
симы от других уровней (что наблюдается редко), этот потенциал вдохновляет.
можете просто наклониться и взять его рукой. Если вы не хотите показаться но#
вичком, вы ударяете по нему ракеткой, пока он не подпрыгнет до пояса, и тогда
вы его ловите. Более трех ударов мяча о землю — серьезная оплошность. Несмот#
ря на кажущуюся незначительность, способ подбора мяча считается в культуре
теннисистов отличительным признаком. Компактность вашего кода также обыч#
но не волнует никого, кроме вас и других программистов. Тем не менее в про#
граммисткой культуре способность создавать компактный и эффективный код слу#
жит подтверждением вашего класса.
Увы, эффективный код не всегда является «лучшим». Этот вопрос мы и обсудим
ниже.
Принцип Парето
Принцип Парето, известный также как «правило 80/20», гласит, что 80% резуль#
тата можно получить, приложив 20% усилий. Относящийся не только к програм#
мированию, этот принцип очень точно характеризует оптимизацию программ.
Барри Бом сообщает, что на 20% методов программы приходятся 80%
времени ее выполнения (Boehm, 1987b). В классической работе «An Empi#
rical Study of Fortran Programs» Дональд Кнут указал, что менее 4% кода обыч#
но соответствуют более чем 50% времени выполнения программы (Knuth, 1971).
Кнут обнаружил это неожиданное отношение при помощи инструмента профи#
лирования, поддерживающего подсчет строк. Следствие очевидно: вам нужно найти
в коде «горячие точки» и сосредоточиться на оптимизации процентов, использу#
емых более всего. Профилируя свою программу подсчета строк, Кнут обнаружил,
что половину времени она проводила в двух циклах. Он изменил несколько строк
кода и удвоил скорость профайлера менее чем за час.
Джон Бентли описывает случай, когда программа из 1000 строк проводила 80%
времени в 5#строчном методе вычисления квадратного корня. Утроив быстродей#
ствие этого метода, он удвоил быстродействие программы (Bentley, 1988). Опи#
раясь на принцип Парето, можно дать еще один совет: напишите большую часть
кода на интерпретируемом языке (скажем, на Python), а потом перепишите про#
блемные фрагменты на более быстром компилируемом языке, таком как C.
Бентли также сообщает о случае, когда группа обнаружила, что ОС половину вре#
мени проводит в одном небольшом цикле. Переписав цикл на микрокоде, разра#
ботчики ускорили его выполнение в 10 раз, но производительность системы ос#
талась прежней — они переписали цикл бездействия системы!
Разработчики языка ALGOL — прародителя большинства современных языков,
сыгравшего одну из самых главных ролей в истории программирования, — руко#
водствовались принципом «Лучшее — враг хорошего». Стремление к совершен#
ству может мешать завершению работы. Доведите работу до конца и только по#
том совершенствуйтесь. Часть, которую нужно довести до совершенства, обычно
невелика.
578 ЧАСТЬ V Усовершенствование кода
Бабушкины сказки
С оптимизацией кода связано множество заблуждений.
Сокращение числа строк высокоуровневого кода повышает быстродей'
ствие или уменьшает объем итогового машинного кода — НЕВЕРНО!
Многие убеждены в том, что, если сократить какой#то фрагмент до одной или двух
строк, он будет максимально эффективным. Рассмотрим код инициализации мас#
сива из 10 элементов:
for i = 1 to 10
a[ i ] = i
end for
Как вы думаете, он выполнится быстрее или медленнее, чем эти 10 строк, решаю#
щих ту же задачу?
a[ 1 ] = 1
a[ 2 ] = 2
a[ 3 ] = 3
a[ 4 ] = 4
a[ 5 ] = 5
a[ 6 ] = 6
a[ 7 ] = 7
a[ 8 ] = 8
a[ 9 ] = 9
a[ 10 ] = 10
Далее он должен был убедить в этом участников проекта. Они выслушали его, и
когда он закончил, создатель старой системы спросил:
— И как быстро выполняется ваша программа?
— Ну, в среднем она обрабатывает каждый набор введенных данных примерно за
10 секунд.
— Ага! Но моей программе для этого требуется только 1 секунда.
Ветеран откинулся назад, удовлетворенный тем, что он приструнил выскочку.
Другие программисты, похоже, согласились с ним, но новичок не смутился.
— Да, но ваша программа не работает. Если бы моя не обязана была работать, я
мог бы сделать так, чтобы она обрабатывала ввод почти мгновенно.
В некоторых проектах быстродействие или компактность кода действительно имеет
большое значение. Однако таких проектов немного — гораздо меньше, чем ка#
жется большинству людей, — и их число постоянно сокращается. В этих проек#
тах проблемы с производительностью нужно решать путем предварительного
проектирования. В остальных случаях ранняя оптимизация представляет серьез#
ную угрозу для общего качества ПО, включая производительность.
1
Часто цитируемая фраза из негритянской сказки про Братца Кролика и Братца Волка в изло#
жении У. Фолкнера. — Прим. перев.
582 ЧАСТЬ V Усовершенствование кода
Время выполне-
Время выполне- ния кода, оптими-
ния кода без зированного Экономия Соотношение
Язык оптимизации компилятором времени быстродействия
Компилятор C++ 1 2,21 1,05 52% 2:1
Компилятор C++ 2 2,78 1,15 59% 2,5:1
Компилятор C++ 3 2,43 1,25 49% 2:1
Компилятор C# 1,55 1,55 0% 1:1
Visual Basic 1,78 1,78 0% 1:1
Java VM 1 2,77 2,77 0% 1:1
Java VM 2 1,39 1,38 <1% 1:1
Java VM 3 2,63 2,63 0% 1:1
Единственное различие между версиями метода заключалось в том, что при пер#
вой компиляции оптимизация была отключена, а при второй включена. Очевид#
но, что одни компиляторы выполняют оптимизацию лучше, чем другие, а неко#
торые изначально генерируют максимально эффективный код без его оптимиза#
ции. Некоторые виртуальные машины Java (Java Virtual Machine, JVM) также бо#
лее эффективны, чем другие. Эффективность вашего компилятора или вашей JVM
может быть другой; оцените ее сами.
ГЛАВА 25 Стратегии оптимизации кода 583
Если для доступа к внешним данным используется более медленная среда (напри#
мер, сетевое соединение), разница только увеличивается. При тестировании слу#
чайного доступа к данным по сети результаты выглядят так:
Конечно, эти результаты сильно зависят от скорости сети, объема трафика, рас#
стояния между компьютером и сетевым диском, производительности сетевого и
локального дисков, фазы Луны и других факторов.
В целом доступ к данным «в памяти» выполняется гораздо быстрее, так что дваж#
ды подумайте, прежде чем включать операции ввода/вывода в фрагменты, к быс#
тродействию которых предъявляются повышенные требования.
Замещение страниц Операция, заставляющая ОС заменять страницы памяти,
выполняется гораздо медленнее, чем операция, ограниченная одной страницей
памяти. Иногда самое простое изменение может принести огромную пользу. На#
пример, один программист обнаружил, что в системе, использующей страницы
объемом по 4 кб, следующий цикл инициализации вызывает массу страничных
ошибок:
Этот код также вызывает страничную ошибку при каждом изменении номера стро#
ки, но это происходит только MAX_ROWS раз, а не MAX_ROWS * MAX_COLUMNS раз.
Степень снижения быстродействия кода из#за замещения страниц во многом за#
висит от объема памяти. На компьютере с небольшим объемом памяти второй
фрагмент кода выполнялся примерно в 1000 раз быстрее, чем первый. При нали#
чии большего объема памяти различие было всего лишь двукратным и было за#
метно лишь при очень больших значениях MAX_ROWS и MAX_COLUMNS.
Системные вызовы Вызовы системных методов часто дороги. Они нередко вклю#
чают переключение контекста — сохранение состояния программы, восстановле#
ние состояния ядра ОС и наоборот. В число системных методов входят методы,
служащие для работы с диском, клавиатурой, монитором, принтером и другими
ГЛАВА 25 Стратегии оптимизации кода 585
Как видите, в плане быстродействия языки C++, Visual Basic и C# примерно одина#
ковы. Код на Java выполняется несколько медленнее. PHP и Python — интерпрети#
руемые языки, и код, написанный на них, обычно выполняется в 100 и более раз
медленнее, чем написанный на C++, Visual Basic, C# или Java. Однако к общим ре#
зультатам, указанным в этой таблице, следует относиться с осторожностью. Отно#
сительная эффективность C++, Visual Basic, C#, Java и других языков во многом за#
висит от конкретного кода (читая главу 26, вы сами в этом убедитесь).
586 ЧАСТЬ V Усовершенствование кода
Относительное быстродействие
распространенных операций
Хотя нельзя с полной уверенностью утверждать, что одни операции медленнее
других, не оценив их, определенные операции все же обычно дороже. Отыскивая
патоку в своей программе, используйте табл. 25#2, которая поможет вам выдвинуть
первоначальные предположения о том, какие фрагменты кода неэффективны.
Табл. 25-2. Быстрота выполнения часто используемых операций
Относительное время выполнения
Операция Пример C++ Java
Исходный показатель i=j 1 1
(целочисленное присваивание)
Вызовы методов
Вызов метода без параметров foo() 1 —
Вызов закрытого метода this.foo() 1 0,5
без параметров
Вызов закрытого метода this.foo( i ) 1,5 0,5
с одним параметром
Вызов закрытого метода this.foo( i, j ) 2 0,5
с двумя параметрами
Вызов метода объекта bar.foo() 2 1
Вызов метода производ# derivedBar.foo() 2 1
ного объекта
Вызов полиморфного метода abstractBar.foo() 2,5 2
Обращения к объектам
Обращение к объекту i = obj.num 1 1
1#го уровня
Обращение к объекту i = obj1.obj2. num 1 1
2#го уровня
Стоимость каждого i = obj1.obj2.obj3... неизмеряема неизмеряема
дополнительного уровня
ГЛАВА 25 Стратегии оптимизации кода 587
25.5. Итерация
Обнаружив в коде узкое место и попробовав его устранить, вы удивитесь, насколько
можно повысить производительность кода путем его оптимизации. Единственная
методика редко приводит к десятикратному улучшению, но методики можно эф#
фективно комбинировать, поэтому даже после обнаружения одного удачного вида
оптимизации продолжайте пробовать другие виды.
Однажды я написал программную реализацию алгоритма Data Encryption Standard
(DES). Ну, на самом деле я писал ее не один раз, а около тридцати. При шифрова#
нии по алгоритму DES цифровые данные кодируются так, что их нельзя расшиф#
ровать без правильного пароля. Этот алгоритм шифрования так хитер, что иног#
да кажется, что он сам зашифрован. Моя цель состояла в том, чтобы файл объе#
мом 18 кб шифровался на IBM PC за 37 секунд. Первая реализация алгоритма вы#
полнялась 21 минуту 40 секунд, так что мне предстояла долгая работа.
Хотя большинство отдельных видов оптимизации было незначительно, в сумме
они привели к впечатляющим результатам. Никакие три или даже четыре вида
оптимизации не позволили бы мне достичь цели, однако итоговая их комбина#
ция оказалась эффективной. Мораль: если копать достаточно глубоко, можно до#
биться подчас неожиданных результатов.
Оптимизация алгоритма DES — самая агрессивная оптими#
Перекрестная ссылка Методи-
зация, которую я когда#либо проделывал. В то же время я
ки, указанные в этой таблице,
обсуждаются в главе 26. никогда не создавал более непонятного и трудного в сопро#
вождении кода. Первоначальный алгоритм был сложен. Код,
получившийся в результате трансформаций высокоуровневого кода, оказался
практически нечитаемым. После преобразования кода на ассемблер я получил один
метод из 500 строк, на который боюсь даже смотреть. Это отношение между оп#
тимизацией кода и его качеством справедливо почти всегда. Вот таблица, отра#
жающая историю оптимизации:
ГЛАВА 25 Стратегии оптимизации кода 591
Дополнительные ресурсы
В этом разделе я указал работы, посвященные повышению
http://cc2e.com/2585
производительности в общем. Книги, в которых обсуждаются
специфические методики оптимизации кода, указаны в раз#
деле «Дополнительные ресурсы» в конце главы 26.
Производительность
Smith, Connie U. and Lloyd G. Williams. Performance Solutions:
http://cc2e.com/2592A Practical Guide to Creating Responsive, Scalable Software. Boston,
MA: Addison#Wesley, 2002. В этой книге обсуждается создание
высокопроизводительного ПО, предусматривающее обеспечение нужной произво#
дительности на всех этапах разработки. В ней вы найдете много примеров и кон#
кретных случаев, относящихся к программам нескольких типов, а также конкрет#
ные рекомендации по повышению производительности Web#приложений. Особое
внимание в книге уделяется масштабируемости программ.
Newcomer, Joseph M. Optimization: Your Worst Enemy. May 2000,
http://cc2e.com/2599
www.flounder.com/optimization.htm. В этой статье, принадле#
жащей перу опытного системного программиста, описыва#
ются разные ловушки, в которые вы можете попасть, используя неэффективные
стратегии оптимизации.
Ключевые моменты
Производительность — всего лишь один из аспектов общего качества ПО, и,
как правило, не самый важный. Оптимизация кода — лишь один из способов
повышения производительности ПО, и тоже обычно не самый важный. Быст#
родействие программы и ее объем обычно в большей степени зависят не от
эффективности кода, а от архитектуры программы, детального проектирова#
ния выбора структур данных и алгоритмов.
Важнейшее условие максимизации быстродействия кода — его количествен#
ная оценка. Она необходима для обнаружения областей, производительность
которых действительно нуждается в повышении, а также для проверки того,
что в результате оптимизации производительность повысилась, а не пони#
зилась.
594 ЧАСТЬ V Усовершенствование кода
Г Л А В А 2 6
Методики
оптимизации кода
Содержание
http://cc2e.com/2665
26.1. Логика
26.2. Циклы
26.3. Изменения типов данных
26.4. Выражения
26.5. Методы
26.6. Переписывание кода на низкоуровневом языке
26.7. Если что#то одно изменяется, что#то другое всегда остается постоянным
Связанные темы
Стратегии оптимизации кода: глава 25
Рефакторинг: глава 24
Оптимизация кода уже давно привлекает пристальное внимание программистов.
Если вы решили повысить производительность и хотите сделать это на уровне кода
(с учетом предупреждений, описанных в главе 25), то можете использовать це#
лый ряд методик.
Эта глава посвящена в первую очередь повышению быстродействия, но включает
и несколько советов по сокращению объема кода. Производительность обычно
охватывает и быстродействие, и объем кода, но при необходимости сокращения
объема кода обычно лучше прибегнуть к перепроектированию классов и данных,
а не к оптимизации кода. Последняя подразумевает небольшие изменения, а не
изменения более крупномасштабных аспектов проектирования.
В этой главе вы почти не найдете методик, настолько общих, чтобы код приме#
ров можно было копировать прямо в другие программы. Я просто хочу проил#
люстрировать ряд видов оптимизации кода, которые вы сможете приспособить
к своей ситуации.
Виды оптимизации, описываемые в этой главе, могут показаться похожими на виды
рефакторинга из главы 24, однако помните, что рефакторинг направлен на улуч#
596 ЧАСТЬ V Усовершенствование кода
шение внутренней структуры программы (Fowler, 1999). То, о чем мы будем гово#
рить в этой главе, возможно, лучше называть «антирефакторингом». Эти измене#
ния ухудшают внутреннуюю структуру программы ради повышения ее произво#
дительности. Это верно по определению. Если бы изменения не ухудшали внут#
реннюю структуру, мы не считали бы их видами оптимизации — мы использова#
ли бы их по умолчанию и считали стандартными методиками кодирования.
Некоторые авторы характеризуют методики оптимизации
Перекрестная ссылка Оптими-
кода как «практические правила» или приводят данные, го#
зация кода основана на эврис-
тике (см. раздел 5.3). ворящие о том, что определенный вид оптимизации непре#
менно обеспечит желательный результат. Однако, как вы ско#
ро увидите, концепция «практических правил» плохо описывает оптимизацию кода.
Единственным надежным практическим правилом является оценка результатов
каждого вида оптимизации в конкретной среде. Так что в этой главе представлен
каталог «вещей, которые стоит попробовать»: многие из них в вашей среде пользы
не принесут, но некоторые на самом деле окажутся очень эффективными.
26.1. Логика
Перекрестная ссылка О других Многие задачи программирования связаны с манипулиро#
аспектах использования опера- ванием логикой программы. В этом разделе мы рассмотрим
торов, определяющих логику эффективное использование логических выражений.
программы, см. главы 14–19.
ливая при обнаружении отрицательного числа флаг negativeFound. Вот как вы#
глядел бы такой цикл поиска:
Пример, в котором цикл продолжает выполняться даже после получения ответа (C++)
negativeInputFound = false;
for ( i = 0; i < count; i++ ) {
if ( input[ i ] < 0 ) {
negativeInputFound = true;
}
}
ProcessMathSymbol( inputCharacter )
Case Else
ProcessError( inputCharacter )
End Select
С Visual Basic все ясно, а вот результаты тестирования кода Java и C# довольно
неожиданны. Очевидно, это объясняется способом структурирования операторов
switch%case в языках C# и Java: из#за необходимости перечисления всех значений
по отдельности, а не в форме диапазонов, код C# и Java не выигрывает от этого
вида оптимизации в отличие от кода Visual Basic. Это доказывает, что никакой из
видов оптимизации не следует применять слепо: результаты будут во многом за#
висеть от реализации конкретных компиляторов.
Вы могли бы предположить, что для аналогичного набора операторов if%then%else
компилятор Visual Basic сгенерирует похожий код. Взгляните на результаты:
Соотношение
Язык case if-then-else Экономия времени быстродействия
C# 0,260 0,330 –27% 1:1
Java 2,56 0,460 82% 6:1
Visual Basic 0,260 1,00 258% 1:4
Вот как эта задача решается при помощи сложной логической цепи:
category = 0;
}
Определение этой таблицы кажется запутанным, но, если она хорошо докумен#
тирована, читать ее будет не труднее, чем код сложной логической цепи. Кроме
того, изменить таблицу будет гораздо легче, чем более раннюю логику. Вот ре#
зультаты сравнения быстродействия обоих подходов:
Время выполнения
Время выполнения оптимизированного Экономия Соотношение
Язык кода до оптимизации кода времени быстродействия
C++ 5,04 3,39 33% 1,5:1
Visual Basic 5,21 2,60 50% 2:1
Отложенные вычисления
Один из моих бывших соседей любил все откладывать на потом. В оправдание
своей лени он говорил, что многое из того, что люди порываются сделать, делать
просто не нужно. Если подождать достаточно долго, утверждал он, неважные дела
канут в Лету, и он не будет тратить на них свое время.
Методика отложенных вычислений основана на принципе моего соседа: программа
делает что#то, только когда это действительно нужно. Отложенное вычисление
похоже на стратегию решения задач «по требованию», при которой работа вы#
полняется максимально близко к тому месту, где нужны ее результаты.
Допустим, ваша программа работает с таблицей из 5000 значений, полностью
генерируя ее при запуске и затем обращайтесь к ней по мере выполнения. Если
программа использует только небольшую часть элементов таблицы, возможно, есть
смысл вычислять их по мере надобности, а не все сразу. После вычисления эле#
мента его можно сохранить на будущее (это называется «кэшированием»).
602 ЧАСТЬ V Усовершенствование кода
26.2. Циклы
Перекрестная ссылка О циклах
Так как циклы выполняются многократно, горячие точки
см. также главу 16. часто следует искать именно внутри циклов. Методики,
описываемые в этом разделе, помогают ускорить выполне#
ние циклов.
Размыкание цикла
Замыканием (switching) цикла называют принятие решения внутри цикла при
каждой его итерации. Если во время выполнения цикла решение не изменяется,
вы можете разомкнуть (unswitch) цикл, приняв решение вне цикла. Обычно для
этого нужно вывернуть цикл наизнанку, т. е. поместить циклы в условный опера#
тор, а не условный оператор внутрь цикла. Вот пример цикла до размыкания:
if ( sumType == SUMTYPE_NET ) {
for ( i = 0; i < count; i++ ) {
netSum = netSum + amount[ i ];
}
}
else {
for ( i = 0; i < count; i++ ) {
grossSum = grossSum + amount[ i ];
}
}
Объединение циклов
Если два цикла работают с одним набором элементов, можно выполнить их объе#
динение (jamming). Выгода здесь объясняется устранением затрат, связанных с
выполнением дополнительного цикла. Например, на объединение претендуют
следующие циклы:
Развертывание цикла
Целью развертывания (unrolling) цикла является сокращение затрат, связанных с его
выполнением. Если помните, после полного развертывания цикла из трех строк в главе
25 оказалось, что 10 отдельных обращений к массиву выполняются быстрее.
Полное развертывание цикла — быстрое решение, эффективное при малом числе
элементов, но оно непрактично, если элементов много или вы не знаете заранее, с
каким числом элементов вы будете иметь дело. Вот пример обычного цикла:
> i = 0;
while ( i < count ) {
a[ i ] = i;
i = i + 1;
}
Пример однократного
развертывания цикла (Java)
i = 0;
while ( i < count 1 ) {
a[ i ] = i;
a[ i + 1 ] = i + 1;
i = i + 2;
}
ГЛАВА 26 Методики оптимизации кода 605
Эти строки обрабатывают случай, который может быть упущен из-за увеличении счетчика цикла
на 2, а не на 1.
> if ( i == count ) {
a[ count 1 ] = count 1;
}
Пример двукратного
развертывания цикла (Java)
i = 0;
while ( i < count 2 ) {
a[ i ] = i;
a[ i + 1 ] = i+1;
a[ i + 2 ] = i+2;
i = i + 3;
}
if ( i <= count 1 ) {
a[ count 1 ] = count 1;
}
if ( i == count 2 ) {
a[ count 2 ] = count 2;
}
606 ЧАСТЬ V Усовершенствование кода
Время выполнения
Время выполнения кода после второго Экономия
Язык кода до оптимизации развертывания цикла времени
C++ 1,75 1,01 42%
Java 1,01 0,581 43%
PHP 5,33 3,70 31%
Python 2,51 2,79 #12%
Примечание: тестирование выполнено для случая count = 100.
Сигнальные значения
Если цикл включает проверку сложного условия, время его выполнения часто
можно сократить, упростив проверку. В случае циклов поиска это можно сделать,
использовав сигнальное значение (sentinel value) — значение, которое распола#
гается сразу после окончания диапазона поиска и непременно завершает поиск.
Классический пример сложной проверки, которую можно упростить с помощью
сигнального значения, — условие цикла поиска, включающее проверки обнару#
жения нужной переменной и выхода за пределы диапазона поиска. Вот код тако#
го цикла:
if ( found ) {
...
При каждой итерации этого цикла проверяются два условия: !found и i < count.
Проверка !found служит для определения того, найден ли нужный элемент. Про#
верка i < count нужна для предотвращения выхода за пределы массива. Кроме того,
внутри цикла проверяются отдельные значения массива item[], так что на самом
деле при каждой итерации цикла выполняются три проверки.
608 ЧАСТЬ V Усовершенствование кода
Этот вид циклов поиска позволяет объединить три проверки и выполнять при
каждой итерации только одну проверку: для этого нужно поместить в конце диа#
пазона поиска «сигнальное значение», завершающее цикл. В нашем случае мож#
но просто присвоить искомое значение элементу, располагающемуся сразу пос#
ле окончания диапазона поиска (объявляя массив, не забудьте выделить место для
этого элемента). Далее вы проверяете по очереди каждый элемент: если вы дос#
тигаете сигнального значения, значит, нужного вам значения в массиве нет. Вот
соответствующий код:
// Обнаружено ли значение?
if ( i < count ) {
...
Если item содержит целые числа, выгода может быть весьма существенной:
Время выполнения
Время выполнения оптимизированного Экономия Соотношение
Язык кода до оптимизации кода времени быстродействия
C# 0,771 0,590 23% 1,3:1
Java 1,63 0,912 44% 2:1
Visual Basic 1,34 0,470 65% 3:1
Примечание: поиск выполнялся в массиве из 100 целых чисел.
Ключ к улучшению цикла в том, что внешний цикл состоит из гораздо большего
числа итераций, чем внутренний. С выполнением любого цикла связаны наклад#
ные расходы: в начале цикла индекс должен быть инициализирован, а при каж#
дой итерации — увеличен и проверен. Общее число итераций равно 100 для внеш#
него цикла и 100 * 5 = 500 для внутреннего цикла, что дает в сумме 600 итераций.
Просто поменяв местами внешний и внутренний циклы, вы можете снизить чис#
ло итераций внешнего цикла до 5, тогда как число итераций внутреннего цикла
останется тем же. В итоге вместо 600 итераций будут выполнены только 505. Можно
ожидать, что перемена циклов местами приведет примерно к 16%#ому улучшению:
(600 – 505) / 600 = 16%. На самом деле результаты таковы:
Этот код прост, но дорог. В то же время цикл можно переписать так, чтобы при
каждой итерации выполнялось более дешевое сложение:
Соотношение
Время выполнения Время выполнения Экономия быстродействия
Язык кода до оптимизации оптимизированного времени кода
C++ 2,80 0,801 71% 3,5:1
PHP 5,01 4,65 7% 1:1
Visual Basic 6,84 0,280 96% 25:1
Время выполнения
Время выполнения оптимизированного Экономия Соотношение
Язык кода до оптимизации кода времени быстродействия
C++ 8,75 7,82 11% 1:1
C# 3,28 2,99 9% 1:1
Java 7,78 4,14 47% 2:1
PHP 6,24 4,10 34% 1,5:1
Python 3,31 2,23 32% 1,5:1
Visual Basic 9,43 3,22 66% 3:1
Примечание: временные показатели, указанные для Python и PHP, получены в резуль#
тате более чем в 100 раз меньшего числа итераций, чем показатели, приведенные для
других языков, поэтому их непосредственное сравнение недопустимо.
Результаты этого вида оптимизации прекрасны для Visual Basic и Java, хороши для
PHP и Python, но довольно заурядны для C++ и C#. Правда, время выполнения не#
оптимизированного кода C# было лучшим, так что на это едва ли можно жаловаться.
Широкий разброс результатов лишь подтверждает недальновидность слепого сле#
дования любым советам по оптимизации. Не испытав методику в конкретных об#
стоятельствах, ни в чем нельзя быть уверенным.
Результаты:
Кэширование
Кэширование — это такой способ хранения нескольких значений, при котором
значения, используемые чаще всего, получить легче, чем значения, используемые
реже. Так, если программа случайным образом читает записи с диска, метод мо#
жет хранить в кэше записи, считываемые наиболее часто. Получив запрос запи#
си, метод проверяет, имеется ли запись в кэше. Если да, запись возвращается не#
посредственно из памяти, а не считывается с диска.
Кэширование можно применять и в других областях. В программе обработки
шрифтов Microsoft Windows узким местом было получение ширины символа при
его отображении на экране. Кэширование ширины символа, использованного
последним, позволило примерно вдвое ускорить отображение.
Вы можете кэшировать и результаты ресурсоемких вычислений, особенно если
их параметры просты. Пусть, например, вам нужно найти длину гипотенузы пря#
моугольного треугольника по длинам двух катетов. Простая реализация этого
метода была бы примерно такой:
Если вы знаете, что те же значения скорее всего будут переданы в метод повтор#
но, их можно кэшировать:
ГЛАВА 26 Методики оптимизации кода 615
return cachedHypotenuse;
}
Вторая версия метода сложнее и объемнее первой, поэтому она должна обосно#
вать свое право на жизнь быстродействием. Многие схемы кэширования предпо#
лагают кэширование более одного элемента, и с ними связаны еще большие за#
траты. Вот быстродействие двух версий метода:
Время выполнения
Время выполнения оптимизированного Экономия Соотношение
Язык кода до оптимизации кода времени быстродействия
C++ 4,06 1,05 74% 4:1
Java 2,54 1,40 45% 2:1
Python 8,16 4,17 49% 2:1
Visual Basic 24,0 12,9 47% 2:1
Примечание: эти результаты предполагают, что на каждый промах кэша приходятся
два попадания.
26.4. Выражения
Многие задачи программирования требуют применения
Перекрестная ссылка О выраже-
ниях см. раздел 19.1. математических и логических выражений. Сложные выра#
жения обычно дороги, и в этом разделе мы рассмотрим
способы их удешевления.
Алгебраические тождества
Алгебраические тождества иногда позволяют заменить дорогие операции на бо#
лее дешевые. Так, следующие выражения логически эквивалентны:
not a and not b
not (a or b)
Время выполнения
Время выполнения оптимизированного Экономия Соотношение
Язык кода до оптимизации кода времени быстродействия
C++ 7,43 0,010 99,9% 750:1
Visual Basic 4,59 0,220 95% 20:1
Python 4,21 0,401 90% 10:1
Если вы имеете дело с многочленами второй или более высокой степени, выгода
может быть очень приличной:
Время выполнения
Время выполнения оптимизированного Экономия Соотношение
Язык кода до оптимизации кода времени быстродействия
Python 3,24 2,60 20% 1:1
Visual Basic 6,26 0,160 97% 40:1
Экономия
Время Время выполнения Время выполнения времени за
кода выполнения после первой после второй счет второй
Язык до оптимизации оптимизации оптимизации оптимизации
Python 3,24 2,60 2,53 3%
Visual Basic 6,26 0,16 0,31 #94%
Этот метод был очень медленным, а так как значение log(2) измениться не может,
я заменил вызов метода log(2) на действительное значение, равное 0.69314718:
Вызов метода log() довольно дорог — гораздо дороже преобразования типа или
деления, и поэтому резонно предположить, что уменьшение числа вызовов мето#
да log() вдвое должно примерно в два раза ускорить выполнение метода. Вот ре#
зультаты измерений:
ГЛАВА 26 Методики оптимизации кода 619
Время выполнения
Время выполнения оптимизированного Экономия Соотношение
Язык кода до оптимизации кода времени быстродействия
C++ 9,66 0,662 93% 15:1
Java 17,0 0,882 95% 20:1
PHP 2,45 3,45 #41% 2:3
620 ЧАСТЬ V Усовершенствование кода
Время выполнения
Время выполнения оптимизированного Экономия Соотношение
Язык кода до оптимизации кода времени быстродействия
C++ 1,11 0,000 100% Не поддается
измерению
C# 1,49 1,48 <1% 1:1
Java 1,66 1,11 33% 1,5:1
Visual Basic 0,721 0,000 100% Не поддается
измерению
PHP 0,872 0,847 3% 1:1
double interestRate
) {
return loanAmount /
(
( 1.0 Math.pow( ( 1.0 + ( interestRate / 12.0 ) ), months ) ) /
( interestRate / 12.0 )
);
}
Время выполнения
Время выполнения оптимизированного Экономия Соотношение
Язык кода до оптимизации кода времени быстродействия
Java 2,97 0,251 92% 10:1
Python 3,86 4,63 –20% 1:1
> ...
}
}
Время выполнения
Время выполнения оптимизированного Экономия Соотношение
Язык кода до оптимизации кода времени быстродействия
Java 7,43 0,24 97% 30:1
Python 5,00 1,69 66% 3:1
Результаты не впечатляют:
26.5. Методы
Одним из самых эффективных способов оптимизации кода
Перекрестная ссылка Об исполь-
является грамотная декомпозиция программы на методы. зовании методов см. главу 7.
Небольшие, хорошо определенные методы делают програм#
му компактнее, устраняя повторяющиеся фрагменты кода. Они упрощают опти#
мизацию, потому что рефакторинг одного метода улучшает все методы, которые
его вызывают. Небольшие методы относительно легко переписать на низкоуров#
невом языке. Объемные хитроумные методы понять сложно, а после переписы#
вания их на низкоуровневом языке вроде ассемблера это вообще невыполнимо.
Встраивание методов
На заре программирования вызывать методы на некоторых компьютерах было
крайне дорого. Вызов метода означал, что ОС должна выгрузить программу, за#
грузить каталог методов, загрузить конкретный метод, выполнить метод, выгру#
зить метод и снова загрузить вызвавший метод. Все это потребляло много ресур#
сов и замедляло программу.
Современные компьютеры облагают вызовы методов гораздо меньшей пошлиной.
Например, встраивание метода копирования строк приводит к таким результатам:
var
index: integer;
lowerByte: byte;
upperByte: byte;
targetIndex: integer;
begin
targetIndex := 1;
for index := 1 to byteCount do begin
target[ targetIndex ] := ( (source[ index ] and $F0) shr 4 ) + $41;
target[ targetIndex+1 ] := (source[ index ] and $0f) + $41;
targetIndex := targetIndex + 2;
end;
end;
Трудно увидеть жир в этом коде, однако он содержит много манипуляций с бита#
ми, что не является сильной стороной Delphi. А вот ассемблер подходит для это#
го как нельзя лучше, так что этот код является хорошим кандидатом на перепи#
сывание. Вот что получается в итоге:
asm
MOV ECX,byteCount // Загрузка числа расширяемых байт.
MOV ESI,source // Смещение источника.
MOV EDI,target // Смещение приемника.
XOR EAX,EAX // Обнуление индекса смещения в массиве.
EXPAND:
MOV EBX,EAX // Смещение в массиве.
MOV DL,[ESI+EBX] // Получение байта источника.
MOV DH,DL // Копирование байта источника.
Дополнительные ресурсы
По#моему, лучшая работа по оптимизации кода — Writing
http://cc2e.com/2679 Efficient Programs (Bentley, Englewood Cliffs, NJ: Prentice Hall,
1982). Это довольно старая книга, и все же постарайтесь ее
найти. В ней вы найдете экспертное обсуждение общих вопросов оптимизации
кода. Бентли описывает методики обмена времени на пространство и простран#
ства на время, а также приводит несколько примеров перепроектирования типов
данных, позволяющего и ускорить код, и сделать его компактнее. Подход Бентли
чуть более описателен, чем тот, что принял я, но приведенные им случаи весьма
интересны. Бентли проводит несколько методов через несколько этапов оптими#
зации, позволяя увидеть результаты первой, второй и третьей попыток. Описание
главной идеи занимает 135 страниц. Эта книга отличается необычайно высоким
отношением «сигнал/шум», что делает ее одним из редких бриллиантов, которые
следует иметь каждому практикующему программисту.
В приложении 4 книги Бентли Programming Pearls, 2d ed. (Bentley, Boston, MA:
Addison#Wesley, 2000) вы можете найти резюме правил оптимизации кода, опи#
санных в его более ранней книге.
Кроме того, есть целый ряд книг, в которых вопросы опти#
http://cc2e.com/2686
мизации рассматриваются в контексте конкретных техно#
логий. Некоторые из них перечислены ниже, а самый све#
жий список вы найдете на Web#странице, адрес которой указан слева.
Booth, Rick. Inner Loops: A Sourcebook for Fast 32%bit Software Development. Boston,
MA: Addison#Wesley, 1997.
Gerber, Richard. Software Optimization Cookbook: High%Performance Recipes for the Intel
Architecture. Intel Press, 2002.
Hasan, Jeffrey and Kenneth Tu. Performance Tuning and Optimizing ASP.NET Applica%
tions. Berkeley, CA: Apress, 2003.
Killelea, Patrick. Web Performance Tuning, 2d ed. Sebastopol, CA: O’Reilly & Associates,
2002.
Larman, Craig and Rhett Guthrie. Java 2 Performance and Idiom Guide. Englewood Cliffs,
NJ: Prentice Hall, 2000.
ГЛАВА 26 Методики оптимизации кода 631
Shirazi, Jack. Java Performance Tuning. Sebastopol, CA: O’Reilly & Associates, 2000.
Wilson, Steve and Jeff Kesselman. Java Platform Performance: Strategies and Tactics.
Boston, MA: Addison#Wesley, 2000.
Ключевые моменты
Результаты конкретных видов оптимизации во многом зависят от языка, ком#
пилятора и среды. Не оценив результатов оптимизации, вы не сможете сказать,
помогает она программе или вредит.
Первый вид оптимизации часто далеко не самый лучший. Обнаружив эффек#
тивный вид оптимизации, продолжайте пробовать и, возможно, найдете еще
более эффективный.
Оптимизация кода похожа на ядерную энергию — это противоречивая и эмо#
циональная тема. Кто#то считает, что оптимизация настолько ухудшает надеж#
ность и удобство сопровождения программы, что ее вообще выполнять не
следует. Другие думают, что при соблюдении должной предосторожности она
приносит пользу. Если вы решите использовать методики, описанные в этой
главе, будьте внимательны и осторожны.
ГЛАВА 25 Стратегии оптимизации кода 633
Часть VI
СИСТЕМНЫЕ ВОПРОСЫ
Г Л А В А 2 7
Содержание
http://cc2e.com/2761
27.1. Взаимодействие и размер
27.2. Диапазон размеров проектов
27.3. Влияние размера проекта на возникновение ошибок
27.4. Влияние размера проекта на производительность
27.5. Влияние размера проекта на процесс разработки
Связанные темы
Предварительные требования к конструированию: глава 3
Определение вида ПО, над которым вы работаете: раздел 3.2
Управление конструированием: глава 28
Масштабирование в области разработки ПО — это не просто вопрос увеличения
составных частей небольшого проекта. Допустим, вы разработали программный
комплекс Gigatron, содержащий 25 000 строк кода, за 20 человеко#месяцев и на#
шли 500 ошибок во время производственного тестирования. Допустим, Gigatron
1.0 имел успех, так же, как и Gigatron 2.0, и вы начали работу над Gigatron Deluxe
— значительно улучшенной версией программы, которая предположительно бу#
дет состоять из 250 000 строк кода.
Хотя ее размер в 10 раз превышает размер начальной версии, Gigatron Deluxe по#
требует не 10#кратных усилий при разработке, а 30#кратных. Более того, 30#крат#
ные общие затраты не означают 30#кратных затрат только на конструирование.
Возможно, потребуется потратить в 25 раз больше усилий на конструирование, и в
40 — на архитектуру и системное тестирование. Точно так же вы не получите
10#кратный прирост ошибок — он может быть 15#кратным и даже больше.
Если вы привыкли работать над маленькими проектами, то ваш первый проект
среднего или большого размера может вырваться из#под контроля, превратившись
в необузданного зверя, вместо благоприятного исхода, который вы себе представ#
ляли. Эта глава рассказывает, какие виды хищников можно встретить и где найти
хлыст и кольцо для их укрощения. С другой стороны, если вы привыкли работать
ГЛАВА 27 Как размер программы влияет на конструирование 635
* Constructive Cost Model (конструктивная стоимостная модель) — метод оценки затрат на раз#
работку ПО. — Прим. перев.
ГЛАВА 27 Как размер программы влияет на конструирование 639
Методология и размер
Методология используется в проектах любых размеров. В маленьких проектах
методология скорее случайна и инстинктивна — в больших она скрупулезна и
тщательно спланирована.
Порой методология бывает столь неопределенной, что программисты даже не по#
дозревают, что ее используют. Некоторые программисты доказывают, что методо#
логия лишена гибкости, поэтому они не будут ее использовать. Хотя они могут не
использовать методологию сознательно, любой подход к программированию со#
стоит из методологии независимо от простоты этого подхода. Обычный утренний
подъем и поездка на работу являются рудиментарной методологией, хотя и не слиш#
ком вдохновляющей. Программисты, отказывающиеся использовать методологию,
на самом деле просто не используют ее явно — куда от нее денешься!
Формальный подход не всегда доставляет удовольствие, а если он применяется
неправильно, накладные расходы могут поглотить получаемые от него преиму#
щества. Однако возрастающая сложность больших проектов требует более осоз#
нанного отношения к методологии. Строить небоскреб и собачью будку нужно
по#разному. То же относится и к программным проектам разных размеров. В боль#
ших проектах случайный выбор методологии не соответствует задаче. Успешные
проектировщики выбирают стратегии для больших проектов осознанно.
В светском обществе чем формальней событие, тем более неудобную
одежду нужно надевать (высокие каблуки, галстуки и т. д.). В разработке
ПО чем формальней проект, тем больше бумажных документов необхо#
димо создать, чтобы убедиться, что вы выполнили ваше домашнее задание. Кей#
перс Джонс указывает, что в проекте длиной в 1000 строк примерно 7% затрат
приходится на бумажную работу, тогда как в проекте в 100 000 строк затраты на
бумажную работу увеличиваются в среднем до 26% (Jones, 1998).
Эта работа проводится не из чистой любви к писанине. Она является прямым ре#
зультатом интересного феномена: чем больше человек вам приходится коорди#
нировать, тем больше требуется документов (рис. 27#1).
ГЛАВА 27 Как размер программы влияет на конструирование 643
Дополнительные ресурсы
Для дальнейшего изучения темы данной главы ознакомьтесь
со следующими источниками. http://cc2e.com/2768
Brooks, Frederick P., Jr. The Mythical Man%Month: Essays on Software Engineering, An%
niversary Edition (2d ed.). Reading, MA: Addison#Wesley, 1995. Брукс работал в IBM
менеджером разработки OS/360 — гигантского проекта, занявшего 5000 челове#
ко#лет. В своем очаровательном эссе он обсуждает вопросы управления, свойствен#
ные маленьким и большим командам, и высказывает исключительно яркое мне#
ние о командах главных программистов.
DeGrace, Peter, и Leslie Stahl. Wicked Problems, Righteous Solutions: A Catalogue of Modern
Software Engineering Paradigms. Englewood Cliffs, NJ: Yourdon Press, 1990. Как следу#
ет из названия, книга классифицирует подходы к разработке ПО. Как упомина#
лось на протяжении всей этой главы, подход должен изменяться при изменении
размера проекта, и Дегрейс и Стал делают эту точку зрения очевидной. В разделе
«Attenuating and Truncating» главы 5 обсуждается настройка процессов разработ#
ки ПО в соответствии с размером проекта и другими формальностями. Книга
содержит описания моделей из НАСА и Минобороны, а также большое количе#
ство поучительных иллюстраций.
Jones, T. Capers. «Program Quality and Programmer Productivity». IBM Technical Report
TR 02.764 (January 1977): 42–78. Статья также доступна в книге Джонса Tutorial:
Programming Productivity: Issues for the Eighties, 2d ed. Los Angeles, CA: IEEE Computer
Society Press, 1986. Эта статья содержит первый глубокий анализ причин, по ко#
торым распределение расходов в больших проектах отлично от маленьких. Это
исчерпывающее обсуждение различий между большими и маленькими проекта#
ми, включающее требования и меры по обеспечению качества. Статья старая, но
все еще интересная.
Ключевые моменты
С ростом размера проекта появляется необходимость поддерживать взаимо#
действие. Смысл большинства подходов к методологии состоит в уменьшении
проблем взаимодействия, поэтому методология должна жить или умереть в
зависимости от ее вклада в облегчение взаимодействия.
При прочих равных, производительность в больших проектах будет ниже, чем
в маленьких.
При прочих равных большие проекты будут содержать больше ошибок на 1000
строк кода, чем маленькие.
Деятельность, которая в малых проектах сама собой разумеется, в больших
проектах должна быть тщательно спланирована. С ростом размера проекта
конструирование занимает все меньшую ее часть.
Увеличение масштаба легковесной методологии обычно работает лучше, чем
уменьшение масштаба тяжеловесной. Наиболее эффективный подход состоит
в использовании «правильновесной» методологии.
ГЛАВА 28 Управление конструированием 645
Г Л А В А 2 8
Управление
конструированием
Содержание
http://cc2e.com/2836
28.1. Поощрение хорошего кодирования
28.2. Управление конфигурацией
28.3. Оценка графика конструирования
28.4. Измерения
28.5. Гуманное обращение с программистами
28.6. Управление менеджером
Связанные темы
Предварительные требования к конструированию: глава 3
Определение вида ПО, над которым вы работаете: раздел 3.2
Размер программы: глава 27
Качество ПО: глава 20
На протяжении нескольких последних десятилетий управление разработкой ПО
является задачей повышенной сложности. Обсуждение общих вопросов управле#
ния программными проектами выходит за рамки данной книги, но в этой главе
рассматриваются некоторые специальные темы управления, напрямую связанные
с конструированием (рис. 28#1). Если вы разработчик, эта глава поможет вам по#
нять те вопросы, которые менеджеры должны принимать во внимание; если же
менеджер — понять, как менеджеры рассматривают разработчиков, а также как
эффективно управлять конструированием. Поскольку глава охватывает широкий
спектр вопросов, то в некоторых разделах также приведены источники дополни#
тельной информации.
646 ЧАСТЬ VI Системные вопросы
Версии инструментария
Для некоторых видов проектов может понадобиться реконструкция точной сре#
ды, используемой для разработки каждой конкретной версии ПО, включая ком#
пиляторы, компоновщики, библиотеки кода и т. д. В этом случае вам также следу#
ет поместить эти инструменты в систему управления версиями.
Конфигурации компьютеров
Многие компании (включая мою) на своем опыте познали преимущества созда#
ния стандартизованных машинных конфигураций. На стандартной рабочей стан#
ции, содержащей необходимый инструментарий, офисные приложения и другие
программы, создается образ диска. Этот образ копируется на машину каждого
разработчика. Стандартная конфигурация позволяет избежать уймы проблем, свя#
занных со слегка различающимися конфигурационными параметрами, версиями
применяемых инструментов и т. п. Стандартизованный образ диска также силь#
но упрощает подготовку новой машины по сравнению с необходимостью уста#
навливать каждую программу отдельно.
предыдущей книге, здесь дается общий обзор SCM и много практической инфор#
мации. Преимуществом этой книги является наличие советов, помогающих груп#
пам разработчиков изолировать и координировать свою работу.
SPMN. Little Book of Configuration Management. Arlington, VA:
Software Program Managers Network, 1998. Эту брошюра зна# http://cc2e.com/2857
комит с процессом управления конфигурацией и опреде#
ляет критически важные факторы успеха. Она находится в свободном доступе на
сайте SPMN по адресу www.spmn.com/products_guidebooks.html.
Bays, Michael. Software Release Methodology. Englewood Cliffs, NJ: Prentice Hall, 1999.
Эта книга обсуждает управление конфигурацией ПО с акцентом на выпуске про#
мышленной версии продукта.
Bersoff, Edward H. and Alan M. Davis. «Impacts of Life Cycle Models on Software Con#
figuration Management». Communications of the ACM 34, no. 8 (August, 1991): 104–
118. В этой статье описано, как влияют на SCM новые подходы к разработке ПО,
особенно касающиеся создания прототипов. Статья в особенности актуальна для
сред, использующих практику быстрой (agile) разработки приложений.
Подходы к оценке
Вы можете оценить размер проекта и усилия, требуемые для
Дополнительные сведения По-
его завершения, одним из нескольких способов: дробнее о методиках оценки гра-
применить оценочное ПО; фика работ см. главу 8 в «Rapid
использовать алгоритмический подход, такой как Cocomo Development» (McConnell, 1996)
и «Software Cost Estimation with
II, оценочная модель Барри Бома (Boehm et al., 2000); Cocomo II» (Boehm et al., 2000).
привлечь внешних экспертов для оценки проекта;
обсудить предполагаемые затраты на собрании;
оценить составные части проекта, а затем сложить их вместе;
предложить каждому оценить их собственные задания, а затем просуммиро#
вать полученные предположения;
применить опыт предыдущих проектов;
сохранить предыдущие оценки и посмотреть, насколько они были аккуратны,
а затем применять их для коррекции новых предположений.
656 ЧАСТЬ VI Системные вопросы
нения (Brooks, 1995). Это все равно, что подливать масло в огонь. Объяснение Брук#
са выглядит убедительно: новым людям требуется время на ознакомление с про#
ектом. Их обучение отнимет время у обученных работников. А само увеличение
числа участников увеличивает сложность и количество вариантов взаимодействий
в проекте. Брукс подчеркивает: то, что одна женщина может родить ребенка че#
рез девять месяцев, не значит, что девять женщин могут родить ребенка через месяц.
Несомненно, предупреждающий закон Брукса следует принимать во внимание
гораздо чаще, чем это делается. Существует тенденция бросать людей на проект
и надеяться, что они выполнят его вовремя. Менеджеры должны понимать, что
разрабатывать ПО не то же самое, что ковать железо: большее число работников
не обязательно означает больший объем выполненной работы.
В то же время простое утверждение, что подключение программистов к работе
над тормозящим проектом задерживает его еще больше, маскирует тот факт, что
при некоторых обстоятельствах подобные меры способны ускорить работу. Как
Брукс отмечает в анализе своего закона, подключение людей к программному
проекту не поможет, если задачи в этом проекте нельзя разбить на составные части
и выполнять их независимо. Но если задачи делятся на части, вы можете распре#
делить их между разными людьми, даже недавно включившимися в работу. Дру#
гие исследователи смогли формально определить обстоятельства, при которых вы
можете подключать людей к проекту на поздних стадиях и не вызывать еще боль#
шую его задержку (Abdel#Hamid, 1989; McConnell, 1999).
Сократить проект О таком мощном способе, как сокра#
Дополнительные сведения Аргу-
менты в защиту реализации толь- щение проекта, часто забывают. Если вы исключаете какую#
ко наиболее необходимой функ- то функцию, вы избавляетесь от проектирования, кодирова#
циональности см. в главе 14 «Fea- ния, отладки, тестирования и документирования этой функ#
ture-Set Control» книги «Rapid De- ции, а также от создания интерфейса между этой и други#
velopment» (McConnell, 1996).
ми функциями.
При первоначальном планировании продукта разделите его возможности на ка#
тегории «должны быть», «хорошо бы сделать» и «необязательные». Если вы отста#
ете, расставьте приоритеты в категориях «необязательные» и «хорошо бы сделать»
и отбросьте те, что имеют меньшее значение.
При невозможности отмены каких#то функций вы можете представить более де#
шевую версию той же функциональности. Так, вы можете сдать версию вовремя,
не обеспечив при этом максимальной производительности. Или в этой версии
менее важная функциональность будет реализована лишь в общих чертах. Вы
можете решить отбросить требования к скорости выполнения, так как медленную
версию сделать проще. Или можете решить отбросить требования к объему па#
мяти, поскольку версию, интенсивно использующую память, сделать быстрее.
Повторно оцените время разработки для менее важных функций. Какую функци#
ональность вы можете предоставить за два часа, два дня или две недели? Какую
выгоду вы получите от двухнедельной версии в отличие от двухдневной и чем двух#
дневная версия будет отличаться от двухчасовой?
ГЛАВА 28 Управление конструированием 661
28.4. Измерения
Программные проекты можно измерить по#разному. Далее приведены важные
причины, по которым вам стоит проводить измерение процесса.
Для любого атрибута проекта существует возможность его из'
мерения, что в любом случае не означает отказа от его изме'
рения Измерение может быть не абсолютно верным, его может быть
трудно сделать и, возможно, придется временами уточнять, но измерение даст вам
такой рычаг управления процессом разработки ПО, который вы не сможете по#
лучить иным способом (Gilb, 2004).
Если данные предназначены для использования в научном эксперименте, то их
надо измерить. Можете ли вы представить ученого, рекомендующего запретить
новый пищевой продукт, потому что группа белых крыс «кажется, чаще болеет»
по сравнению с контрольной группой? Бред! Вы бы потребовали более точного
ответа, например: «Крысы, которые ели новый пищевой продукт, болели на 3,7 дня
дольше, чем крысы, которые его не ели». Чтобы оценить методы разработки ПО,
662 ЧАСТЬ VI Системные вопросы
вы должны измерить их. Заявления типа «Этот новый метод выглядит более эф#
фективным» недостаточно хороши.
Отдавайте себе отчет о побочных эффектах изме'
Что измеряется, то выполня-
ется. рения Измерение влияет на мотивацию. Люди обращают
Том Питерс (Tom Peters)
внимание на выполняемые измерения, предполагая, что
измерения служат для их оценки. Выбирайте объекты для
измерения осторожно. Люди имеют склонность сосредоточиваться на работе,
которая измеряется, и игнорировать остальную.
Возражать против измерений означает утверждать, что лучше не знать
о том, что на самом деле происходит Если вы измерите какой#то аспект про#
екта, вы узнаете о нем нечто, чего не знали ранее. Вы сможете увидеть, стал ли про#
ект больше или меньше или остался таким же. Измерение предоставляет вам окно,
через которое вы можете увидеть хотя бы этот аспект проекта. Окошко может быть
маленьким и мутным до тех пор, пока вы не уточните свои измерения, но это все
равно лучше, чем не иметь окна вообще. Возражать против любых измерений лишь
потому, что некоторые из них неубедительны — все равно, что возражать против
наличия окон, потому что некоторые из них могут быть мутными.
Вы можете измерить практически любой аспект процесса разработки ПО. Вот
некоторые виды измерений, которые некоторые профессионалы посчитали по#
лезными (табл. 28#2).
Табл. 28-2. Полезные объекты для измерения в области разработки ПО
Размер Качество в целом
Общее количество строк Общее число дефектов
Общее количество строк комментариев Число дефектов в каждом классе или методе
Общее число классов или методов Среднее количество дефектов на тысячу
строк кода
Общее количество объявлений данных Среднее время между сбоями
Общее число пустых строк Ошибки, выявленные компилятором
Производительность
Количество человеко#часов,
затраченных на проект
Количество человеко#часов, потрачен#
ных на каждый класс или метод
Количество изменений каждого класса
или метода
Сумма в долларах, потраченная
на проект
Сумма в долларах, потраченная
на строку кода
Сумма в долларах, потраченная
на каждый дефект
Индивидуальные различия
Первоначальное исследование, показавшее огромные различия в произ#
водительности отдельных программистов, было проведено в конце 1960#х
Секменом, Эриксоном и Грантом (Sackman, Erikson, Grant, 1968). Они
изучали профессиональных программистов с примерно 7#летним стажем и вы#
яснили, что соотношение первоначального времени кодирования между лучшим
и худшим программистами — примерно 20:1, соотношение времени отладки —
более, чем 25:1, соотношение размера программы — 5:1, а соотношение скорос#
ти выполнения — 10:1.Они не нашли взаимосвязи между опытом программиста и
качеством кода или производительностью.
Хотя такие определенные значения соотношений, как 25:1, не имеют
особого смысла, более общие утверждения, скажем: «Программисты раз#
личаются между собой на порядки,» — достаточно содержательны и под#
тверждаются другими исследованиями профессиональных программистов (Curtis,
1981; Mills, 1983; DeMarco and Lister, 1985; Curtis et al., 1986; Card, 1987; Boehm and
Papaccio, 1988; Valett and McGarry, 1989; Boehm et al., 2000).
Командные различия
Команды программистов также показывают заметные различия в качестве ПО и
производительности. Хорошие программисты стремятся к объединению, так же
как и плохие программисты. Это наблюдение подтверждается исследованием 166
профессиональных программистов из 18 организаций (Demarco and Lister, 1999).
В одном исследовании семи идентичных проектов потраченные усилия
различались в 3,4 раза, а размеры программ — в 3 раза (Boehm, Gray and
Seewaldt, 1984). Несмотря на такую разницу в производительности, про#
граммистов в этом исследовании нельзя отнести к разным группам. Все они были
профессионалами с несколькими годами стажа, окончившими учебные заведения
по специальности, связанной с вычислительной техникой. Можно предположить,
что исследование менее гомогенной группы показало бы еще большие различия.
ГЛАВА 28 Управление конструированием 667
Вопросы религии
Менеджеры программных проектов не всегда осознают, что некоторые аспекты
программирования сродни религиозным вопросам. Если вы менеджер и пытаетесь
требовать исполнения определенных правил программирования, вы рискуете вы#
звать гнев ваших подопечных. Вот список таких «религиозных» вопросов:
язык программирования;
стиль отступов;
размещение скобок;
выбор среды разработки (IDE);
стиль комментариев;
компромиссы между эффективностью и читабельностью;
выбор методологии — например, между методом Scrum, экстремальным про#
граммированием или эволюционной разработкой;
программные утилиты;
соглашения по именованию;
применение goto;
использование глобальных переменных;
измерения, особенно меры производительности, например, количество строк
кода в день.
Общий знаменатель у всех этих вопросов в том, что позиция программиста по
каждому из них отражает его личный стиль. Если вы считаете, что должны контро#
лировать программиста в каких#то из этих «религиозных» сфер, учтите следующее.
Вы вторгаетесь в область, требующую деликатного обращения Разуз#
найте мнения программистов по поводу каждого эмоционального вопроса, прежде
чем окунуться в него с головой.
668 ЧАСТЬ VI Системные вопросы
Физическая среда
Проведите эксперимент: поезжайте за город, найдите ферму, отыщите фермера и
спросите его, во что ему обошлось оборудование. Фермер посмотрит в амбар и
увидит несколько тракторов, фургонов, зерновой комбайн, молотилку, и скажет, что
сумма превышает $100 000 на работника.
После этого найдите в городе фирму, занимающуюся разработкой ПО, отыщите
менеджера и спросите его, каковы его затраты на оборудование. Менеджер загля#
нет в офис, увидит стол, стул, несколько книг и компьютер и скажет, что они не
превышают $25 000 на каждого работника.
Физическая среда сильно влияет на производительность. Демарко и Листер (DeMarco
and Lister) опросили 166 программистов из 35 организаций о качестве их физи#
ческого окружения. Большинство работников оценило свои рабочие места как не#
приемлемые. После проведения соревнования по программированию оказалось,
что программисты, результаты которых попадают в первые 25%, имеют более про#
сторные, тихие и уединенные кабинеты, а также реже отвлекаются на других людей
и телефонные звонки. Вот сводка различий в офисном пространстве между луч#
шими и худшими программистами:
ГЛАВА 28 Управление конструированием 669
Соответствующие стандарты
IEEE Std 1058%1998, Standard for Software Project Management Plans.
IEEE Std 12207%1997, Information Technology — Software Life Cycle Processes.
672 ЧАСТЬ VI Системные вопросы
Ключевые моменты
Хорошая практика кодирования может быть достигнута путем внедрения стан#
дартов или более ловкими способами.
Управление конфигурацией при правильном применении делает работу про#
граммистов проще. Это особенно касается контроля изменений.
Правильная оценка ПО — сложная задача. Ключ к успеху — использование
нескольких подходов, уточнение оценок по мере продвижения проекта и при#
менение накопленных данных при выполнении оценки.
Измерения — это ключ к успешному управлению конструированием. Вы все#
гда сможете найти способы измерить любой аспект проекта, что будет лучше,
чем полное отсутствие измерений. Точное измерение — ключ к точному со#
ставлению плана, контролю качества и улучшению процесса разработки.
Программисты и менеджеры — в первую очередь люди, и они лучше всего
работают тогда, когда к ним относятся по#человечески.
ГЛАВА 28 Управлениена конструированием 673
ГЛ А В А 2 9
Интеграция
Содержание
http://cc2e.com/2985
29.1. Важность выбора подхода к интеграции
29.2. Частота интеграции — поэтапная или инкремент-
ная?
29.3. Стратегии инкрементной интеграции
29.4. Ежедневная сборка и дымовые тесты
Связанные темы
Тестирование во время разработки: глава 22
Отладка: глава 23
Управление конструированием: глава 28
Термин «интеграция» относится к такой операции в процессе разработки ПО,
при которой вы объединяете отдельные программные компоненты в единую си-
стему. В небольших проектах интеграция может занять одно утро и заключаться
в объединении горстки классов. В больших — могут потребоваться недели или
месяцы, чтобы связать воедино весь набор программ. Независимо от размера за-
дач в них применяются одни и те же принципы.
Тема интеграции тесно переплетается с вопросом последовательности конструи-
рования. Порядок, в котором вы создаете классы или компоненты, влияет на по-
рядок их интеграции: вы не можете интегрировать то, что еще не было создано.
Последовательности интеграции и конструирования имеют большое значение.
В этой главе мы рассмотрим оба вопроса с точки зрения интеграции.
Поэтапная интеграция
За исключением последних нескольких лет поэтапная интеграция была нормой.
Она состояла из хорошо определенных этапов, перечисленных ниже.
1. «Модульная разработка»: проектирование, кодирование, тестирование и отладка
каждого класса.
2. «Системная интеграция»: объединение классов в одну огромную систему.
3. «Системная дезинтеграция» [спасибо Мейлиру Пейдж#Джонсу (Meilir Page#Jones)
за это остроумное замечание]: тестирование и отладка всей системы.
Проблема поэтапной интеграции в том, что, когда классы в системе впервые
соединяются вместе, неизбежно возникают новые проблемы и их причины могут
быть в чем угодно. Поскольку у вас масса классов, которые никогда раньше не ра-
ботали вместе, виновником может быть плохо протестированный класс, ошибка в
интерфейсе между двумя классами или ошибка, вызванная взаимодействием двух
классов. Все классы находятся под подозрением.
Неопределенность местонахождения любой из проблем сочетается с тем фактом,
что все эти проблемы вдруг проявляют себя одновременно. Это заставляет вас
иметь дело не только с проблемами, вызванными взаимодействием классов, но
и другими ошибками, которые трудно диагностировать, так как они взаимодей-
ствуют. Поэтому поэтапную интеграцию называют еще «интеграцией большого
взрыва» (рис. 29#2).
Инкрементная интеграция
При инкрементной интеграции вы пишете и тестируете
Перекрестная ссылка О метафо-
рах, подходящих для инкремент-
маленькие участки программы, а затем комбинируете эти
ной интеграции, см. подразделы кусочки друг с другом по одному. При таком подходе — по
«Метафора жемчужины: мед- одному элементу за раз — вы выполняете перечисленные
ленное приращение системы» далее действия.
и «Строительная метафора: по-
1. Разрабатываете небольшую, функциональную часть си-
строение ПО» раздела 2.3.
стемы. Это может быть наименьшая функциональная часть,
самая сложная часть, основная часть или их комбинация.
Тщательно тестируете и отлаживаете ее. Она послужит скелетом, на котором
будут наращиваться мускулы, нервы и кожа, составляющие остальные части
системы.
2. Проектируете, кодируете, тестируете и отлаживаете класс.
3. Прикрепляете новый класс к скелету. Тестируете и отлаживаете соединение
скелета и нового класса. Убеждаетесь, что эта комбинация работает, прежде
чем переходить к добавлению нового класса. Если дело сделано, повторяете
процесс, начиная с п. 2.
У вас может возникнуть желание интегрировать большие модули, чем отдельный
класс. Например, если компонент был тщательно протестирован и каждый из его
классов прошел мини#интеграцию, вы можете интегрировать весь компонент,
и это все еще будет инкрементная интеграция. По мере того, как вы добавляете
новые куски, система разрастается и ускоряется, как разрастается и ускоряется
снежный ком, катящийся с горы (рис. 29#3).
Нисходящая интеграция
При нисходящей интеграции класс на вершине иерархии пишется и интегрируется
первым. Вершина иерархии — это главное окно, управляющий цикл приложения,
объект, содержащий метод main() в программе на Java, функция WinMain() в про-
граммировании для Microsoft Windows или аналогичные. Для работы этого верхнего
класса пишутся заглушки. Затем, по мере интеграции классов сверху вниз, классы
заглушек заменяются реальными (рис. 29#5).
ГЛАВА 29 Интеграция 679
При нисходящей интеграции интерфейсы между классами нужно задать очень тща-
тельно. Самые трудные для отладки не те ошибки, что влияют на отдельные классы, а
те, что проявляются из#за незаметного взаимодействия между классами. Аккуратное
определение интерфейсов может уменьшить проблему. Спецификация интерфейсов
не относится к интеграции, однако проверка того, что интерфейсы были спроекти-
рованы правильно, является ее задачей.
В дополнение к преимуществам, получаемым от любого типа инкрементной инте-
грации, нисходящая интеграция позволяет относительно просто протестировать
управляющую логику программы. Все классы на вершине иерархии выполняются
большое количество раз, поэтому концептуальные проблемы и ошибки проекти-
рования проявляются достаточно быстро.
Другое преимущество нисходящей интеграции в том, что при тщательном плани-
ровании вы получите работающую систему на ранних стадиях проекта. Если часть,
реализующая пользовательский интерфейс, находится на вершине иерархии, вы
можете быстро получить работающую базовую версию интерфейса, а деталями
заняться потом. Моральное состояние пользователей и программистов выиграет
от раннего получения работающей версии.
Нисходящая инкрементная интеграция также позволяет начать кодирование до
того, как все детали низкоуровневого проектирования завершены. Когда все раз-
делы проекта проработаны достаточно подробно, можно начинать реализовывать
и интегрировать все классы, стоящие на более высоких уровнях иерархии, не
ожидая, когда будут расставлены все точки над «i».
Несмотря на все эти преимущества, чистый вариант нисходящей интеграции ча-
сто содержит недостатки, с которыми вы вряд ли захотите мириться. Нисходящая
интеграция в чистом виде оставляет на потом работу со сложными системными
интерфейсами. Если они содержат много дефектов или имеют проблемы с произ-
водительностью, вы, вероятно, хотели бы получить их задолго до конца проекта. Не
так редко встречается ситуация, когда низкоуровневая проблема всплывает на самый
верх системы, что приводит к высокоуровневым изменениям и снижает выгоду
от раннего начала работы по интеграции. Эту проблему можно минимизировать
посредством тщательного и раннего тестирования во время разработки и анализа
производительности классов, из которых состоят системные интерфейсы.
680 ЧАСТЬ VI Системные вопросы
Другая проблема чистой нисходящей интеграции в том, что при этом нужен це-
лый самосвал заглушек. Масса классов более низкого уровня еще не разработана, а
значит, на промежуточных этапах интеграции потребуются заглушки. Их проблема
в том, что, как тестовый код, они с большей вероятностью содержат ошибки, чем
тщательно спроектированный промышленный код. Ошибки в новых заглушках,
используемых в новом классе, сводят на нет цель инкрементной интеграции, со-
стоящую в том, чтобы ограничить источник ошибок одним новым классом.
Кроме того, нисходящую интеграцию практически невоз-
Перекрестная ссылка Нисходя-
можно реализовать в чистом виде. Если подходить к ней
щая интеграция не имеет ниче-
буквально, то надо начинать с вершины (назовем ее Уровнем
го общего, кроме названия, с
1), а затем интегрировать все классы следующего уровня
нисходящим проектированием
(о нем см. подраздел «Нисхо-
(Уровня 2). Когда вы полностью интегрируете классы Уров#
дящий и восходящий подходы к
ня 2, и не раньше, вы начинаете интегрировать классы Уров-
проектированию» раздела 5.4).
ня 3. Жесткость чистой нисходящей интеграции абсолютно
деспотична. Сложно представить кого#нибудь, кто стал бы
использовать нисходящую интеграцию в чистом виде. Большинство применяет
гибридный подход, такой как интеграция сверху вниз с разбиением на разделы.
И, наконец, вы не можете использовать нисходящую интеграцию, если набор клас-
сов не имеет вершины. Во многих интерактивных системах понятие «вершины»
субъективно. В одних системах вершиной является пользовательский интерфейс,
в других — функция main().
Хорошей альтернативой нисходящей интеграции в чистом виде может стать под-
ход с вертикальным секционированием (рис. 29#6). При этом систему реализуют
сверху вниз по частям, возможно, по очереди выделяя функциональные области
и переходя от одной к другой.
Восходящая интеграция
При восходящей интеграции вы пишете и интегрируете сначала классы, находя-
щиеся в низу иерархии. Добавление низкоуровневых классов по одному, а не всех
одновременно — вот что делает восходящую интеграцию инкрементной стратегией.
Сначала вы пишете тестовые драйверы для выполнения низкоуровневых классов,
а затем добавляете эти классы к тестовым драйверам, пристраивая их по мере
готовности. Добавляя класс более высокого уровня, вы заменяете классы драйве-
ров реальными. На рис. 29#7 показан порядок, в котором происходит интеграция
классов при восходящем подходе.
Рис. 29'7. При восходящей интеграции классы, находящиеся в низу иерархии, объе-
диняются в начале, а находящиеся на вершине иерархии — в конце
Сэндвич-интеграция
Проблемы с нисходящей и восходящей интеграциями в чистом виде привели к
тому, что некоторые эксперты стали рекомендовать сэндвич#подход (Myers, 1976).
Сначала вы объединяете высокоуровневые классы бизнес#объектов на вершине
иерархии. Затем добавляете классы, взаимодействующие с аппаратной частью, и
широко используемые вспомогательные классы в низу иерархии. Эти высоко# и
низкоуровневые классы — хлеб для сэндвича.
Напоследок вы оставляете классы среднего уровня — мясо, сыр и помидоры для
сэндвича. Если вы вегетарианец, они могут представлять собой тофу и проро-
щенные бобы, хотя автор сэндвич#интеграции ничего не сообщает на этот счет
— возможно, он не мог говорить с набитым ртом (рис. 29#9).
Риск-ориентированная интеграция
Риск#ориентированную интеграцию, которую также называют «интеграцией, начиная
с самых сложных частей» (hard part first integration), похожа на сэндвич#интеграцию
тем, что пытается избежать проблем, присущих нисходящей или восходящей ин-
теграциям в чистом виде. Кроме того, в ней также есть тенденция к объединению
классов верхнего и нижнего уровней в первую очередь, оставляя классы среднего
уровня напоследок. Однако суть в другом.
При риск#ориентированной интеграции (рис. 29#10) вы определяете степень
риска, связанную с каждым классом. Вы решаете, какие части системы будут са-
мыми трудными, и реализуете их первыми. Опыт показывает, что это относится к
интерфейсам верхнего уровня, поэтому они часто присутствуют в начале списка
рисков. Системные интерфейсы, обычно расположенные внизу, тоже представляют
опасность, поэтому они также находятся в числе первых в этом списке рисков.
Кроме того, вам может быть известно, что какие#то классы в середине иерархии
могут также создавать трудности. Возможно, это класс, реализующий сложный
для понимания алгоритм или к которому предъявляются повышенные требования
по производительности. Такие классы тоже могут быть обозначены как имеющие
повышенный риск, и их интеграция должна происходить на ранних стадиях.
Оставшаяся несложная часть кода может подождать. Какие#то из этих классов
позже могут оказаться сложнее, чем вы предполагали, но это неизбежно.
Функционально-ориентированная интеграция
Еще один поход — интеграция одной функции в каждый момент времени. Под
«функцией» понимается не нечто расплывчатое, а какое#нибудь поддающееся
определению свойство системы, в которой выполняется интеграция. Если вы пишете
текстовый процессор, то функцией может считаться отображение подчеркиваний,
или автоматическое форматирование документа, или еще что#либо подобное.
Когда интегрируемая функция превышает по размерам отдельный класс, то «еди-
ница приращения» инкрементной интеграции становится больше отдельного
класса. Это немного снижает преимущество инкрементного подхода в том плане,
что уменьшает вашу уверенность об источнике новых ошибок. Однако если вы
тщательно тестировали классы, реализующие эту функцию, перед интеграцией, то
это лишь небольшой недостаток. Вы можете использовать стратегии инкрементной
интеграции рекурсивно, сформировав сначала из небольших кусков отдельные
свойства, а затем инкрементно объединив их в систему.
Обычно процесс начинается с формирования скелета, поскольку он способен
поддерживать остальную функциональность. В интерактивной системе такой
изначальной опцией может стать система интерактивного меню. Вы можете
прикреплять остальную функциональность к той опции, которую интегрировали
первой (рис. 29#11).
Т-образная интеграция
Последний подход, который часто упоминается в связи с проблемами нисходя-
щей и восходящей методик, называется «Т#образной интеграцией». При таком
подходе выбирается некоторый вертикальный слой, который разрабатывается и
интегрируется раньше других. Этот слой должен проходить сквозь всю систему
от начала до конца и позволять выявлять основные проблемы в допущениях, сде-
ланных при проектировании системы. Реализовав этот вертикальный участок (и
устранив все связанные с этим проблемы), можно разрабатывать основную канву
системы (например, системное меню для настольного приложения). Этот подход
часто комбинируют с риск#ориентированной и функционально#ориентированной
интеграциями (рис. 29#12).
1
Термин «дымовой тест» появился в электронике. Разработчики на определенном этапе включают
испытываемое устройство в сеть и смотрят, не задымится ли оно. — Прим. перев.
ГЛАВА 29 Интеграция 687
оставаться после работы, чтобы запустить свои тесты. Поскольку в таких задержках
нет их вины, процесс сборки становится деморализующим.
Заканчивая сборку утром, вы получаете более надежный доступ к разработчикам,
со сборкой возникли проблемы. Во время рабочего дня программисты находятся
в офисе. Вечером они могут быть где угодно. Даже если разработчикам выданы
пейджеры, не всегда легко их найти.
Возможно, было бы круче начинать дымовой тест в конце дня и при возникнове-
нии проблем вызывать людей на работу посреди ночи, но это усложняет работу
команды, приводит к бесполезной трате времени — в результате же вы больше
потеряете, чем приобретете.
Создавайте сборку и проводите дымовой тест даже в экстремальных
условиях Когда сроки начинают поджимать, ежедневные сборки могут пока-
заться излишней роскошью. На самом деле все наоборот. В стрессовых условиях
дисциплина программистов ухудшается. Под давлением обстоятельств они при-
бегают к таким методам ускорения конструирования, которые не использовали
бы в менее критичных ситуациях. Они рецензируют и тестируют собственный
код менее тщательно, чем обычно. Код стремится к состоянию энтропии быстрее,
чем это происходит при меньшем стрессе.
На этом фоне ежедневные сборки ужесточают дисциплину и поддерживают на
плаву критические проекты.
Непрерывная интеграция
Некоторые авторы расценивают ежедневные сборки как аргумент в пользу вы-
полнения непрерывной интеграции (Beck, 2000). В большинстве публикаций под
«непрерывной» понимается «по крайней мере ежедневная» интеграция (Beck, 2000),
что мне кажется обоснованным. Но порой я встречаю людей, понимающих слово
«непрерывная» буквально: они стремятся выполнять интеграцию каждого изменения
с самой последней сборкой каждые пару часов. Думаю, для большинства проектов
действительно непрерывная интеграция выходит за рамки разумного.
ГЛАВА 29 Интеграция 691
Дополнительные ресурсы
Вопросам, обсуждаемым в этой главе, посвящены следующие http://cc2e.com/2999
публикации.
Интеграция
Lakos, John. Large%Scale C++ Software Design. Boston, MA: Addison#Wesley, 1996. Лей-
кос доказывает, что «физическое представление» системы — иерархия файлов,
каталогов и библиотек — заметно влияет на способность команды разработчиков
692 ЧАСТЬ VI Системные вопросы
Инкрементный подход
McConnell, Steve. Rapid Development. Redmond, WA: Microsoft Press, 1996. В главе 7
(Lifecycle Planning) «Планирование жизненного цикла» подробно рассмотрены
плюсы и минусы более гибких и менее гибких моделей жизненного цикла. В гла-
вах 20, 21, 35 и 36 обсуждаются конкретные модели жизненных циклов, поддер-
живающие инкрементный подход в разной степени. Глава 19 содержит описание
«проектирования с поддержкой изменений» (designing for change) — ключевой
методики, необходимой для поддержки интерактивной и инкрементной моделей
разработки.
Boehm, Barry W. «A Spiral Model of Software Development and Enhancement». Computer,
May 1988: 61–72. В этой статье Бом описывает свою «спиральную модель» разра-
ботки ПО. Он предлагает эту модель в качестве подхода к управлению рисками
в программном проекте, поэтому статья больше касается разработки в целом,
чем конкретно интеграции. Бом — один из выдающихся экспертов в области
мас#штабных вопросов разработки ПО, и доходчивость его объяснений отражает
глубину его понимания темы.
Gilb, Tom. Principles of Software Engineering Management. Wokingham, England:
Addison#Wesley, 1988. Главы 7 и 15 содержат исчерпывающее обсуждение эволю-
ционной поставки — одного из первых подходов к инкрементной разработке.
Beck, Kent. Extreme Programming Explained: Embrace Change. Reading, MA: Addison#Wesley,
2000. Эта книга содержит более современное, лаконичное и евангеличе#ское пред-
ставление большинства идей, приведены в книге Гилба. Лично я отдаю предпочте-
ние глубокому анализу Гилба, но некоторые читатели могут посчитать изложение
Бека более доступным или применимым непосредственно к тому типу проекта,
над которым они работают.
Ключевые моменты
Последовательность конструирования и интеграционный подход влияют на
порядок, в котором классы проектируются, кодируются и тестируются.
Порядок интеграции снижает затраты на тестирование и упрощает отладку.
Инкрементная интеграция имеет несколько вариантов, и, помимо совсем три-
виальных проектов, любой из них лучше, чем поэтапная интеграция.
ГЛАВА 29 Интеграция 693
Г Л А В А 3 0
Инструменты
программирования
Содержание
http://cc2e.com/3084
30.1. Инструменты для проектирования
30.2. Инструменты для работы с исходным кодом
30.3. Инструменты для работы с исполняемым кодом
30.4. Инструменты и среды
30.5. Создание собственного программного инструментария
30.6. Волшебная страна инструментальных средств
Связанные темы
Инструменты для управления версиями: раздел 28.2
Инструменты для отладки: раздел 23.5
Инструменты для облегчения тестирования: раздел 22.5
Современные инструменты сокращают время конструирования. Передовой набор
инструментов (и знание этих средств) позволяет повысить производительность
более, чем на 50% (Jones, 2000; Boehm et al., 2000). Программный инструмента#
рий также позволяет уменьшить объем однообразной, монотонной работы.
Собака, может, и лучший друг человека, но хорошие инструменты — луч#
шие друзья программиста. Как уже давно выяснил Барри Бом, 20% ин#
струментария используются приблизительно в 80% случаев (1987b). Если
вы не знакомы с каким#то из наиболее полезных инструментов, вы упускаете шанс
облегчить себе жизнь.
В этой главе мы поговорим об инструментах для конструирования (об инстру#
ментах, применяемых в процессе определения требований, управления, а также
на протяжении всего цикла разработки, см. раздел «Дополнительные ресурсы» в
конце главы). Темой нашего разговора будут не конкретные средства, а их разно#
видности. Некоторые инструменты настолько широко распространены, что упо#
минаются по имени, но номера версий, имена продуктов и компаний меняются
так быстро, что информация о большинстве из них оказалась бы устаревшей до
того, как высохли бы чернила на этих страницах.
ГЛАВА 30 Инструменты программирования 695
Редактирование
Эта группа инструментов служит для редактирования исходного кода.
рошо, потому что у вас должно быть как можно меньше препятствий для созда#
ния лругих имен классов, методов и констант. Для обработки изменений строк в
нескольких файлах служат такие инструменты, как Perl, AWK и sed.
Шаблоны
Шаблоны упрощают задачи ввода данных с клавиатуры. Допустим, вы хотите до#
бавлять стандартный пролог комментариев перед началом ваших методов. Вы
можете создать скелет такого пролога с использованием правильного синтакси#
698 ЧАСТЬ VI Системные вопросы
Трансляторы кода
Некоторые инструменты переводят код с одного языка программирования на
другой. Транслятор позволяет перенести большой объем кода в другую среду. Учтите
700 ЧАСТЬ VI Системные вопросы
однако, что, если вы изначально имеете плохой код, транслятор просто переве#
дет этот плохой код на другой язык.
Управление версиями
Справиться с быстро растущим количеством версий ПО по#
Перекрестная ссылка О таких
инструментах см. подраздел зволяют инструменты управления версиями, которые пре#
«Изменения в коде программ- доставляют следующие возможности:
ного обеспечения» раздела 28.2. управление исходным кодом;
управление зависимостями наподобие того, что делает
утилита make в операционных системах UNIX;
управление версиями проектной документации;
установка соответствия между элементами проекта, такими как требования, код
и тестовые данные, чтобы в случае изменения требований вы могли опреде#
лить, какой код и какие тесты будут затронуты.
Словари данных
Так называются базы данных, которые описывают важную для проекта информа#
цию. Во многих случаях словарь связан преимущественно со схемами баз данных.
В больших проектах такой словарь служит для отслеживания сотен или тысяч оп#
ределений классов. В больших групповых проектах он позволяет избежать конф#
ликтов имен. Конфликт может быть просто синтаксическим (когда одно и то же
имя используется дважды) и более запутанным, при котором различные имена слу#
жат для обозначения одного и того же понятия или одно и то же имя обозначает
немного разные вещи. Для каждого элемента данных (таблицы базы данных или
класса) словарь содержит имя и описание. Кроме того, в нем могут содержаться
пояснения относительно применения элемента.
Создание кода
Инструменты, описанные в этом разделе, оказывают помощь при создании про#
граммы.
Компиляторы и компоновщики
Компиляторы преобразуют исходный код в исполняемый. Большинство программ
предназначено для компиляции, хотя еще встречается и интерпретируемый код.
Типичный компоновщик связывает один или несколько объектных файлов, ко#
торые компилятор сгенерировал из ваших исходников, со стандартным кодом,
необходимым для создания исполняемых программ. Компоновщики, как прави#
ло, могут объединять файлы, созданные на разных языках, позволяя вам выбирать
ГЛАВА 30 Инструменты программирования 701
Библиотеки кода
Хороший способ быстро написать высококачественный код состоит в том, что#
бы не писать его полностью, а найти версию с открытым исходным кодом. Вы
можете найти высококачественные библиотеки для:
контейнерных классов;
сервисов транзакций по кредитным картам (службы e#commerce);
702 ЧАСТЬ VI Системные вопросы
Программы установки
Средства, предназначенные для создания программ установки, обычно поддержи#
вают работу с дискетами, CD или DVD или позволяют создавать программы уста#
новки через Web. Они проверяют, существуют ли на целевой машине общие биб#
лиотечные файлы, контролируют номера версий и т. д.
ГЛАВА 30 Инструменты программирования 703
Препроцессоры
Препроцессоры и препроцессорные макросы помогают при
Перекрестная ссылка О добав-
отладке, поскольку упрощают процесс переключения меж# лении в код и удалении из кода
ду отладочной и промышленной версиями кода. Если во вре# отладочных средств см. подраз-
мя разработки вы хотите проверять фрагментацию памяти дел «Запланируйте удаление от-
в начале каждого метода, то можете разместить там макро# ладочных средств» раздела 8.6.
сы. В промышленной версии вы, возможно, не захотите ос#
тавлять эти проверки — тогда можно переопределить эти макросы так, что они
вообще не будут генерировать кода. По похожим причинам макросы препроцес#
сора подходят для написания кода, предназначенного для компиляции на несколь#
ких различных платформах — например, и в Windows, и в Linux.
Используя язык с примитивными управляющими конструкциями, например ас#
семблер, вы можете написать препроцессор управляющей логики, чтобы эмули#
ровать в языке такие структурированные конструкции, как if%then%else и циклы while.
Если в вашем языке нет препроцессора, вы можете задей#
http://cc2e.com/3091
ствовать его автономный вариант как составляющую про#
цесса сборки. Один из препроцессоров — M4 — доступен
по адресу www.gnu.org/software/m4/.
Отладка
Следующие средства оказывают помощь при отладке:
Перекрестная ссылка Об этих
предупреждающие сообщения компилятора; инструментах см. раздел 23.5.
тестовые леса;
инструменты для сравнения (позволяющие сравнивать различные версии ис#
ходных файлов);
средства профилирования;
мониторы трассировки;
интерактивные отладчики — как программные, так и аппаратные.
Средства тестирования, обсуждаемые далее, имеют отношение и к отладке.
Тестирование
Следующие инструменты помогут вам провести эффектив#
Перекрестная ссылка Об этих
ное тестирование: инструментах см. раздел 22.5.
автоматизированные системы тестирования, такие как
JUnit, NUnit, CppUnit и пр.;
автоматизированные генераторы тестов;
утилиты для записи и воспроизведения тестовых примеров;
мониторы покрытия (анализаторы логики и средства профилирования);
символьные отладчики;
программы для стрессового тестирования (заполняющие оперативную память,
перемешивающие содержимое памяти, генерирующие ошибки при выбороч#
ных обращениях к памяти, проверяющие доступ к памяти);
средства сравнения (сравнивающие файлы данных, результаты работы програм#
мы и экранные изображения);
704 ЧАСТЬ VI Системные вопросы
тестовые леса;
инструменты для внесения дефектов;
ПО для отслеживания дефектов.
Оптимизация кода
Эти инструменты помогут вам при тонкой настройке кода.
Средства профилирования
Профилировщик следит за работой кода и сообщает, сколько раз выполнялся каж#
дый оператор или сколько времени программа затратила на выполнение отдель#
ных выражений или исполняемых ветвей. Профилирование кода во время выпол#
нения действует, как доктор, который приставляет стетоскоп к вашей груди и про#
сит вас покашлять. Профилировщик дает вам понимание того, как работает ваша
программа, где находятся узкие места и где нужно настроить код.
Сценарии
Сценарий — это инструмент автоматизации повторяющихся рутинных операций.
В некоторых системах сценариями называются командные файлы или макросы.
Сценарии могут быть простыми и сложными, а наиболее полезные из них очень
легко написать. Например, я веду дневник и для защиты конфиденциальной ин#
формации всегда держу его в зашифрованном виде, кроме тех моментов, когда
делаю в нем записи. Чтобы быть уверенным, что я всегда шифрую и дешифрую
его надлежащим образом, я использую сценарий, который дешифрует мой днев#
ник, запускает текстовый процессор, а затем шифрует дневник снова. Сценарий
может выглядеть так:
crypto c:\word\journal.* %1 /d /Es /s
word c:\word\journal.doc
crypto c:\word\journal.* %1 /Es /s
Дополнительные ресурсы
Дополнительную информацию о программном инструментарии содержат следу#
ющие источники.
http://cc2e.com/3098
www.sdmagazine.com/jolts. Web#сайт, посвященный ежегодной
премии Jolt Productivity журнала «Software Development Maga#
zine», — хороший источник информации о лучших на се#
http://cc2e.com/3005
годняшний день инструментах.
Hunt, Andrew and David Thomas. The Pragmatic Programmer.
Boston, MA: Addison#Wesley, 2000. Раздел 3 этой книги содержит всестороннее
исследование программного инструментария, включая редакторы, кодогенераторы,
отладчики, системы управления версиями и другие аналогичные инструменты.
Vaughn#Nichols, Steven. «Building Better Software with Better
Tools», IEEE Computer, September 2003, pp. 12–14. В этой статье
http://cc2e.com/3012 приводится обзор инициатив в области разработки инст#
рументальных средств, выдвинутых IBM, Microsoft Research
и Sun Research.
Glass, Robert L. Software Conflict: Essays on the Art and Science of Software Engineering.
Englewood Cliffs, NJ: Yourdon Press, 1991. В главе «Recommended: A Minimum Standard
Software Toolset» в противовес мнению о том, что чем больше инструментов, тем
лучше, автор ратует за определение минимального набора инструментов, кото#
рый должен быть доступен каждому разработчику и предлагаться в виде старто#
вого комплекта.
Jones, Capers. Estimating Software Costs. New York, NY: McGraw#Hill, 1998.
Boehm, Barry, et al. Software Cost Estimation with Cocomo II. Reading, MA: Addison#Wesley,
2000. Книги Джонса и Бома содержат разделы, посвященные влиянию инструмен#
тальных средств на производительность.
ГЛАВА 30 Инструменты программирования 709
Ключевые моменты
Хороший инструментарий может значительно облегчить вам жизнь.
Можно легко приобрести инструменты для редактирования, анализа качества
кода, рефакторинга, управления версиями, отладки, тестирования и настрой#
ки кода.
Вы можете создать множество инструментов специального назначения.
Хорошие инструменты могут упростить наиболее утомительные аспекты раз#
работки ПО, но они не могут исключить необходимость программирования,
хотя и способствуют эволюции того понятия, которое мы вкладываем в слово
«программирование».
ГЛАВА 30 Инструменты программирования 711
Часть VII
МАСТЕРСТВО
ПРОГРАММИРОВАНИЯ
Г Л А В А 3 1
Форматирование и стиль
Содержание
http://cc2e.com/3187
31.1. Основные принципы форматирования
31.2. Способы форматирования
31.3. Стили форматирования
31.4. Форматирование управляющих структур
31.5. Форматирование отдельных операторов
31.6. Форматирование комментариев
31.7. Форматирование методов
31.8. Форматирование классов
Связанные темы
Самодокументируемый код: глава 32
Инструменты для форматирования кода: «Редактирование» в разделе 30.2
В этой главе рассматривается эстетический аспект программирования — форма#
тирование исходного кода программы. Зрительное и интеллектуальное наслаж#
дение, получаемое от хорошо отформатированного кода, — удовольствие, кото#
рое способны оценить лишь немногие непрограммисты. Но программисты, гор#
дящиеся своей работой, получают огромное эстетическое удовлетворение от про#
цесса шлифовки визуальной структуры кода.
Методики, описанные в этой главе, не влияют на скорость выполнения, объем
памяти и другие внешние аспекты программы. Но от них зависит, насколько лег#
ко вы сможете понять, пересмотреть и исправить этот код спустя несколько ме#
сяцев после его создания. От них также зависит, насколько легко другие смогут
его прочесть, понять и изменить в ваше отсутствие.
Эта глава полна мелких подробностей, которые обычно имеют в виду, говоря о
«внимании к деталям». В течение жизни проекта внимание к таким деталям влия#
ет на качество и итоговое удобство сопровождения создаваемого кода. Они слиш#
ком интегрированы в процесс кодирования, чтобы их можно было эффективно
изменить потом. Если вы вообще собираетесь их учитывать, учтите их при пер#
ГЛАВА 31 Форматирование и стиль 713
Листинг 31-1.
Пример № 1 форматирования кода (Java)
/* Используем способ сортировки методом вставок для сортировки массива «data» в
возрастающем порядке. Этот метод предполагает, что [ firstElement ] не является
первым элементом данных и элемент data[ firstElement1 ] достижим. */ public void
InsertionSort( int[] data, int firstElement, int lastElement ) { /* Заменяем
элемент, расположенный на нижней границе интервала, элементом, который гаранти
рованно будет первым в сортированном списке. */ int lowerBoundary = data
[ firstElement1 ]; data[ firstElement1 ] = SORT_MIN; /* Элементы в позициях от
firstElement до sortBoundary1 всегда сортированы. При каждом проходе цикла sort
Boundary увеличивается, и элемент, соответствующий новому sortBoundary, возможно,
будет неправильно отсортирован, поэтому он вставляется в надлежащую позицию массива
гдето между firstElement и sortBoundary. */ for ( int sortBoundary =
firstElement+1; sortBoundary <= lastElement; sortBoundary++ ) { int insertVal =
data[ sortBoundary ]; int insertPos = sortBoundary; while ( insertVal < data[
insertPos1 ] ) { data[ insertPos ] = data[ insertPos1 ]; insertPos = insertPos1;
} data[ insertPos ] = insertVal; } /* Возвращаем исходное значение элементу,
расположенному на нижней границе интервала */ data[ firstElement1 ] = lowerBoundary; }
Листинг 31-2.
Пример № 2 форматирования кода (Java)
/* Используем способ сортировки методом вставок для сортировки массива «data» в
возрастающем порядке. Этот метод предполагает, что [ firstElement ] не является
первым элементом данных и элемент data[ firstElement1 ] достижим. */
public void InsertionSort( int[] data, int firstElement, int lastElement ) {
/* Заменяем элемент, расположенный на нижней границе интервала, элементом,
714 ЧАСТЬ VII Мастерство программирования
Это тот же код, что и в листинге 31#1. Хотя большинство согласится, что такое
форматирование кода гораздо лучше первого примера, но код все еще не слиш#
ком читабелен. Текст еще расположен слишком кучно и не содержит подсказок о
логической организации метода. Такое форматирование соответствует 0 на шкале
вариантов форматирования. Первый пример был несколько надуман, но второй не
так уж редко встречается в жизни. Я видел программы, состоящие из нескольких
тысяч строк кода, форматированные столь же плохо, как и здесь. А при отсутствии
документации и плохих именах переменных общая читабельность была еще хуже,
чем в этом примере. Этот код отформатирован для компьютера, и нет никаких
свидетельств, что автор думал о людях, которые будут читать его код. Листинг 31#3
содержит усовершенствованный вариант.
Человек, читающий этот код, будет склонен интерпретировать это выражение так,
что переменной x присваивается значение (3+4) * (2+7), т. е. 63. Компьютер про#
игнорирует пробелы и подчинится правилам приоритета, интерпретируя это
выражение, как 3 + (4*2) + 7 или 18. Идея в том, что хорошая схема форматиро#
вания приведет в соответствие визуальную и логическую структуру программы,
т. е. расскажет одну и ту же историю и человеку, и компьютеру.
Я ответил: «Ты, наверное, имел в виду, что ты ратовал за применение первого сти#
ля, а Тони — второго, не так ли? Тони приводил доводы в пользу второго стиля, а
не первого».
Хенк ответил: «Забавно. В последнем проекте, над которым Тони и я работали
вместе, я предпочитал использовать стиль №2, а Тони — №1. Весь проект мы спо#
рили о том, какой стиль лучше. Полагаю, мы уговорили друг друга предпочесть
противоположный стиль!»
Этот случай, а также исследования, процитированные выше, позволяют
предположить, что структура помогает экспертам воспринимать, осмыс#
ливать и запоминать важные свойства программ. Опытные программис#
ты часто упорно цепляются за собственный стиль, даже если он очень сильно
отличается от стиля, применяемого другими экспертами. Но практический результат
показывает, что детали конкретного способа структурирования программы гораздо
менее важны, чем сам факт единообразного структурирования программы.
Неотображаемые символы
Используйтенеотображаемыесимволыдляулучшениячитаемости. Неотображаемые
символы, к которым относятся пробелы, знаки табуляции, переводы строк и пус#
тые строки, — это основное средство для демонстрации структуры программы.
Вам вряд ли придет в голову писать книгу без пробелов между
Перекрестная ссылка Некото-
рые исследователи проводят словами, разбиения на абзацы и деления на главы. Может,
аналогии между структурой такую книгу и можно прочитать от начала до конца, но прак#
книги и структурой программы тически невозможно просматривать ее в поисках какой#то
(см. подраздел «Книжная пара- мысли или важного эпизода. Еще хуже, что такой формат
дигма документирования про-
книги не позволит показать читателю, как автор намеревал#
грамм» раздела 32.5).
ся организовать информацию. Структура, предлагаемая ав#
тором, дает подсказку о логической организации темы.
Разбиение книги на главы, абзацы и предложения показывает читателю, как сле#
дует мысленно организовывать тему. Если эта организация неочевидна, читате#
лю приходится самому ее домысливать, что налагает на него более тяжкое бремя
и увеличивает вероятность никогда не узнать, как на самом деле организована
данная тема.
Информация, содержащаяся в программе, сосредоточена еще плотней, чем инфор#
мация в большинстве книг. Если страницу книги можно прочесть и понять за 1
или 2 минуты, то большинство программистов не могут читать и понимать лис#
тинг программы со скоростью, даже приблизительно сравнимой с этой. Программа
должна давать гораздо больше подсказок о своей организации, чем книга.
Группировка Еще один способ применения неотображаемых символов — груп#
пировка взаимосвязанных выражений
В литературе мысли группируются в абзацы. Хорошо написанный абзац содер#
жит предложения, относящиеся только к определенной идее. Он не должен со#
держать посторонних предложений. Точно так же абзац кода должен содержать
только взаимосвязанные операторы, выполняющие одно задание.
Пустые строки Кроме необходимости группировать взаимосвязанные опера#
торы, очень важно отделять несвязанные выражения друг от друга. Начало ново#
го абзаца в книге обозначается отступом или пустой строкой. Начало нового аб#
заца в коде нужно указывать с помощью пустой строки.
Пустые строки позволяют продемонстрировать организацию программы. Вы
можете использовать их для деления групп взаимосвязанных операторов на аб#
зацы, отделения методов друг от друга и выделения комментариев.
Хотя эту статистику тяжело применить на практике, но одно исследова#
ние показало, что оптимальное число пустых строк в программе состав#
ляет от 8% до 16%. Если оно больше 16%, то время, затрачиваемое на от#
ладку, заметно увеличивается (Gorla, Benander and Benander, 1990).
ГЛАВА 31 Форматирование и стиль 721
Скобки
Используйте скобки чаще, чем вам это кажется необходимым. Применяйте скоб#
ки для разъяснения выражений, состоящих из двух и более членов. Возможно, в
скобках нет нужды, но они добавляют ясности и ничего вам не стоят. Например,
скажите, как вычисляются следующие выражения?
Вариант на C++: 12 + 4% 3 * 7 / 8
Вариант на Microsoft Visual Basic: 12 + 4 mod 3 * 7 / 8
Пришлось ли вам задуматься о том, как эти выражения вычисляются, вот в чем
вопрос? Можете ли вы быть уверенными в своем ответе без обращения к спра#
вочной информации? Даже опытные программисты не отвечают с полной уверен#
ностью, и именно поэтому следует использовать скобки, если есть хоть малейшее
сомнение в том, как вычисляется выражение.
явные блоки;
эмуляция явных блоков;
использование пар begin%end (скобок) для обозначения границ блока;
форматирование в конце строки.
Явные блоки
Большинство споров по поводу форматирования возникает из#за несовершенства
большинства популярных языков программирования. Хорошо спроектированный
язык имеет явную структуру блоков, которая приводит к естественному стилю
отступов. Так, в Visual Basic у каждой управляющей структуры есть свой термина#
тор, и вы не сможете ее использовать без этого терминатора. Код разбивается на
блоки естественным образом. Несколько примеров на Visual Basic приведено в
листингах 31#6, 31#7 и 31#8:
Но для того, чтобы в новом стиле begin и end трактовались как составные части
структуры блока, а не управляющего выражения, надо поместить begin в начало
блока (а не в конец управляющего выражения), а end — в конец блока (а не в ка#
честве терминатора управляющего выражения). Говоря абстрактно, вам нужно
сделать нечто, подобное структуре, изображенной в листинге 31#16:
Листинг 31-16. Абстрактный пример использования begin и end в качестве границ блока
A XXXXXXXXXXXXXXXXXXXX
{{XXXXXXXXXXXXXXXX
B XXXXXXXXXXXXXXXXX
C XXXXXXXXXXXXXXXXX
}
}X
Листинг 31-17. Пример применения begin и end в качестве границ блока if (C++)
if ( pixelColor == Color_Red )
{
statement1;
statement2;
...
}
Листинг 31-18. Пример применения begin и end в качестве границ блока while (C++)
while ( pixelColor == Color_Red )
{
statement1;
statement2;
...
}
726 ЧАСТЬ VII Мастерство программирования
Листинг 31-21. Пример форматирования в конце строки в блоке while (Visual Basic)
While ( pixelColor = Color_Red )
statement1;
statement2;
...
Wend
> Else
markdown = 0.05
End If
В этом случае ключевые слова Then, Else и End If выровнены, и код, следующий за ними,
также выровнен. Визуальный эффект соответствует ясной логической структуре.
Критически взглянув на приводимый ранее пример case#оператора, вы, вероят#
но, сможете указать на недостаток данного стиля. По мере усложнения условного
выражения этот стиль начнет давать бесполезные или обманчивые подсказки о
логической структуре кода. В листинге 31#23 приведен пример ухудшения этого
стиля при его применении в более сложном условном выражении:
>}
Хотя такой подход выглядит хорошо, он нарушает Основную теорему формати#
рования, так как не показывает логическую структуру кода. При таком располо#
жении begin и end не являются частью управляющей структуры, но в то же время,
они не являются и частью блока выражений, расположенного далее.
Листинг 31#25 демонстрирует абстрактное представление этого подхода:
Другие соглашения
Хотя отступы в блоках являются главным вопросом форматирования управляю#
щих структур, вы также можете столкнуться с другими проблемами, поэтому да#
лее я привожу еще несколько советов.
Используйте пустые строки между абзацами Некоторые блоки не разгра#
ничиваются с помощью пар begin%end. Логический блок — группа подходящих друг
к другу операторов — должны рассматриваться так же, как абзацы в обычной книге.
Отделяйте их друг от друга с помощью пустых строк. Листинг 31#29 представля#
ет пример абзацев, которые следует разделить:
ГЛАВА 31 Форматирование и стиль 731
Этот код выглядит неплохо, но пустые строки позволят улуч# Перекрестная ссылка Если вы
шить его с двух точек зрения. Во#первых, если у вас есть груп# придерживаетесь Процесса про-
па операторов, не требующих выполнения в определенном граммирования с псевдокодом,
порядке, заманчиво объединить их именно так. Вам не надо ваш код автоматически разби-
вается на абзацы (см. главу 9).
усовершенствовать порядок выражений для помощи компь#
ютеру, но читатели#люди оценят дополнительные подсказ#
ки о том, какие операторы выполнять в определенном порядке, а какие — просто
расположены рядом друг с другом. Дисциплина добавления пустых строк в коде
программы заставляет вас тщательней обдумывать вопрос, какие операторы на самом
деле подходят друг другу. Исправленный фрагмент кода, представленный в листинге
31#30, показывает, как организовать данный набор операторов.
Листинг 31-31. Пример вариантов стиля для блоков из одного оператора (Java)
Стиль 1
>if ( expression )
одиноператор;
Стиль 2а
>if ( expression ) {
одиноператор;
}
Стиль 2б
>if ( expression )
{
одиноператор;
}
Стиль 3
errorCode = FileError_Success;
int fileIndex = 0;
while ( fileIndex < numFilesToPurge ) {
DataFile fileToPurge;
if ( !FindFile( fileList[ fileIndex ], fileToPurge ) ) {
errorCode = FileError_NotFound;
Здесь используется goto.
> goto END_PROC;
}
if ( !OpenFile( fileToPurge ) ) {
errorCode = FileError_NotOpen;
Здесь используется goto.
if ( !OverwriteFile( fileToPurge ) ) {
errorCode = FileError_CantOverwrite;
Здесь используется goto.
if ( !Erase( fileToPurge ) ) {
errorCode = FileError_CantErase;
Здесь используется goto.
Здесь находится метка goto. Заглавные буквы и специальное форматирование служат для того,
чтобы метку было сложно не заметить.
>END_PROC:
DeletePurgeFileList( fileList, numFilesToPurge );
}
ГЛАВА 31 Форматирование и стиль 735
Если вы добавите вариант с более длинным, чем существующие, именем, вам при#
дется сдвигать все варианты и код, к ним относящийся. Изначально большие от#
ступы затрудняют размещение какой#то дополнительной логики, как показано в
варианте WhiteAndBlue. Решением этой проблемы будет переход к стандартному виду
отступов. Так, если в циклах вы делаете отступ величиной в три пробела, исполь#
зуйте в вариантах case такое же число пробелов, как показано в листинге 31#36:
case BallColor_FluorescentGreen:
Spike();
break;
case BallColor_White:
KnockCoverOff();
break;
case BallColor_WhiteAndBlue:
if ( mainColor = BallColor_White ) {
KnockCoverOff();
}
else if ( mainColor = BallColor_Blue ) {
RollOut();
}
break;
default:
FatalError( “Unrecognized kind of ball.” );
break;
}
Это пример ситуации, в которой многие могут предпочесть внешний вид перво#
го варианта. Однако с точки зрения размещения длинных строк, единообразия и
удобства сопровождения второй подход имеет гораздо больше преимуществ.
Если у вас есть оператор case, все варианты которого выглядят абсолютно одина#
ково, а все действия называются кратко, можно рассмотреть возможность разме#
щения и варианта, и действия на одной и той же строке. Однако в большинстве
случаев вы впоследствии пожалеете об этом. Это форматирование изначально
неудобно и портится при модификации. Кроме того, тяжело поддерживать оди#
наковую структуру всех вариантов в случае удлинения кратких имен действий.
Длина выражения
Общее и в какой#то мере устаревшее правило гласит, что
Перекрестная ссылка О доку-
ментировании отдельных выра- длина строки выражения не должна превышать 80 симво#
жений см. подраздел «Коммен- лов, так как строки длиннее 80 символов:
тирование отдельных строк» тяжело читать;
раздела 32.5.
препятствуют созданию глубокой вложенности;
часто не помещаются на стандартный лист бумаги, осо#
бенно если печатается по 2 страницы кода на каждой физической странице
распечатки.
С появлением больших экранов, узких шрифтов и альбомной ориентации бума#
ги 80#символьное ограничение выглядит необоснованным. Одна строка в 90 сим#
волов часто удобнее для чтения, чем другая, разбитая на две только с целью избе#
жать выхода за 80#символьную границу. При современном уровне технологий,
ГЛАВА 31 Форматирование и стиль 737
не легче читать, чем приводимое ранее сжатое выражение while. Добавляйте про#
белы вокруг каждого индекса массива для упрощения их чтения. После примене#
ния этого правила выражение будет выглядеть так:
grossRate[ census[ groupId ].gender, census[ groupId ].ageGroup ]
Какой из них было легче найти? Вопрос вполне закономерный, поскольку поря#
док аргументов имеет большое значение во всех основных процедурных языках.
Довольно часто на одной половине экрана приходится располагать определение
метода, на другой — его вызов и сравнивать каждый формальный параметр с со#
ответствующим фактическим параметром.
738 ЧАСТЬ VII Мастерство программирования
SetFontAttributes(
faceName[ fontId ],
size[ fontId ],
bold[ fontId ],
italic[ fontId ],
syntheticAttribute[ fontId ].underline,
syntheticAttribute[ fontId ].strikeout
);
Ясно, что такой подход требует много места. Однако если аргументами функции
являются длинные названия полей объектов или имена указателей, например, такие,
как два последних в приведенном примере, размещение одного аргумента в строке
существенно улучшает читаемость. Знаки ); в конце блока делают заметным его
окончание. Вам также не придется переформатировать код при добавлении па#
раметра — вы просто добавите новую строку.
На практике только небольшая часть методов нуждается в разбиении на строки.
Остальные можно располагать на одной строке. Любой из трех вариантов фор#
ГЛАВА 31 Форматирование и стиль 741
С высоты 10#летнего опыта могу сказать, что, хотя этот стиль может выглядеть
привлекательно, он превращается в настоящую головную боль, когда приходится
поддерживать выравнивание знаков равенства при изменении имен переменных
и прогоне кода через утилиты, заменяющие пробелы знаками табуляции, а знаки
табуляции — пробелами. Кроме того, его тяжело сопровождать при перемещении
строк между частями программы, в которых применяются разные размеры отступов.
Для обеспечения единообразия с другими принципами отступов, а также учиты#
вая вопросы удобства сопровождения, обращайтесь с группами выражений, со#
держащими операции присваивания, так же, как вы бы обращались с любыми
другими выражениями (листинг 31#47):
Если вы все еще не совсем уверены в том, что побочные эффекты надо выносить
в отдельные строки, попробуйте понять, что делает функция, приводимая в лис#
тинге 31#50:
Это, конечно, крайний случай, однако он не так уж далек от гораздо более рас#
пространенного стиля, показанного в листинге 31#53:
Недостаток такого подхода в том, что в этом случае звездочка может казаться частью
имени переменной, а это не соответствует действительности. Переменную мож#
но применять как со звездочкой, так и без нее.
Наилучший вариант — объявление и использование типа#указателя (листинг 31#57):
GetTransactionType( transactionType )
GetTransactionAmount( transactionAmount )
Else
If transactionType = Transaction_CustomerReturn Then
Else
If transactionType = Transaction_CustomerReturn Then
Логическая структура кода в листинге 31#59 более прозрачна. В ходе одного ис#
следования эффективности комментариев выяснилось, что их применение не
всегда является абсолютным достоинством. Автор объяснил это тем, что они «на#
рушают процесс визуального просмотра программы» (Shneiderman, 1980). Из этих
примеров становится понятно, что стиль комментариев очень сильно влияет на
их «разрушающую» способность.
Отделяйте каждый комментарий хотя бы одной пустой строкой Наибо#
лее эффективный способ беглого просмотра программы — прочитать коммента#
рии, не читая при этом код. Выделение комментариев с помощью пустых строк по#
могает читателю просматривать код. В листинге 31#60 приведен пример:
// комментарий 1
CodeStatementTwo;
CodeStatementThree;
Некоторые добавляют пустую строку и до, и после комментария. Две пустых строки
занимают больше экранного пространства, но кто#то считает, что так код выгля#
дит лучше, чем с одной строкой. Пример таких комментариев приведен в листинге
31#61:
750 ЧАСТЬ VII Мастерство программирования
Листинг 31-61. Пример выделения комментария с помощью двух пустых строк (Java)
// комментарий 0
CodeStatementZero;
CodeStatementOne;
// комментарий 1
CodeStatementTwo;
CodeStatementThree;
...
void InsertionSort( SortArray data,
int firstElement,
int lastElement )
Начало нового класса отмечается несколькими пустыми строками и именем этого класса.
> //______________________________________________________________________
// МАТЕМАТИЧЕСКИЕ ФУНКЦИИ
//
// Этот класс содержит математические функции программы.
//______________________________________________________________________
//**********************************************************************
// Ищем арифметический максимум аргументов arg1 и arg2.
//**********************************************************************
int Math::Max( int arg1, int arg2 ) {
//**********************************************************************
if ( arg1 > arg2 ) {
return arg1;
}
else {
return arg2;
}
}
//**********************************************************************
// Ищем арифметический минимум аргументов arg1 и arg2.
//**********************************************************************
int Math::Min( int arg1, int arg2 ) {
//**********************************************************************
if ( arg1 < arg2 ) {
return arg1;
}
else {
return arg2;
}
}
ГЛАВА 31 Форматирование и стиль 755
Легковесность этой строки по сравнению со строкой звездочек визуально подчеркивает тот факт,
что метод подчиняется классу.
>//______________________________________________________________________
// Ищем арифметический максимум аргументов arg1 и arg2.
//______________________________________________________________________
int Math::Max( int arg1, int arg2 ) {
if ( arg1 > arg2 ) {
return arg1;
}
else {
return arg2;
}
}
//______________________________________________________________________
// Ищем арифметический минимум аргументов arg1 и arg2.
//______________________________________________________________________
int Math::Min( int arg1, int arg2 ) {
if ( arg1 < arg2 ) {
return arg1;
}
else {
return arg2;
}
}
если только у вас нет уважительных причин сделать иначе (например, объединить
нескольких небольших классов, составляющих единый шаблон). Один класс, од#
нако, может содержать группы методов, и эти группы можно выделять с помощью
технологий, продемонстрированных выше.
Листинг 31-69. Пример применения пустых строк между методами (Visual Basic)
‘ ищем арифметический максимум аргументов arg1 и arg2
Function Max( arg1 As Integer, arg2 As Integer ) As Integer
If ( arg1 > arg2 ) Then
Max = arg1
Else
Max = arg2
End If
End Function
Пустые строки набирать легче любых других сепараторов, а выглядят они не хуже.
Этот пример содержит три пустых строки, и поэтому разделение между метода#
ми выглядит заметней, чем пустые строки внутри каждого метода.
Упорядочивайте методы по алфавиту Альтернативой группировке взаимо#
связанных функций может служить их размещение в алфавитном порядке. Если
вы не можете разбить программу на классы или ваш редактор не позволяет легко
находить функции, алфавитный подход может ускорить поиск.
Аккуратно упорядочивайте исходный файл на C++ Далее приведена типич#
ная организация файла с исходным кодом на C++:
1. комментарий с описанием файла;
2. файлы, включаемые директивой #include;
3. определения констант, относящиеся к нескольким классам (если файл содер#
жит несколько классов);
4. перечисления, относящиеся к нескольким классам (если файл содержит несколь#
ко классов);
5. определения макросов;
6. определения типов, относящиеся к нескольким классам (если файл содержит
несколько классов);
7. импортируемые глобальные переменные и функции;
8. экспортируемые глобальные переменные и функции;
9. переменные и функции, видимые только в этом файле;
10. классы, вместе с определениями констант, перечислений и типов, относящи#
мися к конкретному классу.
Дополнительные ресурсы
Большинство учебников по программированию упоминает
http://cc2e.com/3101
о форматировании и стиле вскользь, углубленное обсужде#
ние стиля программирования встречается редко, а пробле#
мы форматирования прорабатываются еще реже. Вопросам форматирования и
стиля посвящены следующие публикации.
Kernighan, Brian W. and Rob Pike. The Practice of Programming. Reading, MA: Addison#
Wesley, 1999. В главе 1 обсуждается стиль программирования на языках C и C++.
Vermeulen, Allan, et al. The Elements of Java Style. Cambridge University Press, 2000.
Misfeldt, Trevor, Greg Bumgardner, and Andrew Gray. The Elements of C++ Style. Cam#
bridge University Press, 2004.
Kernighan, Brian W., and P. J. Plauger. The Elements of Programming Style, 2d ed. New
York, NY: McGraw#Hill, 1978. Этот классический труд по стилю программирования
— первый в этом жанре.
ГЛАВА 31 Форматирование и стиль 759
Ключевые моменты
Главная цель визуального форматирования — это подчеркивание логической
структуры кода. В критерии оценки достижения этой цели входят аккуратность,
единообразие, удобство чтения и сопровождения кода.
Критерий хорошего внешнего вида имеет вторичное, далеко не основное зна#
чение. Однако если другие критерии соблюдены, а лежащий в основе код на#
писан хорошо, то форматирование будет выглядеть привлекательно.
Visual Basic поддерживает явные блоки, а стандартное соглашение в Java пред#
писывает их использование, поэтому, программируя на этих языках, вы може#
те применять явные блоки. В C++ хорошо смотрится эмуляция явных блоков
или обозначение границ блоков с помощью пар begin%end.
Структурирование кода само по себе имеет большое значение. Конкретные
соглашения менее важны, чем сам факт, что вы последовательно применяете
определенные соглашения. Договоренности по форматированию, соблюдае#
мые лишь от случая к случаю, могут сильно ухудшить читаемость кода.
Многие аспекты форматирования сродни религиозным вопросам. Пытайтесь
разделять объективные и субъективные предпочтения. Используйте явные кри#
терии для обоснования вашей точки зрения при обсуждении стилевых пред#
почтений.
760 ЧАСТЬ VII Мастерство программирования
Г Л А В А 3 2
Самодокументирующийся
код
Содержание
http://cc2e.com/3245
32.1. Внешняя документация
32.2. Стиль программирования как вид документации
32.3. Комментировать или не комментировать?
32.4. Советы по эффективному комментированию
32.5. Методики комментирования
32.6. Стандарты IEEE
Связанные темы
Форматирование: глава 31
Процесс программирования с псевдокодом: глава 9
Классы: глава 6
Высококачественные методы: глава 7
Программирование как общение: разделы 33.5 и 34.3
Если стандарты документации разумны, большинству про#
Пишите код, исходя из того, что
все программисты, которые бу- граммистов нравится писать документацию. Как и форма#
дут сопровождать вашу програм- тирование, хорошая документация — свидетельство профес#
му, — склонные к насилию пси- сиональной гордости, выражаемой программистом в про#
хопаты, знающие, где вы живете. грамме. Документация имеет много вариантов, и после ее
Аноним общего обзора в этой главе мы рассмотрим специфический
вид документации, известный как «комментарии».
Перекрестная ссылка Здесь пе- Что, по#вашему, делает этот метод? Его непонятность не
ременная factorableNumber ис- имеет никакого обоснования. Он плохо документирован, но
пользуется исключительно ради дело не в отсутствии комментариев, а в плохом стиле про#
пояснения операции. О добав-
граммирования. Имена переменных неинформативны, а
лении переменных с целью по-
яснения операций см. подраз- способ форматирования груб. Вот улучшенный вариант того
дел «Упрощение сложных выра- же кода — простое улучшение стиля программирования
жений» раздела 19.1. делает его смысл гораздо яснее:
Одного взгляда на этот код достаточно, чтобы понять, что он имеет какое#то от#
ношение к простым числам (prime numbers). Второй взгляд показывает, что он
ГЛАВА 32 Самодокументирующийся код 763
находит простые числа от 1 до Num. Что касается первого фрагмента, то, взгля#
нув на него пару раз, вы даже не поймете, где завершаются циклы.
Различие между двумя фрагментами с комментариями не связано — их вообще
нет. Однако второй фрагмент читать гораздо лучше, приближаясь к Святому Гра#
алю понятности — самодокументированию. Задача документирования такого кода
во многом решается за счет хорошего стиля программирования. Если код напи#
сан хорошо, комментарии — всего лишь глазурь на пирожном читабельности.
Контрольный список:
самодокументирующийся код http://cc2e.com/3252
Классы
Формирует ли интерфейс класса согласованную абстракцию?
Удачное ли имя присвоено классу? Описывает ли оно главную цель класса?
Ясно ли интерфейс описывает применение класса?
Достаточно ли абстрактен интерфейс класса, чтобы можно было не думать
о том, как реализованы его сервисы? Можете ли вы рассматривать класс
как «черный ящик»?
Методы
Точно ли имя каждого метода описывает выполняемые в нем действия?
Выполняет ли каждый метод одну и только одну хорошо определенную задачу?
Все ли части метода, которые целесообразно поместить в отдельные мето-
ды, сделаны отдельными методами?
Очевиден ли и ясен ли интерфейс каждого метода?
Имена данных
Достаточно ли описательны имена типов, чтобы помогать документировать
объявления данных?
Удачно ли названы переменные?
Переменные используются только с той целью, в соответствии с которой они
названы?
Присвоены ли счетчикам циклов более информативные имена, чем i, j и k?
Используете ли вы грамотно названные перечисления вместо самодельных
флагов или булевых переменных?
Используете ли вы именованные константы вместо магических чисел или
магических строк?
Проводят ли конвенции именования различия между именами типов, пере-
числений, именованных констант, локальных переменных, переменных класса
и глобальных переменных?
Организация данных
Используете ли вы дополнительные переменные для пояснения кода?
Сгруппированы ли обращения к переменным?
Просты ли типы данных? Способствуют ли они минимизации сложности?
Обращаетесь ли вы к сложным данным при помощи абстрактных методов
доступа (абстрактных типов данных)?
764 ЧАСТЬ VII Мастерство программирования
Управление
Очевиден ли номинальный путь выполнения кода?
Сгруппированы ли связанные операторы?
Вынесены ли относительно независимые группы операторов в отдельные
методы?
Следует ли нормальный вариант после if, а не после else?
Просты ли управляющие структуры? Способствуют ли они минимизации
сложности?
Выполняет ли цикл одну и только одну функцию, как это делает хорошо
спроектированный метод?
Минимизировали ли вы вложенность?
Упростили ли вы булевы выражения путем использования дополнительных
булевых переменных, булевых функций и таблиц принятия решений?
Форматирование
Характеризует ли форматирование программы ее логическую структуру?
Проектирование
Прост ли код? Избегаете ли вы «хитрых» решений?
Скрыты ли детали реализации настолько, насколько возможно?
Стараетесь ли вы писать программу в терминах проблемной области, а не
структур вычислительной техники или языка программирования?
Комменто
Действующие лица:
ФРАСИМАХ Неопытный пурист теории, который верит всему, что читает.
КАЛЛИКЛ Закаленный в боях представитель старой школы — «настоящий»
программист.
ГЛАВКОН Молодой, самоуверенный, энергичный программист.
ИСМЕНА Опытная разработчица, уставшая от громких обещаний и просто
желающая найти несколько работающих методик.
СОКРАТ Мудрый опытный программист.
Мизансцена:
Завершение ежедневного собрания группы
— Желает ли кто#то обсудить еще что#нибудь, прежде чем мы вернемся к работе?
— спрашивает Сократ.
— Я хочу предложить стандарт комментирования для наших проектов, — гово#
рит Фрасимах. — Некоторые наши программисты почти не комментируют свой
код, а всем известно, что код без комментариев нечитаем.
ГЛАВА 32 Самодокументирующийся код 765
— Ты, должно быть, еще менее опытен, чем я думал, — отвечает Калликл. — Ком#
ментарии — это академическая панацея, и любому, кто писал реальные програм#
мы, известно, что комментарии затрудняют чтение кода, а не облегчают. Естествен#
ный язык менее точен, чем Java или Visual Basic, и страдает от избыточности, тог#
да как операторы языков программирования лаконичны и попадают в самое яб#
лочко. Если кто#то не может написать ясный код, разве ему удастся написать яс#
ные комментарии? Кроме того, комментарии устаревают при изменениях кода.
Доверяя устаревшим комментариям, ты сам себе роешь яму.
— Полностью согласен, — вступает в разговор Главкон. — Щедро прокомментиро#
ванный код читать труднее, потому что в этом случае читать приходится больше. Мало
того, что я должен читать код, так мне нужно читать еще и кучу комментариев!
— Подождите минутку, — вставляет свои две драхмы Исмена, ставя чашку кофе.
— Конечно, злоупотребление комментариями возможно, но хорошие комментарии
ценятся на вес золота. Мне приходилось сопровождать код как с комментариями,
так и без них, и, будь у меня выбор, я бы предпочла первый вариант. Не думаю, что
нам нужен стандарт, заставляющий писать один комментарий на каждые x строк
кода, но побудить всех программистов комментировать код не помешает.
— Если комментарии — пустая трата времени, почему все их используют, Калликл?
— спрашивает Сократ.
— Или потому, что таково требование, или потому, что человек прочитал где#то о
пользе комментариев. Ни один программист, когда#либо задумавшийся об этом,
не пришел к выводу, что комментарии полезны.
— Исмена считает, что они полезны. Она уже три года сопровождает твой код без
комментариев и чужой код с комментариями. Ее мнение ты уже слышал. Что ты
скажешь на это?
— Комментарии бесполезны, потому что они просто повторяют код в более мно#
гословной…
— Подожди, — прерывает Калликла Фрасимах. — Хорошие комментарии
не повторяют код и не объясняют его. Они поясняют его цель. Коммен#
тарии должны объяснять намерения программиста на более высоком
уровне абстракции, чем код.
— Верно, — соглашается Исмена. — Если я ищу фрагмент, который мне нужно из#
менить или исправить, я просматриваю комментарии. Комментарии, повторяю#
щие код, на самом деле бесполезны, потому что все уже сказано в самом коде. Когда
я читаю комментарии, я хочу, чтобы они напоминали оглавление книги. Коммен#
тарии должны помочь мне найти нужный раздел, а после этого я начну читать
код. Гораздо быстрее прочитать одно предложение на обычном языке, чем ана#
лизировать 20 строк кода на языке программирования.
Исмена наливает себе вторую чашку кофе.
— Мне кажется, что люди, отказывающиеся писать комментарии, (1) думают, что
их код понятнее, чем мог бы быть, (2) считают, что другие программисты гораз#
до сильнее интересуются их кодом, чем есть на самом деле, (3) думают, что дру#
гие программисты умнее, чем есть на самом деле, (4) ленятся или (5) боятся, что
кто#то другой узнает, как работает их код.
766 ЧАСТЬ VII Мастерство программирования
Этот метод вычисляет первые num чисел Фибоначчи. Стиль кодирования этого
метода чуть лучше, чем стиль метода, указанного в начале главы, но комментарий
неверен, и если вы слепо доверяете комментариям, то, пребывая в блаженном
неведении, пойдете в неверном направлении.
А что скажете по поводу этого метода?
// цикл от 2 до “num”
for ( int i = 2; i <= num; i++ ) {
// умножение “base” на “product”
product = product * base;
}
System.out.println( “Product = “ + product );
Этот метод возводит целое число base в целую степень num. Комментарии в этом
методе верны, но они не говорят о коде ничего нового. Это просто более много#
словная версия самого кода.
Наконец, еще один метод:
Виды комментариев
Комментарии можно разделить на шесть категорий, описанных ниже.
Повторение кода
Повторяющий комментарий переформулирует код иными словами и только за#
ставляет больше читать, не предоставляя дополнительной информации.
Объяснение кода
Такие комментарии обычно служат для объяснения сложных, хитрых или неста#
бильных фрагментов кода. В этих ситуациях они полезны, но обычно только
потому, что код непонятен. Если код настолько сложен, что требует объяснения,
почти всегда разумнее улучшить код, а не добавлять комментарии. Сделайте сам
код более ясным, а потом добавьте в него резюмирующие комментарии или ком#
ментарии цели.
Маркер в коде
Этот тип предназначен не для того, чтобы оставаться в коде. Такая пометка ука#
зывает программисту на то, что работа еще не выполнена. Некоторые разработ#
чики делают маркеры синтаксически некорректными (например, ******), чтобы о
невыполненной работе напомнил компилятор. Другие включают в комментарии
определенный ряд символов, не приводящий к ошибке компиляции и служащий
для поиска нужного фрагмента.
Мало что может сравниться с чувствами программиста, получившего от заказчика
сообщение о проблеме в коде и увидевшего во время отладки что#нибудь вроде:
return NULL; // ****** НЕ СДЕЛАНО! ИСПРАВИТЬ ДО ВЫПУСКА ПРОГРАММЫ!!!
Резюме кода
Резюмирующие комментарии выражают суть нескольких строк кода в одном#двух
предложениях. Эти комментарии более полезны, чем повторяющие, потому что
программист, читающий программу, может просматривать их быстрее, чем код.
Резюмирующие комментарии особенно полезны, если программу будет изменять
не только ее автор.
Эффективное комментирование
Эффективное комментирование отнимает не так много времени. Избыток ком#
ментариев не менее плох, чем недостаток, а оптимальной середины можно до#
стичь без особых проблем.
Написание комментариев может отнимать много времени по двум причинам. Во#
первых, стиль комментирования может быть трудоемким или нудным. Если это
ГЛАВА 32 Самодокументирующийся код 771
Если вы подумали про точки (.....), вы совершенно правы! Красиво, конечно, но спи#
сок хорош и без них. Точки затрудняют изменение комментариев, и, если есть вы#
бор (а он обычно есть), комментарии лучше сделать правильными, чем красивыми.
Вот пример другого популярного стиля, трудного в сопровождении:
вать не будут. Если можно выровнять столбцы звездочек одним нажатием клави#
ши, прекрасно — можете использовать этот стиль. Проблема не в звездочках, а в
том, что их трудно поддерживать. Следующий комментарий выглядит почти так
же хорошо, а сопровождать его куда легче:
Трудно сказать, какой смысл имеют плюсы в начале и в конце каждой пунктир#
ной линии, зато легко догадаться, что при каждом изменении комментария эту
линию придется адаптировать, чтобы конечный плюс находился на своем месте.
А если комментарий перейдет на вторую строку? Что тогда вы сделаете с плюсом?
Выбросите из комментария какие#то слова, чтобы он занимал только одну стро#
ку? Сделаете длину обеих строк одинаковой? При попытке согласованного при#
менения этой методики проблемы быстро множатся.
На подобных рассуждениях основан и популярный совет использовать в Java и
C++ синтаксис // для однострочных и синтаксис /* ... */ — для более длинных ком#
ментариев:
Мы встретились сейчас на поле одной из величайших битв этой войны. Мы пришли сюда
для того, чтобы отвести часть этого поля для последнего места успокоения тех, кто
отдал здесь свои жизни ради того, чтобы эта нация могла жить. Очень правильно, что
мы делаем это.
*/
Поломав над ним голову всю ночь, программист в итоге все исправил и пошел
домой спать. Несколько месяцев спустя он встретился с автором программы на
конференции и узнал, что комментарий означал «Rest in peace, Ludwig van Beet#
hoven» (Покойся в мире, Людвиг ван Бетховен). Бетховен умер в 1827 году, кото#
рому соответствует шестнадцатеричное значение 723. Необходимость использо#
вания значения 723h не имела никакого отношения к комментарию. &%@*?#%~!!!
Прочитав код, можно понять, что цикл ищет символ $, и это полезно отразить в
комментарии. Недостаток этого комментария в том, что он просто повторяет код
и ничего не говорит о его цели. Следующий комментарий чуть лучше:
// поиск символа ‘$’ в строке inputString
Этот комментарий лучше потому, что он говорит, что целью цикла является по#
иск символа $. Но мы все еще не знаем причину поиска символа $ — иначе гово#
ря, более глубокую цель цикла. Вот комментарий, который еще лучше:
// поиск терминального символа слова команды ($)
Этот комментарий на самом деле сообщает то, чего нет в листинге: что символ $
завершает слово команды. Просто прочитав код, этого никак не понять, так что
данный комментарий действительно важен.
При комментировании на уровне цели полезно подумать о том, как бы вы назва#
ли метод, выполняющий то же самое, что и код, который вы хотите прокоммен#
тировать. Если каждый абзац кода имеет одну цель, это несложно: примером мо#
жет служить комментарий в предыдущем фрагменте кода. Имя FindCommand%
WordTerminator() было бы вполне приемлемым именем метода. Другие варианты
— Find$InInputString() и CheckEachCharacterInInputStrUntilADollarSignIsFoundOrAll%
CharactersHaveBeenChecked() — по очевидным причинам являются плохими (или
некорректными) именами. Опишите суть кода как имя соответствующего метода,
не используя сокращений и аббревиатур. Это описание и будет комментарием, и
скорее всего он будет относиться к уровню цели.
Во время документирования сосредоточьтесь на самом коде Стро#
го говоря, сам код — это документация, которую всегда следует прове#
рять в первую очередь. В предыдущем примере литерал $ следовало бы
заменить на именованную константу, а имена переменных должны были бы пре#
доставлять больше информации о том, что происходит. Если бы вы хотели добиться
максимальной читабельности, вы добавили бы переменную, содержащую резуль#
тат поиска. Это провело бы ясное различие между индексом цикла и результатом
выполнения цикла. Вот предыдущий пример, переписанный в хорошем стиле и
дополненный хорошим комментарием:
Если код достаточно хорош, его цель можно понять при чтении и без коммента#
риев. Фактически они даже могут стать избыточными, но от этого страдают очень
немногие программы.
Наконец, этот код следовало бы выделить в метод с именем
Перекрестная ссылка О вынесе-
вроде FindCommandWordTerminator(). Аналогичный коммента# нии фрагмента кода в отдель-
рий также полезен, но при изменении ПО комментарии уста# ный метод см. также подраздел
ревают и становятся неверными чаще, чем имена методов. «Извлечение метода из друго-
го метода» раздела 24.3.
Придумывая комментарий абзаца, стремитесь отве'
тить на вопрос «почему?», а не «как?» Комментарии,
объясняющие, «как» что#то выполняется, обычно относятся к уровню языка про#
граммирования, а не проблемы. Цель операции почти невозможно выразить в таком
комментарии, поэтому он часто оказывается избыточным. Разве следующий ком#
ментарий сообщает что#то такое, чего не ясно из самого кода?
Пример комментария,
отвечающего на вопрос «как?» (Java)
// если флаг счета равен нулю
if ( accountFlag == 0 ) ...
Этот комментарий гораздо лучше, так как он говорит что#то, чего нельзя понять,
исходя из самого кода. Код также можно улучшить, заменив 0 на элемент пере#
числения с выразительным именем и уточнив имя переменной. Вот самая лучшая
версия этого кода и этого комментария:
избыточным, и его вполне можно удалить. Можно поступить и иначе, чуть изме#
нив роль комментария:
Если этот комментарий относится ко всему блоку кода после проверки условия
if, он является резюмирующим комментарием, и не будет лишним сохранить его
в качестве заголовка абзаца кода.
Используйте комментарии для подготовки читателя кода к последующей
информации Хорошие комментарии говорят человеку, читающему код, чего ожи#
дать. Просмотра комментариев должно быть достаточно для получения хороше#
го представления о том, что делает код и где искать места выполнения отдельных
операций. Из этого следует, что комментарии должны всегда предшествовать
описываемому ими коду. На занятиях по программированию об этом говорят не
всегда, но в отрасли разработки коммерческого ПО эта конвенция стала почти
стандартом.
Не размножайте комментарии без необходимости В избыточном коммен#
тировании ничего хорошего: если комментариев слишком много, они только
скрывают смысл кода, который должны пояснять. Вместо того чтобы писать до#
полнительные комментарии, постарайтесь сделать читабельнее сам код.
Документируйте сюрпризы Если что#то не следует из самого кода, отразите
это в комментарии. Если вы использовали хитрую методику вместо простой для
повышения производительности, укажите в комментариях, каким был бы простой
подход, и оцените прирост производительности, достигаемый благодаря хитро#
сти, например:
Операция сдвига вправо в этом примере выбрана намеренно. Почти всем програм#
мистам известно, что в случае целых чисел сдвиг вправо функционально эквива#
лентен делению на 2.
Если это известно почти всем, зачем это документировать? Потому что целью
данного кода является не сдвиг вправо, а деление на 2. Важно то, что програм#
мист использовал не ту методику, которая лучше всего соответствует цели. Кроме
того, большинство компиляторов в любом случае заменяют целочисленное деле#
ние на 2 операцией сдвига вправо, из чего следует, что ухудшение ясности кода
обычно не требуется. В данной ситуации комментарий говорит, что компилятор
ГЛАВА 32 Самодокументирующийся код 781
Пример комментирования
хитрого кода (C++)
// ОЧЕНЬ ВАЖНОЕ ЗАМЕЧАНИЕ:
// Конструктор этого класса принимает ссылку на объект UiPublication.
// Объект UiPublication НЕЛЬЗЯ УНИЧТОЖАТЬ раньше объекта DatabasePublication,
// иначе программу ожидает мученическая смерть.
if ( *inputString != END_OF_STRING ) {
Цель цикла. Положение комментария ясно говорит, что переменная inputString устанавливается с
целью использования в цикле.
Комментирование методов
С комментариями методов связан одни из самых худших
Перекрестная ссылка О форма-
советов, который дается в типичных учебниках по програм# тировании методов см. раздел
мированию. Многие авторы советуют независимо от размера 31.7. О создании высококаче-
или сложности метода нагромождать перед его началом це# ственных методов см. главу 7.
лые информационные баррикады:
Это глупо. Метод CopyString тривиален и скорее всего включает не более пяти строк
кода. Комментарий совершенно не соответствует объему метода. Цель и алгоритм
метода высосаны из пальца, потому что трудно описать что#то настолько простое,
как CopyString, на уровне детальности между «копированием строки» и самим ко#
дом. Предположения об интерфейсе и история изменений также бесполезны —
эти комментарии только занимают место в листинге. Фамилия автора дополнена
избыточными данными, которые можно легко найти в системе управления реви#
зиями. Заставлять указывать всю эту информацию перед каждым методом — зна#
чит подталкивать программистов к написанию неточных комментариев и затруд#
нять сопровождение программы. Эти лишние усилия не окупятся никогда.
Другая проблема с тяжеловесными заголовками методов состоит в том, что они
мешают факторизовать код: затраты, связанные с созданием нового метода, так
велики, что программисты будут стремиться создавать меньше методов. Конвен#
ции кодирования должны поощрять применение хороших методик — тяжеловес#
ные заголовки методов поощряют их игнорировать.
А теперь нескоько советов по комментированию методов.
Располагайте комментарии близко к описываемому ими коду Одна из при#
чин того, что пролог метода не должен содержать объемной документации, в том,
что при этом комментарии далеки от описываемых ими частей метода. Если ком#
ментарии далеки от кода, вероятность того, что их не будут изменять вместе с кодом
при сопровождении, повышается. Смысл комментариев и кода начинает расхо#
диться, и внезапно комментарии становятся никчемными. Поэтому соблюдайте
ГЛАВА 32 Самодокументирующийся код 789
Это позволяет перескакивать с метода на метод или путем поиска символов /**,
или автоматически, если такую возможность поддерживает редактор.
Похожая методика заключается в использовании разных маркеров для разных видов
комментариев в зависимости от того, что они описывают. Так, в C++ вы могли бы
792 ЧАСТЬ VII Мастерство программирования
в комментарий:
// $Id: ClassName.java,v 1.1 2004/02/05 00:36:43 ismene Exp $
Стандарты разработки ПО
IEEE Std 830%1998 — рекомендуемая методика составления
спецификаций требований к ПО http://cc2e.com/3273
796 ЧАСТЬ VII Мастерство программирования
Стандарты управления
IEEE Std 1058%1998 — стандарт планов управления проек#
http://cc2e.com/3287 тами разработки ПО
IEEE Std 1074%1997 — стандарт разработки процессов жиз#
ненного цикла ПО
IEEE Std 1045%1992 — стандарт метрик продуктивности ПО
IEEE Std 1062%1998 — рекомендуемая методика приобретения ПО
IEEE Std 1540%2001 — стандарт процессов жизненного цикла ПО — управление
риском
IEEE Std 1490%1998 — руководство (заимствование стандарта PMI) к своду знаний
по управлению проектами (PMBOK)
Обзор стандартов
Обзоры стандартов можно найти в двух источниках, указан#
http://cc2e.com/3294 ных ниже.
IEEE Software Engineering Standards Collection, 2003 Edition. New
York, NY: Institute of Electrical and Electronics Engineers (IEEE).
http://cc2e.com/3201
Этот всесторонний труд содержит 40 самых новых стандар#
тов разработки ПО ANSI/IEEE на 2003 год. Каждый стандарт
включает план документа, описание каждого компонента плана и обоснование
этого компонента. Этот документ включает стандарты планов контроля качества,
планов управления конфигурациями, документов тестирования, спецификаций
требований, планов верификации и проверки, описаний проектирования, планов
управления проектами и пользовательской документации. Данная книга представ#
ляет собой квинтэссенцию опыта сотен лучших специалистов в своих областях
и была бы выгодным приобретением практически при любой цене. Некоторые
из стандартов доступны и отдельно. Все стандарты можно заказать у организа#
ГЛАВА 32 Самодокументирующийся код 797
Дополнительные ресурсы
Кроме стандартов IEEE, есть и другие источники информа#
http://cc2e.com/3208
ции о документировании программ.
Spinellis, Diomidis. Code Reading: The Open Source Perspective.
Boston, MA: Addison#Wesley, 2003. Эта книга является праг# Интересно, сколько великих пи-
матичным исследованием методик чтения кода. В ней вы сателей никогда не читали дру-
найдете советы по поиску нужного кода и чтению больших гих авторов, сколько великих
художников никогда не изуча-
объемов кода, сведения об инструментах, облегчающих чте#
ли произведения других масте-
ние кода, и массу полезных рекомендаций. ров, сколько высококвалифици-
SourceForge.net. Уже много лет обучению разработке ПО рованных хирургов никогда не
учились, стоя за плечом колле-
препятствует проблема поиска реальных примеров готового
ги… И все же именно этого мы
кода, которые можно было бы обсудить со студентами. Мно# ожидаем от программистов.
гие люди быстрее всего учатся на реальных примерах, од#
Дэйв Томас (Dave Thomas)
нако большинство реальных программ является собствен#
ностью создавших их компаний. Благодаря Интернету и ПО
с открытым исходным кодом эта ситуация улучшилась. Web# http://cc2e.com/3215
сайт Source Forge содержит код тысяч программ, написан#
ных на C, C++, Java, Visual Basic, PHP, Perl, Python и других языках, причем весь этот
код вы можете загрузить из сети совершенно бесплатно. Вы можете проанализи#
ровать реальные примеры, гораздо более объемные, чем примеры, приведенные
в этой книге. Для начинающих программистов, не имевших ранее дела с объем#
ными примерами готового кода, этот Web#сайт окажется особенно полезен как
источник и хороших, и плохих методик кодирования.
Sun Microsystems. «How to Write Doc Comments for the Javadoc
Tool», 2000. В этой статье (http://java.sun.com/j2se/javadoc/ http://cc2e.com/3222
writingdoccomments/) описывается применение инструмента
Javadoc для документирования программ Java. Она включает детальное описание
разметки комментариев с использованием нотации стиля @tag, а также многие
конкретные советы по написанию самих комментариев. Конвенции Javadoc яв#
ляются, наверное, наиболее развитыми из нынешних стандартов документирования
на уровне кода.
Ниже указаны источники информации о других аспектах документирования ПО.
McConnell, Steve. Software Project Survival Guide. Redmond, WA: Microsoft Press, 1998.
В этой книге описывается документация, необходимая в критически важных про#
ектах среднего размера. На Web#сайте книги вы можете найти много шаблонов
документов.
798 ЧАСТЬ VII Мастерство программирования
Управляющие структуры
Комментируете ли вы каждый управляющий оператор?
Комментируете ли вы концы длинных или сложных управляющих структур
или упрощаете ли вы эти структуры по мере возможности, чтобы они не
требовали комментариев?
Методы
Комментируете ли вы назначение каждого метода?
Указываете ли вы в комментариях к методам в случае надобности другую
информацию, такую как входные и выходные данные, предположения об
интерфейсе, ограничения, исправленные ошибки, глобальные эффекты и
источники алгоритмов?
Файлы, классы и программы
Включает ли программа краткий документ, подобный документу Книжной па-
радигмы, предоставляющий общую информацию об организации программы?
Описали ли вы назначение каждого файла?
Указали ли вы в листинге фамилию и имя автора, адрес электронной поч-
ты и номер телефона?
Ключевые моменты
Вопрос о том, стоит ли комментировать код, вполне обоснован. При плохом
выполнении комментирование является пустой тратой времени и иногда при#
чиняет вред. При грамотном применении комментирование полезно.
Основная часть критически важной информации о программе должна содер#
жаться в исходном коде. На протяжении жизни программы актуальности ис#
ходного кода уделяется больше внимания, чем актуальности любого другого
ресурса, поэтому важную информацию полезно интегрировать в код.
Хороший код сам является самой лучшей документацией. Если код настолько
плох, что требует объемных комментариев, попытайтесь сначала улучшить его.
Комментарии должны сообщать о коде что#то такое, что он не может сооб#
щить сам — на уровне резюме или уровне цели.
Некоторые стили комментирования заставляют выполнять много нудной кан#
целярской работы. Используйте стиль, который было бы легко поддерживать.
800 ЧАСТЬ VII Мастерство программирования
Г Л А В А 3 3
Личность
Содержание
http://cc2e.com/3313
33.1. Причем тут личность?
33.2. Интеллект и скромность
33.3. Любопытство
33.4. Профессиональная честность
33.5. Общение и сотрудничество
33.6. Творчество и дисциплина
33.7. Лень
33.8. Свойства, которые менее важны, чем кажется
33.9. Привычки
Связаные темы
Мастерство разработки ПО: глава 34
Сложность: разделы 5.2 и 19.6
С момента публикации в 1965 году знаменитой статьи Эдсгера Дейкстры «Prog#
ramming Considered as a Human Activity» («Программирование как человеческая
деятельность») личности программистов привлекают самое пристальное внимание
ученых. Названия книг «Психология конструирования мостов» и «Эксперименталь#
ные исследования поведения юристов» могли бы показаться абсурдными, однако
работы «Психология программирования» («The Psychology of Computer Program#
ming»), «Экспериментальные исследования поведения программистов» («Exploratory
Experiments in Programmer Behavior») и т. п. уже стали классическими.
В какой бы области ни работали инженеры, они должны знать возможности при#
меняемых инструментов и материалов. Инженеры#электрики знают проводимость
разных металлов и сотни способов использования вольтметра. Инженерам#про#
ектировщикам строительных конструкций известны параметры прочности дерева,
бетона и стали.
ГЛАВА 33 Личность 801
33.3. Любопытство
Как только вы признали, что ваши способности слишком малы для понимания
большинства программ, и поняли, что эффективное программирование — это
поиск способов компенсировать данный недостаток, вы начинаете этот поиск,
продолжающийся вплоть до окончания карьеры. А поэтому важная черта харак#
тера программиста — любопытство к техническим вопросам. Релевантная техни#
ческая информация постоянно изменяется. Многим Web#программистам никог#
да не приходилось программировать для Microsoft Windows, а многие разработ#
чики программ для Windows никогда не имели дела с DOS, UNIX или перфокар#
тами. Специфические особенности технической среды изменяются каждые 5—10
лет. Если вы недостаточно любопытны для того, чтобы не отставать от измене#
ний, вы рискуете разделить участь динозавров.
Программисты часто так заняты работой, что у них не остается времени на изуче#
ние более эффективных способов работы. Если это относится и к вам, вы не оди#
ноки. Ниже я расскажу, как развить любопытство и сделать обучение приоритетом.
Изучите процесс разработки Чем больше вы узнаете
Перекрестная ссылка О важно-
о процессе разработки ПО из книг или на собственном опы# сти процесса разработки ПО см.
те, тем лучше будете понимать изменения и тем эффектив# раздел 34.2.
нее вести свою группу в правильном направлении.
Если ваша работа состоит исключительно из краткосрочных заданий, не способ#
ствующих развитию навыков, подумайте, как исправить эту ситуацию. Если вы
работаете на конкурентном рынке ПО, половина ваших знаний устареет за три
года. Без обучения вы превратитесь в ископаемое.
Не тратьте время, работая на компанию, не учитывающую ваших инте#
ресов. Несмотря на экономические подъемы и спады и перемещение
некоторых рабочих мест в другие регионы, ожидается, что среднее чис#
ло рабочих мест, связанных с программированием, за период с 2002 до 2012 года
в США значительно увеличится. Ожидается, что число должностей системных ана#
литиков вырастет примерно на 60%, а разработчиков ПО — примерно на 50%. Что
касается всех должностей, связанных с компьютерами, то в дополнение к 3 мил#
лионам имеющихся рабочих мест за это время будет создано еще около 1 милли#
она мест (Hecker, 2001; BLS, 2004). Если работа не способствует вашему развитию,
найдите другую работу.
Экспериментируйте Экспериментирование с процессами
Перекрестная ссылка Идея эк-
программирования и разработки — эффективный способ спериментирования лежит в
самообразования. Если вы не знаете, как работает какая#то основе нескольких ключевых
возможность языка, напишите небольшую программу и аспектов программирования
узнайте. Создайте прототип. Изучите выполнение програм# (см. подраздел «Эксперименти-
рование» раздела 34.9).
мы в отладчике. Гораздо лучше написать короткую програм#
му для проверки какой#то возможности, чем создать большую
программу, реализовав в ней возможность, которую вы не совсем понимаете.
А если короткая программа показывает, что какая#то функция работает не так, как
вы хотите? Этого#то вам и нужно! Лучше обнаружить это в небольшой програм#
ме, чем в большой. Быстро совершая ошибки и извлекая из них уроки, вы облег#
804 ЧАСТЬ VII Мастерство программирования
желание ясно понять программу и отказ от компиляции кода с той лишь це#
лью, чтобы узнать, работает ли он;
предоставление реалистичных отчетов о статусе проекта;
предоставление реалистичных оценок срока выполнения проекта и отстаива#
ние своей позиции, даже если руководители просят адаптировать оценку.
Два первых элемента этого списка — признание собственных недостатков и ошибок
— можно считать отголосками интеллектуальной скромности, рассмотренной
выше. Как вы узнаете что#то новое, если будете считать, что уже все знаете? Если
на то пошло, гораздо лучше считать, что вы ничего не знаете. Прислушивайтесь к
мнениям людей и учитесь у них, но старайтесь при этом понять, действительно
ли они знают, что говорят.
Оценивайте степень своей уверенности в тех или иных суждениях. Если вы обычно
уверены в них на 100%, это предупреждающий знак.
Отказ от признания ошибок — особенно вредная привычка.
Любой дурак способен отстаивать
Если Салли отказывается признать ошибку, она, очевидно, свои ошибки — большинство ду-
считает, что сможет убедить в этом окружающих, но на са# раков именно так и делают.
мом деле имеет место обратное. Все будут знать, что ошиб# Дейл Карнеги
ку допустила Салли. Ошибки — естественный результат подъ# (Dale Carnegie)
емов и спадов сложных интеллектуальных процессов, и если
Салли не была небрежной, никто не будет ее ни в чем упрекать.
Если она отказывается признать ошибку, она сможет одурачить только одного
человека — себя. Что касается ее коллег, то они поймут, что они работают с над#
менным и не совсем искренним программистом. Это уже не просто ошибка, а нечто
худшее. Если вы совершили ошибку, признавайте это быстро и охотно.
Еще один распространенный недостаток — убежденность в понимании сообще#
ний компилятора. Если вы не понимаете предупреждение компилятора или ду#
маете, что понимаете, но вам не хватает времени, чтобы проверить свое предпо#
ложение, к чему это приведет? Вероятно, в итоге вам придется решать проблему
с нуля, тогда как компилятор буквально размахивал готовым решением перед вашим
носом. Если меня просят помочь отладить программу, я спрашиваю, компилиру#
ется ли она без ошибок и предупреждений. Довольно часто программисты отве#
чают «да» и начинают объяснять симптомы проблемы. Я отвечаю: «Хм… Похоже
на неинициализированный указатель, но компилятор должен был предупредить
вас об этом». В ответ на это они заявляют: «О да, он предупреждал об этом, но мы
думали, что это означает что#то другое». Совершив ошибку, вы едва ли сможете
убедить людей в том, что она не ваша. Обмануть компьютер еще сложнее, так что
не тратьте попусту свое время.
Похожий вид интеллектуальной небрежности имеет место тогда, когда вы не со#
всем понимаете свою программу и «просто компилируете ее, чтобы увидеть, бу#
дет ли она работать». Примером может служить запуск программы с целью опре#
делить, какое условие следует использовать: < или <=. На самом деле работоспо#
собность программы в этой ситуации не играет никакой роли, потому что вы
понимаете ее недостаточно хорошо, чтобы сказать, почему она работает. Помните:
тестирование может указать только на наличие, но не на отсутствие ошибок. Если
вы не понимаете программу, вы не сможете тщательно ее протестировать. Ком#
808 ЧАСТЬ VII Мастерство программирования
чае неуместно: это все равно что обсуждать, сколько футов в миле. Мы не можем
пересмотреть законы природы. Однако мы можем пересмотреть другие аспекты
проекта, влияющие на срок его выполнения, и переоценить его. Мы можем отка#
заться от некоторых возможностей, снизить производительность, выполнить про#
ект инкрементно, а также привлечь к работе меньшее число людей и установить
более длительный срок выполнения проекта или наоборот: привлечь больше раз#
работчиков и реализовать проект быстрее».
Один из самых ужасных советов я получил на лекции об управлении проектами
разработки ПО. Лектором был автор бестселлера по управлению проектами. Один
из присутствующих спросил: «Что вы делаете, если руководители просят оценить
срок выполнения проекта, но вы знаете, что если вы сообщите верную оценку, они
откажутся от проекта?» Лектор ответил, что в такой щекотливой ситуации следу#
ет склонить руководителей к реализации проекта, недооценив его. Он сказал, что,
как только руководители инвестируют средства в первую часть проекта, им при#
дется довести его до конца.
Абсолютное заблуждение! Начальство отвечает за управление компанией в целом.
Если какая#то функция программы обойдется компании в 250 000 долларов, а вы
оцените ее в 750 000, разрабатывать программу не следует. Принимать такие ре#
шения должны руководители. Когда лектор отстаивал недооценку проекта, он от#
стаивал скрытое воровство власти у руководителей. Если вы считаете, что проект
интересен, открывает перед компанией новые возможности или позволяет мно#
гому научиться, так и скажите. Руководители тоже способны взвесить эти факто#
ры. Но подталкивание их к неверным решениям может стоить компании сотен
тысяч долларов. Если из#за этого вы лишитесь работы, пеняйте на себя.
33.7. Лень
Лень может проявляться несколькими способами:
Лень: качество, которое застав-
ляет прилагать больше усилий отсрочка выполнения неприятной задачи;
для снижения общих затрат немедленное выполнение неприятной задачи с целью как
энергии. Она заставляет писать
можно более быстрого избавления от нее;
программы, облегчающие труд,
и документировать написанное, написание инструмента для выполнения неприятной за#
чтобы вам не пришлось отве- дачи, чтобы ее никогда не пришлось выполнять снова.
чать на лишние вопросы.
Одни проявления лени лучше, другие — хуже. Первое едва
Ларри Уолл (Larry Wall) ли когда#нибудь бывает выгодным. Наверное, и вы когда#то
тратили несколько часов на выполнение необязательных
ГЛАВА 33 Личность 811
Настойчивость
Как и большинство подобных субъективных понятий, настойчивость в зависимости
от обстоятельств может быть и достоинством, и недостатком и обозначается раз#
ными словами. Если вы хотите считаете ее плохим качеством, вы называете ее
«упрямством», а то и «ослиным упрямством». Если вы желаете придать ей хоро#
ший оттенок, можете назвать ее «упорством».
Как правило, при разработке ПО настойчивость принимает форму ослиного уп#
рямства, т. е. пользы не приносит. Настойчивое стремление довести код до ума с
использованием одного подхода трудно признать достоинством. Попробуйте
перепроектировать класс, перепишите код или вернитесь к проблеме позже. Если
один подход не работает, самое время испытать другой (Pirsig, 1974).
812 ЧАСТЬ VII Мастерство программирования
Опыт
По ряду причин важность практического опыта в сравнении с книжным образо#
ванием в области разработки ПО не столь велика, как во многих других областях.
В других областях базовые положения изменяются довольно медленно, вследствие
чего люди, закончившие вуз с интервалом в 10 лет, обладают по сути одинаковы#
ми знаниями. В отрасли разработки ПО даже основы претерпевают быстрые из#
менения. Человек, закончивший вуз через 10 лет после вас, вероятно, будет знать
вдвое больше об эффективных методиках программирования. К программистам
старшего возраста относятся с подозрением не только потому, что они якобы не
имеют представления об отдельных технологиях, а потому, что они, возможно,
никогда и не сталкивались с базовыми концепциями программирования, распро#
странившимися после того, как они закончили вуз.
В других областях текущие знания человека о работе будут служить ему и завтра.
Что до разработки ПО, то, если программист неспособен пересматривать привыч#
ные способы мышления, приобретенные им при использовании предыдущего языка
программирования, или методики оптимизации кода, работавшие на старом ком#
пьютере, наличие опыта окажется худшим вариантом, чем его полное отсутствие.
Многие программисты тратят время на подготовку к завершившейся битве, а не
предстоящей. Если вы не изменяетесь вместе со временем, опыт скорее вредит,
чем помогает.
Кроме того, опираясь на собственный опыт, люди часто делают неверные выво#
ды. Трудно объективно оценить свою жизнь. Вы можете упустить из виду ключе#
вые элементы своего опыта, которые подтолкнули бы вас к другим выводам, если
бы вы их учли. В этом смысле могут помочь книги о работе других программис#
тов, потому что так вы узнаете опыт других людей, достаточно очищенный, что#
бы его можно было изучить объективно.
Стоит упомянуть также абсурдное внимание к объему опыта программистов. «Нам
нужен программист, обладающий 5#летним опытом программирования на C» —
ГЛАВА 33 Личность 813
глупое высказывание. Если программист не изучил C за год или два, еще три года
не сыграют особой роли. Подобный вид «опыта» очень слабо связан с произво#
дительностью труда.
Быстрые изменения информации в сфере программирования создают странную
динамику в области «опыта». Во многих отраслях специалист, имеющий за пле#
чами массу успехов и достижений, может расслабиться и наслаждаться заслужен#
ным уважением. В то же время знания расслабившегося программиста очень бы#
стро устаревают. Чтобы поддерживать компетентность, вы должны идти в ногу со
временем. Для молодых и энергичных программистов это преимущество. Более
пожилые программисты иногда чувствуют, что они уже заслужили свои эполеты,
и возмущаются, когда их год за годом принуждают подтверждать квалификацию.
Главное то, что опыт может иметь разное качество. Если вы работаете 10 лет,
получаете ли вы 10 лет опыта или 1 год опыта 10 раз? Чтобы приобрести истин#
ный опыт, вы должны действовать с умом. Если вы постоянно учитесь, вы приоб#
ретаете опыт. Если вы не учитесь, о приобретении опыта не может быть и речи,
как бы долго вы ни работали.
Страсть к программированию
Если вы не проводили хотя бы месяц, работая над одной программой — рабо%
тая по 16 часов в день, грезя о ней в остальные 8 часов беспокойного сна, ра%
ботая несколько ночей подряд над устранением из программы «одной после%
дней ошибки», — вы не писали сложную компьютерную программу. Тогда вам
трудно понять, что в программировании есть что%то захватывающее.
Эдвард Йордон (Edward Yourdon)
Настолько щедрая дань богам программирования — едва ли не самый верный путь
к неудаче. Ночные бдения позволят вам на какое#то время почувствовать себя самым
великим программистом в мире, но потом вам придется потратить несколько
недель на исправление дефектов, внесенных в код в безудержном порыве. Во что
бы то ни стало вызовите у себя увлечение программированием, но помните, что
увлечение никогда не заменит компетентности.
33.9. Привычки
Следовательно, нравственные добродетели существуют в нас не от приро%
ды и не вопреки природе… а благодаря приучению мы в них совершенствуем%
ся… Ибо если нечто следует делать, пройдя обучение, то учимся мы, делая это…
Хорошо строя дома, люди станут добрыми зодчими, а строя худо — худыми…
Так что вовсе не мало, а очень много, пожалуй, даже все зависит от того, к
чему именно приучаться с самого детства.
Аристотель
Выработать хорошие привычки крайне важно, потому что очень многое из того,
что вы делаете как программист, вы делаете не задумываясь. Например, когда#то
вы могли думать о форматировании циклов, но вы не думаете об этом при напи#
сании каждого нового цикла. Вы пишете их как привыкли. Это относится почти
ко всем аспектам форматирования кода. Когда вы в последний раз всерьез заду#
814 ЧАСТЬ VII Мастерство программирования
Дополнительные ресурсы
«Человеческому фактору» в разработке ПО посвящены сле#
дующие материалы. http://cc2e.com/3327
Ключевые моменты
Способность к написанию программ напрямую зависит от личного характера.
Важнейшие качества программиста — скромность, любопытство, профессио#
нальная честность, творчество и дисциплина, а также «просвещенная» лень.
Чтобы стать отличным программистом, можно не обладать особым талантом,
но необходимо постоянно стремиться к самосовершенствованию.
Удивительно, но интеллект, опыт и настойчивость вредят программистам не
меньше, чем помогают.
Многие программисты не ведут активного поиска новых сведений и методик,
а полагаются на случайные столкновения с новой информацией на работе. Если
вы посвятите небольшую долю своего времени чтению книг и изучению про#
граммирования, через несколько месяцев вы будете намного превосходить
почти всех своих коллег.
Хороший характер во многом — продукт правильных привычек. Если хотите
стать великолепным программистом, выработайте правильные привычки, а все
остальное придет само собой.
ГЛАВА 34 Основы мастерства 817
Г Л А В А 3 4
Основы мастерства
Содержание
http://cc2e.com/3444
34.1. Боритесь со сложностью
34.2. Анализируйте процесс разработки
34.3. Пишите программы в первую очередь для людей и лишь во вторую — для
компьютеров
34.4. Программируйте с использованием языка, а не на языке
34.5. Концентрируйте внимание с помощью соглашений
34.6. Программируйте в терминах проблемной области
34.7. Опасайтесь падающих камней
34.8. Итерируйте, итерируйте и итерируйте
34.9. И да отделена будет религия от разработки ПО
Связанные темы
Вся книга
Эта книга посвящена главным образом деталям конструирования ПО: созданию
высококачественных классов и циклов, выбору имен переменных, форматирова#
нию исходного кода, интеграции системы и т. д. Абстрактные вопросы в ней от#
части принесены в жертву более конкретным темам.
Поскольку конкретные вопросы уже рассмотрены в предыдущих главах, то, чтобы
получить представление об абстрактных концепциях, нам нужно лишь вернуться
к темам разных глав и посмотреть, как они взаимосвязаны. В этой главе явно об#
суждаются абстрактные аспекты программирования, такие как сложность, абстрак#
ция, процесс разработки, удобочитаемость кода, итерация и т. д. Эти аспекты во
многом объясняют разницу между хакерством и мастерством разработки ПО.
Серьезным программистам я
Тот же принцип осознанного внимания к процессу справед#
хочу сказать: уделяйте часть ра- лив и для проектирования. Перед началом стройки нужно
бочего дня изучению и улучше- создать надежный фундамент. Если вы начнете писать код,
нию собственных методик. Хотя не завершив создания фундамента, изменить архитектуру си#
на программистов всегда давит стемы будет труднее. Программисты будут дорожить уже
какой-то будущий или прошед-
ший крайний срок, методологи-
написанным кодом. Трудно избавиться от плохого фунда#
ческая абстракция — мудрая мента, если вы начали возводить на нем здание.
долговременная инвестиция.Процесс разработки важен в первую очередь потому, что ка#
Роберт У. Флойд
чество должно встраиваться в ПО с самого начала. Это про#
(Robert W. Floyd)
тиворечит давней «мудрости», согласно которой вы можете
написать сколь угодно плохой код и устранить все ошибки на
этапе тестирования. Это заблуждение. Тестирование может только указать на отдельные
дефектные области программы — оно не сделает вашу программу удобнее в исполь#
зовании, более быстрой, компактной, удобочитаемой или расширяемой.
Преждевременная оптимизация — еще один изъян процесса разработки. Эффек#
тивный процесс подразумевает, что вы выполняете грубую работу в начале и тонкую
в конце. Если бы вы были скульптором, вы придавали бы композиции общую форму
и только потом начинали бы работать над отдельными деталями. Преждевремен#
но выполняя оптимизацию, вы тратите время на полирование фрагментов кода,
которые полировать не нужно. Вы можете отполировать фрагменты, которые и
так достаточно малы и быстры, вы можете отполировать код, который позднее
придется выбросить, и можете отказаться от выбрасывания плохого кода, потому
что уже потратили время на его полировку. Всегда спрашивайте себя: «Делаю ли я
это в правильном порядке? Что изменилось бы при изменении порядка?» Исполь#
зуйте адекватный процесс разработки и делайте это осознанно.
ГЛАВА 34 Основы мастерства 821
ствие между тем, что вы хотите сделать, и тем, что уже поддерживают инструмен#
ты, невелико и заставляет идти лишь на небольшие уступки.
Если вы ловите себя на том, что работаете над повторяющимся кодом или вноси#
те похожие изменения в несколько фрагментов, вам следует почувствовать себя
«неловко и неприятно», усомнившись в том, что управление было адекватно цен#
трализовано в классах или методах. Если из#за проблем с отдельным классом вы
не можете с легкостью создать тестовые леса, вы должны почувствовать сомне#
ние и спросить себя, не слишком ли сильно класс сопряжен с другими классами.
Если вы не можете повторно использовать код в других программах, потому что
некоторые классы слишком взаимозависимы, это также предупреждает о черес#
чур сильном сопряжении классов.
Погрузившись в детали программы, обращайте внимание на предупреждающие
знаки, указывающие на то, что часть проекта программы недостаточно хорошо
определена для кодирования. Трудности при написании комментариев, именова#
нии переменных и декомпозиции проблемы на связные классы с ясными интер#
фейсами — все это говорит о том, что нужно более тщательно выполнить проек#
тирование перед началом кодирования. Невыразительные имена и сложности при
описании фрагментов кода лаконичными комментариями — другие признаки
проблем. Если вы хорошо представляете проект программы, реализовать низко#
уровневые детали будет легко.
Обращайте внимание на признаки того, что программу трудно понять. Об этом
свидетельствуют любые неудобства. Если это трудно для вас, для будущих програм#
мистов это окажется еще труднее. Они по достоинству оценят ваши дополнитель#
ные усилия по улучшению кода. Если вы разгадываете код, а не читаете его, он
слишком сложен. Если он сложен, он неверен. Сделайте его проще.
Если вы хотите воспользоваться всеми преимуществами предупреждаю#
щих знаков, программируйте так, чтобы создать собственные предупреж#
дения. Это полезно потому, что, даже если вам известны предупреждаю#
щие знаки, их на удивление легко упустить из виду. Гленфорд Майерс исследовал
исправления дефектов и обнаружил, что самой частой причиной плохого обна#
ружения ошибок была простая невнимательность. Ошибки были видны в резуль#
татах тестов, но программисты их не замечали (Myers, 1978b).
Программируйте так, чтобы ошибки было трудно не заметить. Примером этого
может служить установка указателей в NULL после их освобождения, чтобы их
ошибочное использование вызывало безобразные проблемы. Освобожденный
указатель даже после освобождения может указывать на корректную область па#
мяти. Установка указателя в NULL гарантирует, что он будет указывать на некор#
ректный адрес, благодаря чему ошибку будет сложнее не заметить.
Предупреждения компилятора являются буквальными предупреждающими знаками,
которые часто упускают из виду. Если ваша программа генерирует предупрежде#
ния или ошибки, устраните их. Невелика вероятность того, что вы заметите тон#
кие предупреждающие знаки, если вы игнорируете те, на которых прямо написа#
но «ПРЕДУПРЕЖДЕНИЕ».
Почему внимание к интеллектуальным предупреждающим знакам особенно важ#
но при разработке ПО? Качество мышления, воплощаемое в программе, во мно#
гом определяет качество самой программы, поэтому внимание к предупреждениям
о качестве мышления напрямую влияет на итоговый продукт.
830 ЧАСТЬ VII Мастерство программирования
Оракулы программирования
Увы, догматиками бывают и некоторые из наиболее уважа#
Перекрестная ссылка О том, как
емых людей в нашей отрасли. Конечно, инновационные руководителям следует подхо-
методики нужно разглашать, чтобы их могли попробовать дить к религии программирова-
практикующие разработчики. До того как эффективность ния, см. подраздел «Вопросы
методик можно будет полностью доказать или опровергнуть, религии» раздела 28.5.
их нужно испытать. Распространение результатов исследо#
ваний на практикующих специалистов называется «передачей технологий» и иг#
рает важную роль в совершенствовании способов разработки ПО. Однако распро#
странение новой методологии нужно отличать от продажи чудодейственного
средства от всех болезней программирования. Передача технологий — это совсем
не то, чем занимаются торговцы догматичными методологиями, пытающиеся
убедить вас в том, что их высокотехнологичная новинка решит все проблемы всех
программистов во Вселенной. Забудьте обо всем, что вы изучили, потому что эта
великолепная новая методика повысит производительность вашего труда в два раза
во всех областях!
Вместо чрезмерного увлечения модными штучками используйте смесь методик.
Экспериментируйте с интересными новыми методиками, но делайте ставку на
старые и надежные.
Эклектизм
Слепая вера в одну методику подавляет возможность выбо#
Перекрестная ссылка О разли-
ра, нужную для обнаружения наиболее эффективных реше# чии между алгоритмическим и
ний проблем. Будь разработка ПО детерминированным ал# эвристическим подходами см.
горитмическим процессом, вы могли бы следовать жесткой раздел 2.2, об эклектизме при
методологии. Однако разработка ПО — не детерминирован# проектировании — подраздел
«Используйте итерацию» разде-
ный, а эвристический процесс, а значит, жесткие подходы
ла 5.4.
редко приводят к успеху. Например, при проектировании
иногда хорошо работает нисходящая декомпозиция, но
иногда лучшим вариантом будет объектно#ориентированный подход, восходящая
композиция или подход, основанный на структурах данных. Вы должны быть
готовы к испытанию нескольких подходов, зная, что некоторые окажутся неэф#
фективными, а какие#то приведут к успеху, но не зная заранее, какие из них ка#
кие. Вам следует быть эклектичным.
Приверженность одной методике вредна и тем, что она заставляет подгонять
проблему под решение. Выбирая методику решения до полного понимания про#
блемы, вы слишком спешите. Чрезмерное ограничение набора возможных реше#
ний может исключить из области видимости наиболее эффективное решение.
832 ЧАСТЬ VII Мастерство программирования
Экспериментирование
Эклектизм тесно связан с экспериментированием. Вам нужно экспериментиро#
вать на всем протяжении процесса разработки, но непреклонность подавляет этот
импульс. Чтобы экспериментирование было эффективным, вы должны охотно
изменять свои убеждения на основе результатов экспериментов — иначе экспе#
риментирование становится пустой тратой времени.
Многие негибкие подходы к разработке ПО основаны на страхе допустить ошибку,
но нет ошибки серьезнее, чем глобальное стремление избежать ошибок. Проек#
тирование — это процесс тщательного планирования мелких ошибок с целью
предотвращения крупных. Экспериментирование при разработке ПО позволяет
узнать, эффективен ли тот или иной подход, — сам эксперимент является успе#
хом, если он решает проблему.
ГЛАВА 34 Основы мастерства 833
Ключевые моменты
Главная цель программирования — управление сложностью.
Процесс программирования оказывает большое влияние на итоговый продукт.
Групповое программирование является в большей степени общением с други#
ми людьми, а не с компьютером. Индивидуальное программирование — это в
первую очередь общение с самим собой, а не с компьютером.
При неадекватном использовании конвенция программирования может ока#
заться лекарством, причиняющим больше вреда, чем болезнь; при грамотном
— конвенция добавляет ценную структуру в среду разработки, помогает управ#
лять сложностью и облегчает общение.
Программирование в терминах проблемы, а не решения помогает управлять
сложностью.
Внимание к интеллектуальным предупреждающим знакам вроде сомнения
особенно важно в программировании, потому что программирование — по#
чти исключительно умственная деятельность.
Чем больше внимания итерации вы уделяете на конкретном этапе разработ#
ки, тем лучше будет результат этого этапа.
Догматичные методологии и разработка высококачественного ПО исключают
друг друга. Заполняйте свой интеллектуальный инструментарий альтернатив#
ными подходами к программированию и улучшайте навык выбора инструмента,
лучше всего подходящего для работы.
834 ЧАСТЬ VII Мастерство программирования
Г Л А В А 3 5
Содержание
http://cc2e.com/3560
35.1. Информация о конструировании ПО
35.2. Не связанные с конструированием темы
35.3. Периодические издания
35.4. Список литературы для разработчика ПО
35.5. Профессиональные ассоциации
Связанные темы
Web#ресурсы: www.cc2e.com
Если вы так далеко продвинулись в чтении этой книги, то уже знаете, как много
написано о практике эффективной разработки ПО. Доступной информации гораздо
больше, чем можно представить. Все ошибки, которые вы делаете сейчас, люди уже
сделали до вас. И если вы не хотите стать мальчиком для битья, то предпочтете читать
их книги, чтобы не повторять их ошибки и не изобретать велосипед.
Поскольку в этой книге упоминаются сотни других книг и статей по разработке
ПО, трудно сказать, с чего начать чтение. Библиотека программиста включает в
себя несколько видов информации. Основу составляют книги, объясняющие фун#
даментальные концепции эффективного программирования. В других более под#
робно рассматриваются технические, управленческие, интеллектуальные проблемы
программирования. Подробные справочники по языкам, операционным системам,
средам разработки и аппаратному обеспечению содержат информацию, полез#
ную для конкретных проектов.
Обычно книги последней категории представляют интерес
http://cc2e.com/3581в рамках одного проекта; в большей или меньшей степени
они являются временными и здесь не обсуждаются. Что ка#
сается других категорий, полезно иметь библиотечку, в которой основные виды
деятельности по разработке ПО обсуждаются более глубоко: книги по выработке
требований, конструированию, проектированию, управлению, тестированию и т. д.
В следующих разделах подробно описываются ресурсы по конструированию, а
ГЛАВА 35 Где искать дополнительную информацию 835
Обзорный материал
Ряд книг позволяет взглянуть на процесс разработки ПО с
http://cc2e.com/3595 разных сторон.
Роберт Л. Гласс в работе «Facts and Fallacies of Software Engi#
neering» (Glass, 2003) предлагает интересную трактовку традиционных представ#
лений о возможном и невозможном в разработке ПО. Книга содержит много ука#
зателей на дополнительные ресурсы.
В книге «Professional Sofware Development» (2004) я рассматриваю практику раз#
работки ПО в ее современном виде и размышляю о том, какой она должна быть в
идеале.
Книга «The Swebok: Guide to the Software Engineering Body of Knowledge» (Abran,
2001) разделяет на составляющие прикладную отрасль знаний, которая занима#
ется оптимизацией и повышением эффективности разработки ПО, и погружает
в детали конструирования ПО. Именно этот труд показывает, что в нашей облас#
ти значительно больше достижений, чем можно представить.
Книга Джеральда Вейнберга «The Psychology of Computer Programming» (Weinberg,
1998) наполнена замечательными анекдотами о программировании. Она несколько
устарела, так как написана во времена, когда программирование считалось един#
ственным аспектом создания ПО. Совет, прозвучавший при первом обсуждении
этой книги в «ACM Computing Reviews», актуален и сегодня:
«Каждый руководитель программистов должен иметь собственный экземпляр этой
книги. Он должен читать ее, хранить у сердца, следовать ее наставлениям, а затем
оставлять на своем столе, чтобы подчиненные могли ее украсть. Взамен украден#
ным экземплярам следует подбрасывать новые, пока все не успокоятся». (Weiss, 1972).
Если вы не смогли найти «The Psychology of Computer Programming», ищите «The
Mythical Man#Month» (Brooks, 1995) или «PeopleWare» (DeMarco and Lister, 1999).
Обе книги убеждают в том, что программирование — прежде всего результат че#
ловеческой деятельности и лишь во вторую очередь — нечто связанное с компь#
ютерами.
ГЛАВА 35 Где искать дополнительную информацию 837
Специализированные публикации
Часть периодики более глубоко рассматривает специальные темы.
Профессиональные издания
Компьютерная ассоциация IEEE выпускает специализирован#
ные журналы по следующим вопросам: проектирование ПО, http://cc2e.com/3551
безопасность и защита информации, компьютерная графи#
ка и анимация, Интернет#технологии, мультимедиа, интеллектуальные системы,
история вычислительной техники и др. Подробнее см. по адресу www.computer.org.
ACM также публикует специальные выпуски по таким раз#
делам, как искусственный интеллект, взаимодействие чело# http://cc2e.com/3558
Вводный курс
Чтобы пройти «начальный» уровень по методике Construx, необходимо прочесть
следующие книги.
Adams, James L. Conceptual Blockbusting: A Guide to Better Ideas, 4th ed. Cambridge,
MA: Perseus Publishing, 2001.
840 ЧАСТЬ VII Мастерство программирования
Библиография
Baker, F. Terry, and Harlan D. Mills. 1973. «Chief Programmer Teams.» Datamation 19, no. 12
(December): 58–61.
Barbour, Ian G. 1966. Issues in Science and Religion. New York, NY: Harper & Row.
Barbour, Ian G. 1974. Myths, Models, and Paradigms: A Comparative Study in Science and Religion.
New York, NY: Harper & Row.
Barwell, Fred, et al. 2002. Professional VB.NET, 2d ed. Birmingham, UK: Wrox.
Basili, V. R., and B. T. Perricone. 1984. «Software Errors and Complexity: An Empirical Investigation.»
Communications of the ACM 27, no. 1 (January): 42–52.
Basili, Victor R., and Albert J. Turner. 1975. «Iterative Enhancement: A Practical Technique for Software
Development.» IEEE Transactions on Software Engineering SE#1, no. 4 (December): 390–96.
Basili, Victor R., and David M. Weiss. 1984. «A Methodology for Collecting Valid Software Engi#
neering Data.» IEEE Transactions on Software Engineering SE#10, no. 6 (November): 728–38.
Basili, Victor R., and Richard W. Selby. 1987. «Comparing the Effectiveness of Software Testing
Strategies.» IEEE Transactions on Software Engineering SE#13, no. 12 (December): 1278–96.
Basili, Victor R., et al. 2002. «Lessons learned from 25 years of process improvement: The Rise
and Fall of the NASA Software Engineering Laboratory,» Proceedings of the 24th International
Conference on Software Engineering, Orlando, FL.
Basili, Victor R., Richard W. Selby, and David H. Hutchens. 1986. «Experimentation in Software
Engineering.» IEEE Transactions on Software Engineering SE#12, no. 7 (July): 733–43.
Basili, Victor, L. Briand, and W.L. Melo. 1996. «A Validation of Object#Oriented Design Metrics as
Quality Indicators,» IEEE Transactions on Software Engineering, October 1996, 751–761.
Bass, Len, Paul Clements, and Rick Kazman. 2003. Software Architecture in Practice, 2d ed. Boston,
MA: Addison#Wesley.
Bastani, Farokh, and Sitharama Iyengar. 1987. «The Effect of Data Structures on the Logical
Complexity of Programs.» Communications of the ACM 30, no. 3 (March): 250–59.
Bays, Michael. 1999. Software Release Methodology. Englewood Cliffs, NJ: Prentice Hall.
Beck, Kent. 2000. Extreme Programming Explained: Embrace Change. Reading, MA: Addison#Wesley.
Beck, Kent. 2003. Test%Driven Development: By Example. Boston, MA: Addison#Wesley.
Beck, Kent. 1991. «Think Like An Object.» Unix Review 9, no. 10 (October): 39–43.
Beck, Leland L., and Thomas E. Perkins. 1983. «A Survey of Software Engineering Practice: Tools, Methods,
and Results.» IEEE Transactions on Software Engineering SE#9, no. 5 (September): 541–61.
Beizer, Boris. 1990. Software Testing Techniques, 2d ed. New York, NY: Van Nostrand Reinhold.
Bentley, Jon, and Donald Knuth. 1986. «Literate Programming.» Communications of the ACM 29,
no. 5 (May): 364–69.
Bentley, Jon, Donald Knuth, and Doug McIlroy. 1986. «A Literate Program.» Communications of
the ACM 29, no. 5 (May): 471–83.
Bentley, Jon. 1982. Writing Efficient Programs. Englewood Cliffs, NJ: Prentice Hall.
Bentley, Jon. 1988. More Programming Pearls: Confessions of a Coder. Reading, MA: Addison#Wesley.
Bentley, Jon. 1991. «Software Exploratorium: Writing Efficient C Programs.» Unix Review 9, no. 8
(August): 62–73.
Bentley, Jon. 2000. Programming Pearls, 2d ed. Reading, MA: Addison#Wesley.
Berczuk, Stephen P. and Brad Appleton. 2003. Software Configuration Management Patterns:
Effective Teamwork, Practical Integration. Boston, MA: Addison#Wesley.
Berry, R. E., and B. A. E. Meekings. 1985. «A Style Analysis of C Programs.» Communications of the
ACM 28, no. 1 (January): 80–88.
844 Библиография
Booth, Rick. 1997. Inner Loops : A Sourcebook for Fast 32#bit Software Development. Boston,
MA: Addison#Wesley.
Boundy, David. 1991. «A Taxonomy of Programmers.» ACM SIGSOFT Software Engineering Notes
16, no. 4 (October): 23–30.
Brand, Stewart. 1995. How Buildings Learn: What Happens After They’re Built. Penguin USA.
Branstad, Martha A., John C. Cherniavsky, and W. Richards Adrion. 1980. «Validation, Verification,
and Testing for the Individual Programmer.» Computer, December, 24–30.
Brockmann, R. John. 1990. Writing Better Computer User Documentation: From Paper to Hyper#
text: Version 2.0. New York, NY: John Wiley & Sons.
Brooks, Frederick P., Jr. 1987. «No Silver Bullets—Essence and Accidents of Software Engineering.»
Computer, April, 10–19.
Brooks, Frederick P., Jr. 1995. The Mythical Man#Month: Essays on Software Engineering, Anniversary
Edition (2d ed.). Reading, MA: Addison#Wesley.
Brooks, Ruven. 1977. «Towards a Theory of the Cognitive Processes in Computer Programming.»
International Journal of Man%Machine Studies 9: 737–51.
Brooks, W. Douglas. 1981. «Software Technology Payoff—Some Statistical Evidence.» The Journal
of Systems and Software 2: 3–9.
Brown, A. R., and W. A. Sampson. 1973. Program Debugging. New York, NY: American Elsevier.
Buschman, Frank, et al. 1996. Pattern#Oriented Software Architecture, Volume 1: A System of
Patterns. New York, NY: John Wiley & Sons.
Bush, Marilyn, and John Kelly. 1989. «The Jet Propulsion Laboratory’s Experience with Formal
Inspections.» Proceedings of the Fourteenth Annual Software Engineering Workshop, November 29,
1989. Greenbelt, MD: Goddard Space Flight Center. Document SEL#89#007.
Caine, S. H., and E. K. Gordon. 1975. «PDL—A Tool for Software Design.» AFIPS Proceedings of the
1975 National Computer Conference 44. Montvale, NJ: AFIPS Press, 271–76.
Card, David N. 1987. «A Software Technology Evaluation Program.» Information and Software
Technology 29, no. 6 (July/August): 291–300.
Card, David N., Frank E. McGarry, and Gerald T. Page. 1987. «Evaluating Software Engineering
Technologies.» IEEE Transactions on Software Engineering SE#13, no. 7 (July): 845–51.
Card, David N., Victor E. Church, and William W. Agresti. 1986. «An Empirical Study of Software
Design Practices.» IEEE Transactions on Software Engineering SE#12, no. 2 (February): 264–71.
Card, David N., with Robert L. Glass. 1990. Measuring Software Design Quality. Englewood Cliffs,
NJ: Prentice Hall.
Card, David, Gerald Page, and Frank McGarry. 1985. «Criteria for Software Modularization.»
Proceedings of the 8th International Conference on Software Engineering. Washington, DC: IEEE
Computer Society Press, 372–77.
Carnegie, Dale. 1981. How to Win Friends and Influence People, Revised Edition. New York, NY:
Pocket Books.
Chase, William G., and Herbert A. Simon. 1973. «Perception in Chess.» Cognitive Psychology 4: 55–81.
Clark, R. Lawrence. 1973. «A Linguistic Contribution of GOTO#less Programming,» Datamation,
December 1973.
Clements, Paul, ed. 2003. Documenting Software Architectures: Views and Beyond. Boston, MA:
Addison#Wesley.
Clements, Paul, Rick Kazman, and Mark Klein. 2002. Evaluating Software Architectures: Methods
and Case Studies. Boston, MA: Addison#Wesley.
Coad, Peter, and Edward Yourdon. 1991. Object%Oriented Design. Englewood Cliffs, NJ: Yourdon Press.
846 Библиография
Cobb, Richard H., and Harlan D. Mills. 1990. «Engineering Software Under Statistical Quality
Control.» IEEE Software 7, no. 6 (November): 45–54.
Cockburn, Alistair. 2000. Writing Effective Use Cases. Boston, MA: Addison#Wesley.
Cockburn, Alistair. 2002. Agile Software Development. Boston, MA: Addison#Wesley.
Collofello, Jim, and Scott Woodfield. 1989. «Evaluating the Effectiveness of Reliability Assurance
Techniques.» Journal of Systems and Software 9, no. 3 (March).
Comer, Douglas. 1981. «Principles of Program Design Induced from Experience with Small Public
Programs.» IEEE Transactions on Software Engineering SE#7, no. 2 (March): 169–74.
Constantine, Larry L. 1990a. «Comments on ‘On Criteria for Module Interfaces.’» IEEE Transactions
on Software Engineering SE#16, no. 12 (December): 1440.
Constantine, Larry L. 1990b. «Objects, Functions, and Program Extensibility.» Computer Language,
January, 34–56.
Conte, S. D., H. E. Dunsmore, and V. Y. Shen. 1986. Software Engineering Metrics and Models. Menlo
Park, CA: Benjamin/ Cummings.
Cooper, Doug, and Michael Clancy. 1982. Oh! Pascal! 2d ed. New York, NY: Norton.
Cooper, Kenneth G. and Thomas W. Mullen. 1993. «Swords and Plowshares: The Rework Cycles
of Defense and Commercial Software Development Projects,» American Programmer, May 1993,
41–51.
Corbatу, Fernando J. 1991. «On Building Systems That Will Fail.» 1991 Turing Award Lecture.
Communications of the ACM 34, no. 9 (September): 72–81.
Cornell, Gary and Jonathan Morrison. 2002. Programming VB .NET: A Guide for Experienced
Programmers, Berkeley, CA: Apress.
Corwin, Al. 1991. Private communication.
CSTB 1990. «Scaling Up: A Research Agenda for Software Engineering.» Excerpts from a report
by the Computer Science and Technology Board. Communications of the ACM 33, no. 3 (March):
281–93.
Curtis, Bill, ed. 1985. Tutorial: Human Factors in Software Development. Los Angeles, CA: IEEE
Computer Society Press.
Curtis, Bill, et al. 1986. «Software Psychology: The Need for an Interdisciplinary Program.»
Proceedings of the IEEE 74, no. 8: 1092–1106.
Curtis, Bill, et al. 1989. «Experimentation of Software Documentation Formats.» Journal of Systems
and Software 9, no. 2 (February): 167–207.
Curtis, Bill, H. Krasner, and N. Iscoe. 1988. «A Field Study of the Software Design Process for Large
Systems.» Communications of the ACM 31, no. 11 (November): 1268–87.
Curtis, Bill. 1981. «Substantiating Programmer Variability.» Proceedings of the IEEE 69, no. 7: 846.
Cusumano, Michael and Richard W. Selby. 1995. Microsoft Secrets. New York, NY: The Free Press.
Cusumano, Michael, et al. 2003. «Software Development Worldwide: The State of the Practice,»
IEEE Software, November/December 2003, 28–34.
Dahl, O. J., E. W. Dijkstra, and C. A. R. Hoare. 1972. Structured Programming. New York, NY: Academic
Press.
Date, Chris. 1977. An Introduction to Database Systems. Reading, MA: Addison#Wesley.
Davidson, Jack W., and Anne M. Holler. 1992. «Subprogram Inlining: A Study of Its Effects on
Program Execution Time.» IEEE Transactions on Software Engineering SE#18, no. 2 (February):
89–102.
Davis, P. J. 1972. «Fidelity in Mathematical Discourse: Is One and One Really Two?» American
Mathematical Monthly, March, 252–63.
Библиография 847
DeGrace, Peter, and Leslie Stahl. 1990. Wicked Problems, Righteous Solutions: A Catalogue of
Modern Software Engineering Paradigms. Englewood Cliffs, NJ: Yourdon Press.
DeMarco, Tom and Timothy Lister. 1999. Peopleware: Productive Projects and Teams, 2d ed. New
York, NY: Dorset House.
DeMarco, Tom, and Timothy Lister. 1985. «Programmer Performance and the Effects of the
Workplace.» Proceedings of the 8th International Conference on Software Engineering. Washington,
DC: IEEE Computer Society Press, 268–72.
DeMarco, Tom. 1979. Structured Analysis and Systems Specification: Tools and Techniques.
Englewood Cliffs, NJ: Prentice Hall.
DeMarco, Tom. 1982. Controlling Software Projects. New York, NY: Yourdon Press.
DeMillo, Richard A., Richard J. Lipton, and Alan J. Perlis. 1979. «Social Processes and Proofs of
Theorems and Programs.» Communications of the ACM 22, no. 5 (May): 271–80.
Dijkstra, Edsger. 1965. «Programming Considered as a Human Activity.» Proceedings of the 1965
IFIP Congress. Amsterdam: North#Holland, 213–17. Reprinted in Yourdon 1982.
Dijkstra, Edsger. 1968. «Go To Statement Considered Harmful.» Communications of the ACM 11,
no. 3 (March): 147–48.
Dijkstra, Edsger. 1969. «Structured Programming.» Reprinted in Yourdon 1979.
Dijkstra, Edsger. 1972. «The Humble Programmer.» Communications of the ACM 15, no. 10 (October):
859–66.
Dijkstra, Edsger. 1985. «Fruits of Misunderstanding.» Datamation, February 15, 86– 87.
Dijkstra, Edsger. 1989. «On the Cruelty of Really Teaching Computer Science.» Communications
of the ACM 32, no. 12 (December): 1397–1414.
Dunn, Robert H. 1984. Software Defect Removal. New York, NY: McGraw#Hill.
Ellis, Margaret A., and Bjarne Stroustrup. 1990. The Annotated C++ Reference Manual. Boston, MA:
Addison#Wesley.
Elmasri, Ramez, and Shamkant B. Navathe. 1989. Fundamentals of Database Systems. Redwood
City, CA: Benjamin/Cummings.
Elshoff, James L. 1976. «An Analysis of Some Commercial PL/I Programs.» IEEE Transactions on
Software Engineering SE#2, no. 2 (June): 113–20.
Elshoff, James L. 1977. «The Influence of Structured Programming on PL/I Program Profiles.» IEEE
Transactions on Software Engineering SE#3, no. 5 (September): 364–68.
Elshoff, James L., and Michael Marcotty. 1982. «Improving Computer Program Readability to Aid
Modification.» Communications of the ACM 25, no. 8 (August): 512–21.
Endres, Albert. 1975. «An Analysis of Errors and Their Causes in System Programs.» IEEE Transactions
on Software Engineering SE#1, no. 2 (June): 140–49.
Evangelist, Michael. 1984. «Program Complexity and Programming Style.» Proceedings of the First
International Conference on Data Engineering. New York, NY: IEEE Computer Society Press, 534–41.
Fagan, Michael E. 1976. «Design and Code Inspections to Reduce Errors in Program Development.»
IBM Systems Journal 15, no. 3: 182–211.
Fagan, Michael E. 1986. «Advances in Software Inspections.» IEEE Transactions on Software
Engineering SE#12, no. 7 (July): 744–51.
Federal Software Management Support Center. 1986. Programmers Work%bench Handbook. Falls
Church, VA: Office of Software Development and Information Technology.
Feiman, J., and M. Driver. 2002. «Leading Programming Languages for IT Portfolio Planning,»
Gartner Research report SPA#17#6636, September 27, 2002.
848 Библиография
Fetzer, James H. 1988. «Program Verification: The Very Idea.» Communications of the ACM 31, no.
9 (September): 1048–63.
FIPS PUB 38, Guidelines for Documentation of Computer Programs and Automated Data Systems.
1976. U.S. Department of Commerce. National Bureau of Standards. Washington, DC: U.S. Govern#
ment Printing Office, Feb. 15.
Fishman, Charles. 1996. «They Write the Right Stuff,» Fast Company, December 1996.
Fjelstad, R. K., and W. T. Hamlen. 1979. «Applications Program Maintenance Study: Report to our
Respondents.» Proceedings Guide 48, Philadelphia. Reprinted in Tutorial on Software Maintenance,
G. Parikh and N. Zvegintzov, eds. Los Alamitos, CA: CS Press, 1983: 13–27.
Floyd, Robert. 1979. «The Paradigms of Programming.» Communications of the ACM 22, no. 8
(August): 455–60.
Fowler, Martin. 1999. Refactoring: Improving the Design of Existing Code. Reading, MA: Addison#
Wesley.
Fowler, Martin. 2002. Patterns of Enterprise Application Architecture. Boston, MA: Addison#Wesley.
Fowler, Martin. 2003. UML Distilled: A Brief Guide to the Standard Object Modeling Language,
3d ed. Boston, MA: Addison#Wesley.
Fowler, Martin. 2004. UML Distilled, 3d ed. Boston, MA: Addison#Wesley.
Fowler, Priscilla J. 1986. «In#Process Inspections of Work Products at AT&T.» AT&T Technical Journal,
March/April, 102–12.
Foxall, James. 2003. Practical Standards for Microsoft Visual Basic .NET. Redmond, WA: Microsoft
Press.
Freedman, Daniel P., and Gerald M. Weinberg. 1990. Handbook of Walkthroughs, Inspections and
Technical Reviews, 3d ed. New York, NY: Dorset House.
Freeman, Peter, and Anthony I. Wasserman, eds. 1983. Tutorial on Software Design Techniques,
4th ed. Silver Spring, MD: IEEE Computer Society Press.
Gamma, Erich, et al. 1995. Design Patterns. Reading, MA: Addison#Wesley.
Gerber, Richard. 2002. Software Optimization Cookbook: High#Performance Recipes for the Intel
Architecture. Intel Press.
Gibson, Elizabeth. 1990. «Objects—Born and Bred.» BYTE, October, 245–54.
Gilb, Tom, and Dorothy Graham. 1993. Software Inspection. Wokingham, England: Addison#Wesley.
Gilb, Tom. 1977. Software Metrics. Cambridge, MA: Winthrop.
Gilb, Tom. 1988. Principles of Software Engineering Management. Wokingham, England: Addison#
Wesley.
Gilb, Tom. 2004. Competitive Engineering. Boston, MA: Addison#Wesley. Downloadable from
www.result%planning.com.
Ginac, Frank P. 1998. Customer Oriented Software Quality Assurance. Englewood Cliffs, NJ: Prentice
Hall.
Glass, Robert L. 1982. Modern Programming Practices: A Report from Industry. Englewood Cliffs,
NJ: Prentice Hall.
Glass, Robert L. 1988. Software Communication Skills. Englewood Cliffs, NJ: Prentice Hall.
Glass, Robert L. 1991. Software Conflict: Essays on the Art and Science of Software Engineering.
Englewood Cliffs, NJ: Yourdon Press.
Glass, Robert L. 1995. Software Creativity. Reading, MA: Addison#Wesley.
Glass, Robert L. 1999. «Inspections—Some Surprising Findings,» Communications of the ACM, April
1999, 17–19.
Библиография 849
Glass, Robert L. 1999. «The realities of software technology payoffs,» Communications of the ACM,
February 1999, 74–79.
Glass, Robert L. 2003. Facts and Fallacies of Software Engineering. Boston, MA: Addison#Wesley.
Glass, Robert L., and Ronald A. Noiseux. 1981. Software Maintenance Guidebook. Englewood Cliffs,
NJ: Prentice Hall.
Gordon, Ronald D. 1979. «Measuring Improvements in Program Clarity.» IEEE Transactions on
Software Engineering SE#5, no. 2 (March): 79–90.
Gordon, Scott V., and James M. Bieman. 1991. «Rapid Prototyping and Software Quality: Lessons
from Industry.» Ninth Annual Pacific Northwest Software Quality Conference, October 7–8. Oregon
Convention Center, Portland, OR.
Gorla, N., A. C. Benander, and B. A. Benander. 1990. «Debugging Effort Estimation Using Software
Metrics.» IEEE Transactions on Software Engineering SE#16, no. 2 (February): 223–31.
Gould, John D. 1975. «Some Psychological Evidence on How People Debug Computer Programs.»
International Journal of Man%Machine Studies 7: 151–82.
Grady, Robert B. 1987. «Measuring and Managing Software Maintenance.» IEEE Software 4, no. 9
(September): 34–45.
Grady, Robert B. 1993. «Practical Rules of Thumb for Software Managers.» The Software Practitioner
3, no. 1 (January/February): 4–6.
Grady, Robert B. 1999. «An Economic Release Decision Model: Insights into Software Project
Management.» In Proceedings of the Applications of Software Measurement Conference, 227–239.
Orange Park, FL: Software Quality Engineering.
Grady, Robert B., and Tom Van Slack. 1994. «Key Lessons in Achieving Widespread Inspection
Use,» IEEE Software, July 1994.
Grady, Robert B. 1992. Practical Software Metrics For Project Management And Process Impro#
vement. Englewood Cliffs, NJ: Prentice Hall.
Grady, Robert B., and Deborah L. Caswell. 1987. Software Metrics: Establishing a Company%Wide
Program. Englewood Cliffs, NJ: Prentice Hall.
Green, Paul. 1987. «Human Factors in Computer Systems, Some Useful Readings.» Sigchi Bulletin
19, no. 2: 15–20.
Gremillion, Lee L. 1984. «Determinants of Program Repair Maintenance Requirements.» Communi%
cations of the ACM 27, no. 8 (August): 826–32.
Gries, David. 1981. The Science of Programming. New York, NY: Springer#Verlag.
Grove, Andrew S. 1983. High Output Management. New York, NY: Random House.
Haley, Thomas J. 1996. «Software Process Improvement at Raytheon.» IEEE Software, November
1996.
Hansen, John C., and Roger Yim. 1987. «Indentation Styles in C.» SIGSMALL/PC Notes 13, no. 3
(August): 20–23.
Hanson, Dines. 1984. Up and Running. New York, NY: Yourdon Press.
Harrison, Warren, and Curtis Cook. 1986. «Are Deeply Nested Conditionals Less Readable?» Journal
of Systems and Software 6, no. 4 (November): 335–42.
Hasan, Jeffrey and Kenneth Tu. 2003. Performance Tuning and Optimizing ASP.NET Applications.
Apress.
Hass, Anne Mette Jonassen. 2003. Configuration Management Principles and Practices, Boston,
MA: Addison#Wesley.
Hatley, Derek J., and Imtiaz A. Pirbhai. 1988. Strategies for Real%Time System Specification. New
York, NY: Dorset House.
850 Библиография
Hecht, Alan. 1990. «Cute Object#oriented Acronyms Considered FOOlish.» Software Engineering
Notes, January, 48.
Heckel, Paul. 1994. The Elements of Friendly Software Design. Alameda, CA: Sybex.
Hecker, Daniel E. 2001. «Occupational Employment Projections to 2010.» Monthly Labor Review,
November 2001.
Hecker, Daniel E. 2004. «Occupational Employment Projections to 2012.» Monthly Labor Review,
February 2004, Vol. 127, No. 2, pp. 80#105.
Henry, Sallie, and Dennis Kafura. 1984. «The Evaluation of Software Systems’ Structure Using
Quantitative Software Metrics.» Software—Practice and Experience 14, no. 6 (June): 561–73.
Hetzel, Bill. 1988. The Complete Guide to Software Testing, 2d ed. Wellesley, MA: QED Information
Systems.
Highsmith, James A., III. 2000. Adaptive Software Development: A Collaborative Approach to
Managing Complex Systems. New York, NY: Dorset House.
Highsmith, Jim. 2002. Agile Software Development Ecosystems. Boston, MA: Addison#Wesley.
Hildebrand, J. D. 1989. «An Engineer’s Approach.» Computer Language, October, 5–7.
Hoare, Charles Anthony Richard, 1981. «The Emperor’s Old Clothes.» Communications of the ACM,
February 1981, 75–83.
Hollocker, Charles P. 1990. Software Reviews and Audits Handbook. New York, NY: John Wiley &
Sons.
Houghton, Raymond C. 1990. «An Office Library for Software Engineering Professionals.» Software
Engineering: Tools, Techniques, Practice, May/June, 35–38.
Howard, Michael, and David LeBlanc. 2003. Writing Secure Code, 2d ed. Redmond, WA: Microsoft
Press.
Hughes, Charles E., Charles P. Pfleeger, and Lawrence L. Rose. 1978. Advanced Programming
Techniques: A Second Course in Programming Using Fortran. New York, NY: John Wiley & Sons.
Humphrey, Watts S. 1989. Managing the Software Process. Reading, MA: Addison#Wesley.
Humphrey, Watts S. 1995. A Discipline for Software Engineering. Reading, MA: Addison#Wesley.
Humphrey, Watts S., Terry R. Snyder, and Ronald R. Willis. 1991. «Software Process Improvement
at Hughes Aircraft.» IEEE Software 8, no. 4 (July): 11–23.
Humphrey, Watts. 1997. Introduction to the Personal Software Process. Reading, MA: Addison#Wesley.
Humphrey, Watts. 2002. Winning with Software: An Executive Strategy. Boston, MA: Addison#Wesley.
Hunt, Andrew, and David Thomas. 2000. The Pragmatic Programmer. Boston, MA: Addison#Wesley.
Ichbiah, Jean D., et al. 1986. Rationale for Design of the Ada Programming Language. Minneapolis,
MN: Honeywell Systems and Research Center.
IEEE Software 7, no. 3 (May 1990).
IEEE Std 1008#1987 (R1993), Standard for Software Unit Testing
IEEE Std 1016#1998, Recommended Practice for Software Design Descriptions
IEEE Std 1028#1997, Standard for Software Reviews
IEEE Std 1045#1992, Standard for Software Productivity Metrics
IEEE Std 1058#1998, Standard for Software Project Management Plans
IEEE Std 1061#1998, Standard for a Software Quality Metrics Methodology
IEEE Std 1062#1998, Recommended Practice for Software Acquisition
IEEE Std 1063#2001, Standard for Software User Documentation
Библиография 851
IEEE Std 1074#1997, Standard for Developing Software Life Cycle Processes
IEEE Std 1219#1998, Standard for Software Maintenance
IEEE Std 1233#1998, Guide for Developing System Requirements Specifications
IEEE Std 1233#1998. IEEE Guide for Developing System Requirements Specifications
IEEE Std 1471#2000. Recommended Practice for Architectural Description of Software Intensive
Systems
IEEE Std 1490#1998, Guide # Adoption of PMI Standard # A Guide to the Project Management
Body of Knowledge
IEEE Std 1540#2001, Standard for Software Life Cycle Processes # Risk Management
IEEE Std 730#2002, Standard for Software Quality Assurance Plans
IEEE Std 828#1998, Standard for Software Configuration Management Plans
IEEE Std 829#1998, Standard for Software Test Documentation
IEEE Std 830#1998, Recommended Practice for Software Requirements Specifications
IEEE Std 830%1998. IEEE Recommended Practice for Software Requirements Specifications. Los
Alamitos, CA: IEEE Computer Society Press.
IEEE, 1991. IEEE Software Engineering Standards Collection, Spring 1991 Edition. New York, NY:
Institute of Electrical and Electronics Engineers.
IEEE, 1992. «Rear Adm. Grace Hopper dies at 85.» IEEE Computer, February, 84.
Ingrassia, Frank S. 1976. «The Unit Development Folder (UDF): An Effective Management Tool
for Software Development.» TRW Technical Report TRW#SS#76#11. Also reprinted in Reifer 1986,
366–79.
Ingrassia, Frank S. 1987. «The Unit Development Folder (UDF): A Ten#Year Perspective.» Tutorial:
Software Engineering Project Management, ed. Richard H. Thayer. Los Alamitos, CA: IEEE Computer
Society Press, 405–15.
Jackson, Michael A. 1975. Principles of Program Design. New York, NY: Academic Press.
Jacobson, Ivar, Grady Booch, and James Rumbaugh. 1999. The Unified Software Development Process.
Reading, MA: Addison#Wesley.
Johnson, Jim. 1999. «Turning Chaos into Success,» Software Magazine, December 1999, 30–39.
Johnson, Mark. 1994a. «Dr. Boris Beizer on Software Testing: An Interview Part 1,» The Software
QA Quarterly, Spring 1994, 7–13.
Johnson, Mark. 1994b. «Dr. Boris Beizer on Software Testing: An Interview Part 2,» The Software
QA Quarterly, Summer 1994, 41–45.
Johnson, Walter L. 1987. «Some Comments on Coding Practice.» ACM SIGSOFT Software Engineering
Notes 12, no. 2 (April): 32–35.
Jones, T. Capers. 1977. «Program Quality and Programmer Productivity.» IBM Technical Report
TR 02.764, January, 42–78. Also in Jones 1986b.
Jones, Capers. 1986a. Programming Productivity. New York, NY: McGraw#Hill.
Jones, T. Capers, ed. 1986b. Tutorial: Programming Productivity: Issues for the Eighties, 2d ed. Los
Angeles, CA: IEEE Computer Society Press.
Jones, Capers. 1996. «Software Defect#Removal Efficiency,» IEEE Computer, April 1996.
Jones, Capers. 1997. Applied Software Measurement: Assuring Productivity and Quality, 2d ed.
New York, NY: McGraw#Hill.
Jones, Capers. 1998. Estimating Software Costs. New York, NY: McGraw#Hill.
852 Библиография
Jones, Capers. 2000. Software Assessments, Benchmarks, and Best Practices. Reading, MA: Addison#
Wesley.
Jones, Capers. 2003. «Variations in Software Development Practices,» IEEE Software, November/
December 2003, 22–27.
Jonsson, Dan. 1989. «Next: The Elimination of GoTo#Patches?» ACM Sigplan Notices 24, no. 3
(March): 85–92.
Kaelbling, Michael. 1988. «Programming Languages Should NOT Have Comment Statements.»
ACM Sigplan Notices 23, no. 10 (October): 59–60.
Kaner, Cem, Jack Falk, and Hung Q. Nguyen. 1999. Testing Computer Software, 2d ed. New York,
NY: John Wiley & Sons.
Kaner, Cem, James Bach, and Bret Pettichord. 2002. Lessons Learned in Software Testing. New
York, NY: John Wiley & Sons.
Keller, Daniel. 1990. «A Guide to Natural Naming.» ACM Sigplan Notices 25, no. 5 (May): 95–102.
Kelly, John C. 1987. «A Comparison of Four Design Methods for Real#Time Systems.» Proceedings
of the Ninth International Conference on Software Engineering. 238–52.
Kelly#Bootle, Stan. 1981. The Devil’s DP Dictionary. New York, NY: McGraw#Hill.
Kernighan, Brian W., and Rob Pike. 1999. The Practice of Programming. Reading, MA: Addison#
Wesley.
Kernighan, Brian W., and P. J. Plauger. 1976. Software Tools. Reading, MA: Addison#Wesley.
Kernighan, Brian W., and P. J. Plauger. 1978. The Elements of Programming Style. 2d ed. New York,
NY: McGraw#Hill.
Kernighan, Brian W., and P. J. Plauger. 1981. Software Tools in Pascal. Reading, MA: Addison#Wesley.
Kernighan, Brian W., and Dennis M. Ritchie. 1988. The C Programming Language, 2d ed. Englewood
Cliffs, NJ: Prentice Hall.
Killelea, Patrick. 2002. Web Performance Tuning, 2d ed. Sebastopol, CA: O’Reilly & Associates.
King, David. 1988. Creating Effective Software: Computer Program Design Using the Jackson
Methodology. New York, NY: Yourdon Press.
Knuth, Donald. 1971. «An Empirical Study of FORTRAN programs,» Software—Practice and
Experience 1: 105–33.
Knuth, Donald. 1974. «Structured Programming with go to Statements.» In Classics in Software
Engineering, edited by Edward Yourdon. Englewood Cliffs, NJ: Yourdon Press, 1979.
Knuth, Donald. 1986. Computers and Typesetting, Volume B, TEX: The Program. Reading, MA:
Addison#Wesley.
Knuth, Donald. 1997a. The Art of Computer Programming, vol. 1, Fundamental Algorithms, 3d ed.
Reading, MA: Addison#Wesley.
Knuth, Donald. 1997b. The Art of Computer Programming, vol. 2, Seminumerical Algorithms, 3d
ed. Reading, MA: Addison#Wesley.
Knuth, Donald. 1998. The Art of Computer Programming, vol. 3, Sorting and Searching, 2d ed.
Reading, MA: Addison#Wesley.
Knuth, Donald. 2001. Literate Programming. Cambridge University Press.
Korson, Timothy D., and Vijay K. Vaishnavi. 1986. «An Empirical Study of Modularity on Program
Modifiability.» In Soloway and Iyengar 1986: 168–86.
Kouchakdjian, Ara, Scott Green, and Victor Basili. 1989. «Evaluation of the Cleanroom Methodology
in the Software Engineering Laboratory.» Proceedings of the Fourteenth Annual Software Engineering
Workshop, November 29, 1989. Greenbelt, MD: Goddard Space Flight Center. Document SEL#89#007.
Библиография 853
Kovitz, Benjamin, L. 1998 Practical Software Requirements: A Manual of Content and Style, Manning
Publications Company.
Kreitzberg, C. B., and B. Shneiderman. 1972. The Elements of Fortran Style. New York, NY: Harcourt
Brace Jovanovich.
Kruchten, Philippe B. «The 4+1 View Model of Architecture.» IEEE Software, pages 42–50, November
1995.
Kruchten, Philippe. 2000. The Rational Unified Process: An Introduction, 2d Ed., Reading, MA:
Addison#Wesley.
Kuhn, Thomas S. 1996. The Structure of Scientific Revolutions, 3d ed. Chicago: University of Chicago
Press.
Lammers, Susan. 1986. Programmers at Work. Redmond, WA: Microsoft Press.
Lampson, Butler. 1984. «Hints for Computer System Design.» IEEE Software 1, no. 1 (January): 11–28.
Larman, Craig and Rhett Guthrie. 2000. Java 2 Performance and Idiom Guide. Englewood Cliffs,
NJ: Prentice Hall.
Larman, Craig. 2001. Applying UML and Patterns: An Introduction to Object#Oriented Analysis
and Design and the Unified Process, 2d ed. Englewood Cliffs, NJ: Prentice Hall.
Larman, Craig. 2004. Agile and Iterative Development: A Manager’s Guide. Boston, MA: Addison#
Wesley, 2004.
Lauesen, Soren. Software Requirements: Styles and Techniques. Boston, MA: Addison#Wesley, 2002.
Laurel, Brenda, ed. 1990. The Art of Human%Computer Interface Design. Reading, MA: Addison#
Wesley.
Ledgard, Henry F., with John Tauer. 1987a. C With Excellence: Programming Proverbs. Indianapolis:
Hayden Books.
Ledgard, Henry F., with John Tauer. 1987b. Professional Software, vol. 2, Programming Practice.
Indianapolis: Hayden Books.
Ledgard, Henry, and Michael Marcotty. 1986. The Programming Language Landscape: Syntax,
Semantics, and Implementation, 2d ed. Chicago: Science Research Associates.
Ledgard, Henry. 1985. «Programmers: The Amateur vs. the Professional.» Abacus 2, no. 4 (Summer):
29–35.
Leffingwell, Dean. 1997. «Calculating the Return on Investment from More Effective Requirements
Management,» American Programmer, 10(4):13–16.
Lewis, Daniel W. 1979. «A Review of Approaches to Teaching Fortran.» IEEE Transactions on
Education, E#22, no. 1: 23–25.
Lewis, William E. 2000. Software Testing and Continuous Quality Improvement, 2d ed. Auerbach
Publishing.
Lieberherr, Karl J. and Ian Holland. 1989. «Assuring Good Style for Object#Oriented Programs.»
IEEE Software, September 1989, pp. 38f.
Lientz, B. P., and E. B. Swanson. 1980. Software Maintenance Management. Reading, MA: Addison#
Wesley.
Lind, Randy K., and K. Vairavan. 1989. «An Experimental Investigation of Software Metrics and
Their Relationship to Software Development Effort.» IEEE Transactions on Software Engineering
SE#15, no. 5 (May): 649–53.
Linger, Richard C., Harlan D. Mills, and Bernard I. Witt. 1979. Structured Programming: Theory
and Practice. Reading, MA: Addison#Wesley.
Linn, Marcia C., and Michael J. Clancy. 1992. «The Case for Case Studies of Programming Problems.»
Communications of the ACM 35, no. 3 (March): 121–32.
854 Библиография
Liskov, Barbara, and Stephen Zilles. 1974. «Programming with Abstract Data Types.» ACM Sigplan
Notices 9, no. 4: 50–59.
Liskov, Barbara. «Data Abstraction and Hierarchy,» ACM SIGPLAN Notices, May 1988.
Littman, David C., et al. 1986. «Mental Models and Software Maintenance.» In Soloway and Iyengar
1986: 80–98.
Longstreet, David H., ed. 1990. Software Maintenance and Computers. Los Alamitos, CA: IEEE
Computer Society Press.
Loy, Patrick H. 1990. «A Comparison of Object#Oriented and Structured Development Methods.»
Software Engineering Notes 15, no. 1 (January): 44–48.
Mackinnon, Tim, Steve Freeman, and Philip Craig. 2000. «Endo#Testing: Unit Testing with Mock
Objects,» eXtreme Programming and Flexible Processes Software Engineering # XP2000 Conference.
Maguire, Steve. 1993. Writing Solid Code. Redmond, WA: Microsoft Press.
Mannino, P. 1987. «A Presentation and Comparison of Four Information System Development
Methodologies.» Software Engineering Notes 12, no. 2 (April): 26– 29.
Manzo, John. 2002. «Odyssey and Other Code Science Success Stories.» Crosstalk, October 2002.
Marca, David. 1981. «Some Pascal Style Guidelines.» ACM Sigplan Notices 16, no. 4 (April): 70–80.
March, Steve. 1999. «Learning from Pathfinder’s Bumpy Start.» Software Testing and Quality
Engineering, September/October 1999, pp. 10f.
Marcotty, Michael. 1991. Software Implementation. New York, NY: Prentice Hall.
Martin, Robert C. 2003. Agile Software Development: Principles, Patterns, and Practices. Upper Saddle
River, NJ: Pearson Education.
McCabe, Tom. 1976. «A Complexity Measure.» IEEE Transactions on Software Engineering, SE#2,
no. 4 (December): 308–20.
McCarthy, Jim. 1995. Dynamics of Software Development. Redmond, WA: Microsoft Press.
McConnell, Steve. 1996. Rapid Development. Redmond, WA: Microsoft Press.
McConnell, Steve. 1997a. «The Programmer Writing,» IEEE Software, July/August 1997.
McConnell, Steve. 1997b. «Achieving Leaner Software,» IEEE Software, November/December 1997.
McConnell, Steve. 1998a. Software Project Survival Guide. Redmond, WA: Microsoft Press.
McConnell, Steve. 1998b. «Why You Should Use Routines, Routinely,» IEEE Software, Vol. 15, No.
4, July/August 1998.
McConnell, Steve. 1999. «Brooks Law Repealed?» IEEE Software, November/December 1999.
McConnell, Steve. 2004. Professional Software Development. Boston, MA: Addison#Wesley.
McCue, Gerald M. 1978. «IBM’s Santa Teresa Laboratory—Architectural Design for Program
Development.» IBM Systems Journal 17, no. 1: 4–25.
McGarry, Frank, and Rose Pajerski. 1990. «Towards Understanding Software—15 Years in the SEL.»
Proceedings of the Fifteenth Annual Software Engineering Workshop, November 28–29, 1990.
Greenbelt, MD: Goddard Space Flight Center. Document SEL#90#006.
McGarry, Frank, Sharon Waligora, and Tim McDermott. 1989. «Experiences in the Software
Engineering Laboratory (SEL) Applying Software Measurement.» Proceedings of the Fourteenth
Annual Software Engineering Workshop, November 29, 1989. Greenbelt, MD: Goddard Space Flight
Center. Document SEL#89#007.
McGarry, John, et al. 2001. Practical Software Measurement: Objective Information for Decision
Makers. Boston, MA: Addison#Wesley.
McKeithen, Katherine B., et al. 1981. «Knowledge Organization and Skill Differences in Computer
Programmers.» Cognitive Psychology 13: 307–25.
Библиография 855
Metzger, Philip W., and John Boddie. 1996. Managing a Programming Project: Processes and People,
3d ed. Englewood Cliffs, NJ: Prentice Hall, 1996.
Meyer, Bertrand. 1997. Object%Oriented Software Construction, 2d ed. New York, NY: Prentice Hall.
Meyers, Scott. 1996. More Effective C++: 35 New Ways to Improve Your Programs and Designs.
Reading, MA: Addison#Wesley.
Meyers, Scott. 1998. Effective C++: 50 Specific Ways to Improve Your Programs and Designs, 2d
ed. Reading, MA: Addison#Wesley.
Miaria, Richard J., et al. 1983. «Program Indentation and Comprehensibility.» Communications of
the ACM 26, no. 11 (November): 861–67.
Michalewicz, Zbigniew, and David B. Fogel. 2000. How to Solve It: Modern Heuristics. Berlin: Springer#
Verlag.
Miller, G. A. 1956. «The Magical Number Seven, Plus or Minus Two: Some Limits on Our Capacity
for Processing Information.» The Psychological Review 63, no. 2 (March): 81–97.
Mills, Harlan D. 1983. Software Productivity. Boston, MA: Little, Brown.
Mills, Harlan D. 1986. «Structured Programming: Retrospect and Prospect.» IEEE Software, Novem#
ber, 58–66.
Mills, Harlan D., and Richard C. Linger. 1986. «Data Structured Programming: Program Design
Without Arrays and Pointers.» IEEE Transactions on Software Engineering SE#12, no. 2 (February):
192–97.
Mills, Harlan D., Michael Dyer, and Richard C. Linger. 1987. «Cleanroom Software Engineering.»
IEEE Software, September, 19–25.
Misfeldt, Trevor, Greg Bumgardner, and Andrew Gray. 2004. The Elements of C++ Style. Cambridge
University Press.
Mitchell, Jeffrey, Joseph Urban, and Robert McDonald. 1987. «The Effect of Abstract Data Types
on Program Development.» IEEE Computer 20, no. 9 (September): 85–88.
Mody, R. P. 1991. «C in Education and Software Engineering.» SIGCSE Bulletin 23, no. 3 (September):
45–56.
Moore, Dave. 1992. Private communication.
Moore, James W. 1997. Software Engineering Standards: A User’s Road Map. Los Alamitos, CA: IEEE
Computer Society Press.
Morales, Alexandra Weber. 2003. «The Consummate Coach: Watts Humphrey, Father of Cmm and
Author of Winning with Software, Explains How to Get Better at What You Do,» SD Show Daily,
September 16, 2003.
Myers, Glenford J. 1976. Software Reliability. New York, NY: John Wiley & Sons.
Myers, Glenford J. 1978a. Composite/Structural Design. New York, NY: Van Nostrand Reinhold.
Myers, Glenford J. 1978b. «A Controlled Experiment in Program Testing and Code Walkthroughs/
Inspections.» Communications of the ACM 21, no. 9 (September): 760–68.
Myers, Glenford J. 1979. The Art of Software Testing. New York, NY: John Wiley & Sons.
Myers, Ware. 1992. «Good Software Practices Pay Off—Or Do They?» IEEE Software, March, 96–97.
Naisbitt, John. 1982. Megatrends. New York, NY: Warner Books.
NASA Software Engineering Laboratory, 1994. Software Measurement Guidebook, June 1995, NASA#
GB#001#94. Available from http://sel.gsfc.nasa.gov/website/documents/online#doc/94#102.pdf.
NCES 2002. National Center for Education Statistics, 2001 Digest of Educational Statistics, Document
Number NCES 2002130, April 2002.
Nevison, John M. 1978. The Little Book of BASIC Style. Reading, MA: Addison#Wesley.
856 Библиография
Newcomer, Joseph M. 2000. «Optimization: Your Worst Enemy,» May 2000, www.flounder. com/
optimization.htm.
Norcio, A. F. 1982. «Indentation, Documentation and Programmer Comprehension.» Proceedings:
Human Factors in Computer Systems, March 15–17, 1982, Gaithersburg, MD: 118–20.
Norman, Donald A. 1988. The Psychology of Everyday Things. New York, NY: Basic Books. (Also
published in paperback as The Design of Everyday Things. New York, NY: Doubleday, 1990.)
Oman, Paul and Shari Lawrence Pfleeger, eds. 1996. Applying Software Metrics. Los Alamitos, CA:
IEEE Computer Society Press.
Oman, Paul W., and Curtis R. Cook. 1990a. «The Book Paradigm for Improved Maintenance.» IEEE
Software, January, 39–45.
Oman, Paul W., and Curtis R. Cook. 1990b. «Typographic Style Is More Than Cosmetic.» Commu%
nications of the ACM 33, no. 5 (May): 506–20.
Ostrand, Thomas J., and Elaine J. Weyuker. 1984. «Collecting and Categorizing Software Error Data
in an Industrial Environment.» Journal of Systems and Software 4, no. 4 (November): 289–300.
Page#Jones, Meilir. 2000. Fundamentals of Object%Oriented Design in UML. Boston, MA: Addison#
Wesley.
Page#Jones, Meilir. 1988. The Practical Guide to Structured Systems Design. Englewood Cliffs, NJ:
Yourdon Press.
Parikh, G., and N. Zvegintzov, eds. 1983. Tutorial on Software Maintenance. Los Alamitos, CA: IEEE
Computer Society Press.
Parikh, Girish. 1986. Handbook of Software Maintenance. New York, NY: John Wiley & Sons.
Parnas, David L. 1972. «On the Criteria to Be Used in Decomposing Systems into Modules.»
Communications of the ACM 5, no. 12 (December): 1053–58.
Parnas, David L. 1976. «On the Design and Development of Program Families.» IEEE Transactions
on Software Engineering SE#2, 1 (March): 1–9.
Parnas, David L. 1979. «Designing Software for Ease of Extension and Contraction.» IEEE Trans%
actions on Software Engineering SE#5, no. 2 (March): 128–38.
Parnas, David L. 1999. ACM Fellow Profile: David Lorge Parnas,» ACM Software Engineering Notes,
May 1999, 10–14.
Parnas, David L., and Paul C. Clements. 1986. «A Rational Design Process: How and Why to Fake
It.» IEEE Transactions on Software Engineering SE#12, no. 2 (February): 251–57.
Parnas, David L., Paul C. Clements, and D. M. Weiss. 1985. «The Modular Structure of Complex
Systems.» IEEE Transactions on Software Engineering SE#11, no. 3 (March): 259–66.
Perrott, Pamela. 2004. Private communication.
Peters, L. J., and L. L. Tripp. 1976. «Is Software Design Wicked» Datamation, Vol. 22, No. 5 (May
1976), 127–136.
Peters, Lawrence J. 1981. Handbook of Software Design: Methods and Techniques. New York, NY:
Yourdon Press.
Peters, Lawrence J., and Leonard L. Tripp. 1977. «Comparing Software Design Methodologies.»
Datamation, November, 89– 94.
Peters, Tom. 1987. Thriving on Chaos: Handbook for a Management Revolution. New York, NY:
Knopf.
Petroski, Henry. 1994. Design Paradigms: Case Histories of Error and Judgment in Engineering.
Cambridge, U.K.: Cambridge University Press.
Pietrasanta, Alfred M. 1990. «Alfred M. Pietrasanta on Improving the Software Process.» Software
Engineering: Tools, Techniques, Practices 1, no. 1 (May/ June): 29–34.
Библиография 857
Pietrasanta, Alfred M. 1991a. «A Strategy for Software Process Improvement.» Ninth Annual Pacific
Northwest Software Quality Conference, October 7–8, 1991. Oregon Convention Center, Portland, OR
Pietrasanta, Alfred M. 1991b. «Implementing Software Engineering in IBM.» Keynote address. Ninth
Annual Pacific Northwest Software Quality Conference, October 7– 8, 1991. Oregon Convention
Center, Portland, OR.
Pigoski, Thomas M. 1997. Practical Software Maintenance. New York, NY: John Wiley & Sons.
Pirsig, Robert M. 1974. Zen and the Art of Motorcycle Maintenance: An Inquiry into Values. William
Morrow.
Plauger, P. J. 1988. «A Designer’s Bibliography.» Computer Language, July, 17–22.
Plauger, P. J. 1993. Programming on Purpose: Essays on Software Design. New York, NY: Prentice
Hall.
Plum, Thomas. 1984. C Programming Guidelines. Cardiff, NJ: Plum Hall.
Polya, G. 1957. How to Solve It: A New Aspect of Mathematical Method, 2d ed. Princeton, NJ: Princeton
University Press.
Post, Ed. 1983. «Real Programmers Don’t Use Pascal,» Datamation, July 1983, 263– 265.
Prechelt, Lutz. 2000. «An Empirical Comparison of Seven Programming Languages,» IEEE Computer,
October 2000, 23–29.
Pressman, Roger S. 1987. Software Engineering: A Practitioner’s Approach. New York, NY: McGraw#Hill.
Pressman, Roger S. 1988. Making Software Engineering Happen: A Guide for Instituting the
Technology. Englewood Cliffs, NJ: Prentice Hall.
Putnam, Lawrence H. 2000. «Familiar Metric Management – Effort, Development Time, and Defects
Interact.» Downloadable from www.qsm.com.
Putnam, Lawrence H., and Ware Myers. 1992. Measures for Excellence: Reliable Software On Time,
Within Budget. Englewood Cliffs, NJ: Yourdon Press, 1992.
Putnam, Lawrence H., and Ware Myers. 1997. Industrial Strength Software: Effective Management
Using Measurement. Washington, DC: IEEE Computer Society Press.
Putnam, Lawrence H., and Ware Myers. 2000. «What We Have Learned.» Downloadable from
www.qsm.com, June 2000.
Raghavan, Sridhar A., and Donald R. Chand. 1989. «Diffusing Software#Engineering Methods.»
IEEE Software, July, 81–90.
Ramsey, H. Rudy, Michael E. Atwood, and James R. Van Doren. 1983. «Flowcharts Versus Program
Design Languages: An Experimental Comparison.» Communications of the ACM 26, no. 6 (June):
445–49.
Ratliff, Wayne. 1987. Interview in Solution System.
Raymond, E. S. 2000. «The Cathedral and the Bazaar,» www.catb.org/~esr/writings/cathedral%bazaar.
Raymond, Eric S. 2004. The Art of Unix Programming. Boston, MA: Addison#Wesley.
Rees, Michael J. 1982. «Automatic Assessment Aids for Pascal Programs.» ACM Sigplan Notices 17,
no. 10 (October): 33–42.
Reifer, Donald. 2002. «How to Get the Most Out of Extreme Programming/Agile Methods,»
Proceedings, XP/Agile Universe 2002. New York, NY: Springer; 185–196.
Reingold, Edward M., and Wilfred J. Hansen. 1983. Data Structures. Boston, MA: Little, Brown.
Rettig, Marc. 1991. «Testing Made Palatable.» Communications of the ACM 34, no. 5 (May): 25–29.
Riel, Arthur J. 1996. Object%Oriented Design Heuristics. Reading, MA: Addison#Wesley.
Rittel, Horst, and Melvin Webber. 1973. «Dilemmas in a General Theory of Planning.» Policy Sciences
4: 155–69.
858 Библиография
Robertson, Suzanne, and James Robertson, 1999. Mastering the Requirements Process. Reading,
MA: Addison#Wesley.
Rogers, Everett M. 1995. Diffusion of Innovations, 4th ed. New York, NY: The Free Press.
Rombach, H. Dieter. 1990. «Design Measurements: Some Lessons Learned.» IEEE Software, March,
17–25.
Rubin, Frank. 1987. «‘GOTO Considered Harmful’ Considered Harmful.» Letter to the editor.
Communications of the ACM 30, no. 3 (March): 195–96. Follow#up letters in 30, no. 5 (May 1987):
351–55; 30, no. 6 (June 1987): 475–78; 30, no. 7 (July 1987): 632–34; 30, no. 8 (August 1987):
659–62; 30, no. 12 (December 1987): 997, 1085.
Sackman, H., W. J. Erikson, and E. E. Grant. 1968. «Exploratory Experimental Studies Comparing Online
and Offline Programming Performance.» Communications of the ACM 11, no. 1 (January): 3–11.
Schneider, G. Michael, Johnny Martin, and W. T. Tsai. 1992. «An Experimental Study of Fault
Detection in User Requirements Documents,» ACM Transactions on Software Engineering and
Methodology, vol 1, no. 2, 188–204.
Schulmeyer, G. Gordon. 1990. Zero Defect Software. New York, NY: McGraw#Hill.
Sedgewick, Robert. 1997. Algorithms in C, Parts 1%4, 3d ed. Boston, MA: Addison#Wesley.
Sedgewick, Robert. 2001. Algorithms in C, Part 5, 3d ed. Boston, MA: Addison#Wesley.
Sedgewick, Robert. 1998. Algorithms in C++, Parts 1%4, 3d ed. Boston, MA: Addison#Wesley.
Sedgewick, Robert. 2002. Algorithms in C++, Part 5, 3d ed. Boston, MA: Addison#Wesley.
Sedgewick, Robert. 2002. Algorithms in Java, Parts 1%4, 3d ed. Boston, MA: Addison#Wesley.
Sedgewick, Robert. 2003. Algorithms in Java, Part 5, 3d ed. Boston, MA: Addison#Wesley.
SEI 1995. The Capability Maturity Model: Guidelines for Improving the Software Process, Software
Engineering Institute, Reading, MA: Addison#Wesley, 1995.
SEI, 2003. «Process Maturity Profile: Software CMM®, CBA IPI and SPA Appraisal Results: 2002
Year End Update,» Software Engineering Institute, April 2003.
Selby, Richard W., and Victor R. Basili. 1991. «Analyzing Error#Prone System Structure.» IEEE
Transactions on Software Engineering SE#17, no. 2 (February): 141–52.
SEN 1990. «Subsection on Telephone Systems,» Software Engineering Notes, April 1990, 11–14.
Shalloway, Alan, and James R. Trott. 2002. Design Patterns Explained. Boston, MA: Addison#Wesley.
Sheil, B. A. 1981. «The Psychological Study of Programming.» Computing Surveys 13, no. 1 (March):
101–20.
Shen, Vincent Y., et al. 1985. «Identifying Error#Prone Software—An Empirical Study.» IEEE
Transactions on Software Engineering SE#11, no. 4 (April): 317–24.
Sheppard, S. B., et al. 1978. «Predicting Programmers’ Ability to Modify Software.» TR 78%388100%
3, General Electric Company, May.
Sheppard, S. B., et al. 1979. «Modern Coding Practices and Programmer Performance.» IEEE
Computer 12, no. 12 (December): 41–49.
Shepperd, M., and D. Ince. 1989. «Metrics, Outlier Analysis and the Software Design Process.»
Information and Software Technology 31, no. 2 (March): 91–98.
Shirazi, Jack. 2000. Java Performance Tuning. Sebastopol, CA: O’Reilly & Associates.
Shlaer, Sally, and Stephen J. Mellor. 1988. Object Oriented Systems Analysis—Modeling the World
in Data. Englewood Cliffs, NJ: Prentice Hall.
Shneiderman, Ben, and Richard Mayer. 1979. «Syntactic/Semantic Interactions in Programmer
Behavior: A Model and Experimental Results.» International Journal of Computer and Information
Sciences 8, no. 3: 219–38.
Библиография 859
Tenner, Edward. 1997. Why Things Bite Back: Technology and the Revenge of Unintended
Consequences. Vintage Books.
Tenny, Ted. 1988. «Program Readability: Procedures versus Comments.» IEEE Transactions on
Software Engineering SE#14, no. 9 (September): 1271–79.
Thayer, Richard H., ed. 1990. Tutorial: Software Engineering Project Management. Los Alamitos,
CA: IEEE Computer Society Press.
Thimbleby, Harold. 1988. «Delaying Commitment.» IEEE Software, May, 78–86.
Thomas, Dave, and Andy Hunt. 2002. «Mock Objects,» IEEE Software, May/June 2002.
Thomas, Edward J., and Paul W. Oman. 1990. «A Bibliography of Programming Style.» ACM Sigplan
Notices 25, no. 2 (February): 7–16.
Thomas, Richard A. 1984. «Using Comments to Aid Program Maintenance.» BYTE, May, 415–22.
Tripp, Leonard L., William F. Struck, and Bryan K. Pflug. 1991. «The Application of Multiple Team
Inspections on a Safety#Critical Software Standard,» Proceedings of the 4th Software Engineering
Standards Application Workshop, Los Alamitos, CA: IEEE Computer Society Press.
U.S. Department of Labor. 1990. «The 1990– 91 Job Outlook in Brief.» Occupational Outlook
Quarterly, Spring. U.S. Government Printing Office. Document 1990#282#086/20007.
Valett, J., and F. E. McGarry. 1989. «A Summary of Software Measurement Experiences in the Software
Engineering Laboratory.» Journal of Systems and Software 9, no. 2 (February): 137–48.
Van Genuchten, Michiel. 1991. «Why Is Software Late? An Empirical Study of Reasons for Delay in
Software Development.» IEEE Transactions on Software Engineering SE#17, no. 6 (June): 582–90.
Van Tassel, Dennie. 1978. Program Style, Design, Efficiency, Debugging, and Testing, 2d ed. Englewood
Cliffs, NJ: Prentice Hall.
Vaughn#Nichols, Steven. 2003. «Building Better Software with Better Tools,» IEEE Computer,
September 2003, 12–14.
Vermeulen, Allan, et al. 2000. The Elements of Java Style. Cambridge University Press.
Vessey, Iris, Sirkka L. Jarvenpaa, and Noam Tractinsky. 1992. «Evaluation of Vendor Products: CASE
Tools as Methodological Companions.» Communications of the ACM 35, no. 4 (April): 91–105.
Vessey, Iris. 1986. «Expertise in Debugging Computer Programs: An Analysis of the Content of
Verbal Protocols.» IEEE Transactions on Systems, Man, and Cybernetics SMC#16, no. 5 (September/
October): 621–37.
Votta, Lawrence G., et al. 1991. «Investigating the Application of Capture#Recapture Techniques
to Requirements and Design Reviews.» Proceedings of the Sixteenth Annual Software Engineering
Workshop, December 4–5, 1991. Greenbelt, MD: Goddard Space Flight Center. Document SEL#
91#006.
Walston, C. E., and C. P. Felix. 1977. «A Method of Programming Measurement and Estimation.»
IBM Systems Journal 16, no. 1: 54–73.
Ward, Robert. 1989. A Programmer’s Introduction to Debugging C. Lawrence, KS: R & D Publications.
Ward, William T. 1989. «Software Defect Prevention Using McCabe’s Complexity Metric.» Hewlett%
Packard Journal, April, 64–68.
Webster, Dallas E. 1988. «Mapping the Design Information Representation Terrain.» IEEE Computer,
December, 8–23.
Weeks, Kevin. 1992. «Is Your Code Done Yet?» Computer Language, April, 63–72.
Weiland, Richard J. 1983. The Programmer’s Craft: Program Construction, Computer Architecture,
and Data Management. Reston, VA: Reston Publishing.
Weinberg, Gerald M. 1983. «Kill That Code!» Infosystems, August, 48–49.
Библиография 861
Weinberg, Gerald M. 1998. The Psychology of Computer Programming: Silver Anniversary Edition.
New York, NY: Dorset House.
Weinberg, Gerald M., and Edward L. Schulman. 1974. «Goals and Performance in Computer
Programming.» Human Factors 16, no. 1 (February): 70–77.
Weinberg, Gerald. 1988. Rethinking Systems Analysis and Design. New York, NY: Dorset House.
Weisfeld, Matt. 2004. The Object%Oriented Thought Process, 2d ed. SAMS, 2004.
Weiss, David M. 1975. «Evaluating Software Development by Error Analysis: The Data from the
Architecture Research Facility.» Journal of Systems and Software 1, no. 2 (June): 57–70.
Weiss, Eric A. 1972. «Review of The Psychology of Computer Programming, by Gerald M. Weinberg.»
ACM Computing Reviews 13, no. 4 (April): 175–76.
Wheeler, David, Bill Brykczynski, and Reginald Meeson. 1996. Software Inspection: An Industry
Best Practice. Los Alamitos, CA: IEEE Computer Society Press.
Whittaker, James A. 2000 «What Is Software Testing? And Why Is It So Hard?» IEEE Software, January
2000, 70–79.
Whittaker, James A. 2002. How to Break Software: A Practical Guide to Testing. Boston, MA: Addison#
Wesley.
Whorf, Benjamin. 1956. Language, Thought and Reality. Cambridge, MA: MIT Press.
Wiegers, Karl. 2002. Peer Reviews in Software: A Practical Guide. Boston, MA: Addison#Wesley.
Wiegers, Karl. 2003. Software Requirements, 2d ed. Redmond, WA: Microsoft Press.
Williams, Laurie, and Robert Kessler. 2002. Pair Programming Illuminated. Boston, MA: Addison#
Wesley.
Willis, Ron R., et al. 1998. «Hughes Aircraft’s Widespread Deployment of a Continuously Improving
Software Process,» Software Engineering Institute/Carnegie Mellon University, CMU/SEI#98#TR#
006, May 1998.
Wilson, Steve, and Jeff Kesselman. 2000. Java Platform Performance: Strategies and Tactics. Boston,
MA: Addison#Wesley.
Wirth, Niklaus. 1995. «A Plea for Lean Software,» IEEE Computer, February 1995.
Wirth, Niklaus. 1971. «Program Development by Stepwise Refinement.» Communications of the
ACM 14, no. 4 (April): 221–27.
Wirth, Niklaus. 1986. Algorithms and Data Structures. Englewood Cliffs, NJ: Prentice Hall.
Woodcock, Jim, and Martin Loomes. 1988. Software Engineering Mathematics. Reading, MA: Addison#
Wesley.
Woodfield, S. N., H. E. Dunsmore, and V. Y. Shen. 1981. «The Effect of Modularization and Comments
on Program Comprehension.» Proceedings of the Fifth International Conference on Software
Engineering, March 1981, 215–23.
Wulf, W. A. 1972. «A Case Against the GOTO.» Proceedings of the 25th National ACM Conference,
August 1972, 791–97.
Youngs, Edward A. 1974. «Human Errors in Programming.» International Journal of Man%Machine
Studies 6: 361–76.
Yourdon, Edward, and Larry L. Constantine. 1979. Structured Design: Fundamentals of a Discipline
of Computer Program and Systems Design. Englewood Cliffs, NJ: Yourdon Press.
Yourdon, Edward, ed. 1979. Classics in Software Engineering. Englewood Cliffs, NJ: Yourdon Press.
Yourdon, Edward, ed. 1982. Writings of the Revolution: Selected Readings on Software Engineering.
New York, NY: Yourdon Press.
862 Библиография
Yourdon, Edward. 1986a. Managing the Structured Techniques: Strategies for Software Development
in the 1990s, 3d ed. New York, NY: Yourdon Press.
Yourdon, Edward. 1986b. Nations at Risk. New York, NY: Yourdon Press.
Yourdon, Edward. 1988. «The 63 Greatest Software Books.» American Programmer, September.
Yourdon, Edward. 1989a. Modern Structured Analysis. New York, NY: Yourdon Press.
Yourdon, Edward. 1989b. Structured Walk%Throughs, 4th ed. New York, NY: Yourdon Press.
Yourdon, Edward. 1992. Decline & Fall of the American Programmer. Englewood Cliffs, NJ: Yourdon
Press.
Zachary, Pascal. 1994. Showstopper! The Free Press.
Zahniser, Richard A. 1992. «A Massively Parallel Software Development Approach.» American
Programmer, January, 34–41.
Предметный указатель
Pseudocode Programming Process См. ППП блок 436, см. также оператор, составной
— границы 725
U — эмуляция 723
UDT 272 — явный 722
UML (Unified Modeling Language) 115
В
время связывания 104
А
абстрактный тип данных См. АТД Г
абстракция 86, 135, 149, 160 глобальные данные 326, 327, 328, 329, 330, 334
аккреция 14
алгоритм 11 Д
архитектура 41 диаграмма 104
— безопасность 45 директива 11
— бизнес#правила 44
З
— ввод#вывод 46
заглушка 203
— взаимодействие с другими системами 45
— возможность реализации 48 И
— избыточная функциональность 48 иерархия 102
— интернационализация/локализация 46 изоляция повреждений 198 см. также бар#
— масштабируемость 45 рикада
— обработка ошибок 46 индекс
— общее качество 50 — длины строки 613
— организация данных 43 — цикла 257, 258
— организация программы 42 инкапсуляция 87, 135
— основные классы 43 инкрементное улучшение 108
— отказоустойчивость 47 инспекция 477
— повторное использование 49 интеграция 3, 4, 673
— пользовательский интерфейс 44 — инкрементная 676, 678
— производительность 45 — — восходящая 681
— стратегия изменений 49 — — нисходящая 678
— управление ресурсами 44 — — риск#ориентированная 683
864 Предметный указатель
К М
класс 86, 121, 145–152 массив 301, 379, 611
— включение 139 метафора 8, 9, 10, 11, 12, 14, 15, 19
— данные#члены 146 метод 133, 138, 143, 157, 158, 160, 162, 163, 165
— конструирование всех методов 210 — встраиваемый 178, 180
— конструктор 147 — встраивание 625
— контракт 103 — доступа 331, 332, 333
— метод#член 146 — заголовок 217
— наследование 140 — имя 167, 168, 215
— — множественное 145 — интерфейс 170, 175, 219, 224
— оценка 210 — кодирование 218
— пакет 153 — комментирование 787
— проект 575 — компиляция 223
— создание общей структуры 210 — макрос 178–186
— тестирование 210 — множественные возвраты 382
— форматирование 752 — наследование 161
ключевое слово 152 — объем 169
код — параметр 170–177
— библиотека 701 — проверка кода 223
— инструменты для сборки 701 — проект 575
— компилятор 700 — проектирование 214
— компиляция 575 — псевдокод 217
— компоновщик 700 — размещение 750
— мастер для генерации 702 — рекурсия 385
— оптимизация 574, 576, 581, 582, 595, 704 — связанность 163
— создание 700 — создание 211
— транслятор 699 — табличный 405
кодирование 2, 3, 4 — тестирование 224
комментарий 220, 221, 222, 747, 764 методология 23
конвенции программирования 63 моделирование 8
конвенция именования 263, 264, 266, 267, 268, модель 9
269, 270, 271 модульность проекта системы 104
константа 263
— именованная 299
Предметный указатель 865
О — структурное 448
обработка ошибок 189–198, 215, 393 — — выбор 449
объект 84 — — итерация 450
оператор 338 — — последовательность 448
— case 353 проект
— if 346 — анализ 484
— switch 353 — измерение 661
— порядок выполнения 342 — презентация 487
— пустой 437 — размер 5, 635
— составно См. также блок — чтение кода 486
— составной 436 проектирование 70, 71, 72, 73, 74, 84
— форматирование 736 — восходящее 108, 109, 110
оптимизация 573 — высокоуровневое 2
отладка 2, 3, 4, 5, 200, 201, 202, 203, 204, 524 — детальное 2, 3, 4
оценка 659 — инструмент 695
— метод 163
П — методика 107
переменная 230 — нисходящее 108, 110
— булева 261 — программная система 79
— временная 260 — методов 83
— время жизни 239, 241 — разделение
— время связывания 246 — — классов на методы 83
— единственность цели 249 — — подсистем на классы 82
— имя 253, 254, 257, 274, 275, 277, 279 — — системы на подсистемы или паке#
— — длина 255 ты 79
— инициализация 233, 234, 235, 236 — регистрация 114
— логическая 292 — связность 102
— область видимости 238, 239, 242, 244, — совместное 112
255 — управление сложностью 74, 75
— обращение 238 — характеристики проекта
— объявление 232, 236 — — возможность повторного использо#
— — неявное 232, 233 вания 78
— персистентность 245 — — высокий коэффициент объедине#
— статуса 258, 259 ния по входу 78
Об авторе
Совершенный код
Мастеркласс