close

Вход

Забыли?

вход по аккаунту

?

Lekarev2

код для вставкиСкачать
ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ
Государственное образовательное учреждение
высшего профессионального образования
САНКТ)ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ
АЭРОКОСМИЧЕСКОГО ПРИБОРОСТРОЕНИЯ
М. Ф. Лекарев, Ю. Ю. Аксенова
ЭЛЕМЕНТЫ
ПРОФЕССИОНАЛЬНОГО ДИЗАЙНА
ПРОГРАММ НА ЯЗЫКЕ C / C++
В УЧЕБНЫХ РАБОТАХ СТУДЕНТОВ
Конспект лекций
Санкт)Петербург
2007
УДК 621.391
ББК 32.811
Л43
Рецензент
доктор технических наук, профессор, заведующий кафедрой технологии
программирования СПбГУИТМО А. А. Шалыто
Утверждено редакционно)издательским советом университета
в качестве конспекта лекций
Лекарев М. Ф., Аксенова Ю. Ю.
Л43
Элементы профессионального дизайна программ на языке
С/С++ в учебных работах студентов: конспект лекций / М. Ф. Ле)
карев, Ю. Ю. Аксенова; – ГУАП. СПб., 2007. – 100 с.: ил.
Обсуждается стиль программирования, продуманное комментиро)
вание исходных текстов, функциональная и файловая декомпозиция
программного проекта. Приведены примеры оформления программ.
Книга предназначена для студентов всех специальностей, изуча)
ющих программирование на языках С, С++, Java, C#, а также для
аспирантов и специалистов)практиков, которые занимаются профес)
сиональной разработкой программного обеспечения.
УДК 621.391
ББК 32.811
Учебное издание
Лекарев Михаил Федорович
Аксенова Юлиана Юрьевна
ЭЛЕМЕНТЫ
ПРОФЕССИОНАЛЬНОГО ДИЗАЙНА
ПРОГРАММ НА ЯЗЫКЕ C / C++
В УЧЕБНЫХ РАБОТАХ СТУДЕНТОВ
Конспект лекций
Редактор А. Г. Ларионова
Компьютерная верстка А. Н. Колешко
Сдано в набор 11.10.06. Подписано к печати 13.02.07. Формат 60 84 1/16.
Бумага офсетная. Печать офсетная. Усл. печ. л. 6,25. Уч. )изд. л. 3,5.
Тираж 100 экз. Заказ №
Редакционно)издательский центр ГУАП
190000, Санкт)Петербург, ул. Б. Морская, 67
© ГУАП, 2007
© М. Ф. Лекарев, Ю. Ю. Аксенова, 2007
2
СОДЕРЖАНИЕ
Предисловие ...................................................................
1. Особенности мышления разработчика программного
обеспечения ....................................................................
1.1. Определение терминов .............................................
1.2. Стиль программирования как элемент дизайна
программ ...............................................................
1.3. Выполнение учебных работ ......................................
2. Комментирование .........................................................
2.1. Основы комментирования ........................................
2.2. Типы комментариев ................................................
2.3. Комментарии – заголовки файлов .............................
2.4. Комментарии – заголовки функций ...........................
2.5. Комментарии блоков текста .....................................
2.6. Сопровождающие комментарии ................................
2.7. Комментирование данных .......................................
2.8. Комментирование определений типов данных .............
3. Именование .................................................................
3.1. Общие замечания ...................................................
3.2. Именование функций ..............................................
3.3. Именование переменных .........................................
4. Основы размещения кода ...............................................
4.1. Использование пробелов .........................................
4.2. Размещение по строчкам .........................................
4.3. Вертикальное выравнивание и отступы по горизонтали
4.4. Размещение фигурных скобок ..................................
4.5. Объявления данных ................................................
4.6. Определения и прототипы функций ...........................
5. Инструкции: размещение и употребление .........................
5.1. Инструкция)выражение ..........................................
5.2. Составная инструкция (инструкция)блок) .................
5.3. Условная инструкция if ..........................................
5.4. Инструкция выбора варианта switch .........................
5.5. Условная инструкция if ... else if ... else ....................
5.6. Инструкция цикла с предусловием while ....................
5.7. Инструкция цикла с постусловием do ... while ............
5.8. Инструкция цикла for ............................................
5.9. Инструкция цикла нестандартной структуры .............
5
6
6
9
14
20
20
23
23
25
27
28
30
33
34
34
37
39
43
43
46
48
49
49
52
55
55
56
56
58
61
62
62
63
63
3
5.10. Инструкция возврата из функции return .................. 64
6. Функциональная декомпозиция программного проекта ..... 66
6.1. Сложность программного обеспечения и ее источники .. 66
6.2. Методы преодоления сложности программного
обеспечения ........................................................... 69
6.3. «Монолитный» стиль программирования .................. 70
6.4. Функциональная декомпозиция ............................... 73
7. Файловая декомпозиция программного проекта ................ 76
7.1. Расширения имен файлов ........................................ 76
7.2. Исходные файлы .................................................... 77
7.3. Заголовочные файлы .............................................. 82
8. Тестирование и отладка ................................................ 93
8.1. Общие замечания ................................................... 93
8.2. Разновидности исходных данных для тестирования .... 93
8.3. Выполнение теста: обработка пакета контрольных
примеров ............................................................... 94
8.4. Переработанный текст демонстрационного примера ..... 95
Библиографический список ............................................ 100
4
ПРЕДИСЛОВИЕ
Один из важнейших элементов квалификации разработчика про)
граммного обеспечения (ПО) – владение профессиональным дизай)
ном программ.
В книге элементы профессионального дизайна программ на языке
С/С++ [2, 3, 12] рассмотрены в контексте выполнения лаборатор)
ных и курсовых работ по курсу «Программирование на языках высо)
кого уровня», который изучается студентами во втором–четвертом
семестрах.
Ограниченный объем издания не позволяет обстоятельно обосно)
вать изложенные требования и рекомендации. Полное и всесторон)
нее рассмотрение вопроса можно найти в работе [11]. Особенно за)
метные заимствования из книги [11] имеются в разд. 2–4, хотя этот
материал радикально переработан, существенно дополнен и приве)
ден в авторизованном переводе автора настоящего конспекта лек)
ций.
5
1. ОСОБЕННОСТИ МЫШЛЕНИЯ РАЗРАБОТЧИКА
ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ
1.1. Определение терминов
1.1.1. Главные качества программного обеспечения
Два главных качества должны быть присущи программному обес)
печению – правильность (корректность) и модифицируемость.
Правильная программа преобразует корректные исходные данные
в корректные результаты (в соответствии со своей спецификацией)
и, возможно, в состоянии отличить корректные исходные данные от
некорректных.
Модифицируемая программа разработана так, что внесение в нее
вероятных изменений связано с относительно небольшими затрата)
ми. Для этого программа должна быть, в частности, легко понимае)
мой при чтении.
Конечно, остальные качества программы (производительность,
компактность и т. д.) тоже имеют большое значение. Но по отноше)
нию к правильности и модифицируемости они находятся, так ска)
зать, на втором плане.
1.1.2. Дизайн программ и его составляющие
Элегантную программу очень легко узнать с первого взгляда, но
очень сложно точно определить. Она эффективно использует возмож)
ности языка в сочетании с ясным изложением. Она занимает мало
места, но не содержит «плотного» кода. Она кодирует сложные вы)
числения, оставаясь простой и понятной. В ней найден идеальный
компромисс между принципами простоты и явного формулирования.
В этом кажущемся противоречии нет ничего удивительного. В кон)
це концов, в любой области деятельности создавать простые вещи
намного труднее, чем сложные.
Таким образом, программирование как вид деятельности включает
элементы не только инженерного проектирования, но и художествен)
ного творчества. Цель, к которой должны стремиться все программис)
ты, – это кодовый эквивалент высокохудожественной прозы.
В этой связи определим следующие термины.
Дизайн программ – все виды деятельности по эстетическому про)
ектированию программ. Цель этой деятельности состоит в радикаль)
ном уменьшении затрат на обеспечение правильности и модифици)
руемости программ.
6
Стиль программирования – одна из составных частей дизайна
программ, которая состоит в продуманном следовании совокупности
проверенных практикой принципов разработки программ и в рацио)
нальном использовании языка программирования.
Стандарт кодирования – это одна из составных частей стиля
программирования, которая регламентирует использование изобра)
зительных средств конкретного языка программирования и разме)
щение текста программ.
1.1.3. Жизненный цикл программного обеспечения
Жизненный цикл ПО – это весь процесс его создания и примене)
ния от начала до конца. Этапы этого процесса и их стоимость пред)
ставлены на рис. 1.1 [7, 8, 10].
Разработка состоит в создании ПО «с нуля».
На стадии системного анализа осуществляется осмысливание
цели и постановка задачи. Ее решение может быть найдено на этой
же стадии работы. Затем решение следует всесторонне проанализи)
ровать и зафиксировать в форме спецификаций.
На стадии проектирования системы осуществляется выбор воз)
можных путей и методов реализации. Здесь определяются тип вы)
числительной машины и объем ее ресурсов, язык программирования,
перечень выполняемых функций. Разрабатываются структуры дан)
ных и алгоритмы их обработки.
123456 7829
278
944422 935 1
3445
645
45
55285
1
22249
22355676 552852
3445
345
785
395
45
978
4
54539
12892549
292549
8355
Рис. 1.1. Относительная стоимость этапов жизненного цикла ПО
7
На стадии кодирования проектное решение реализуется в форме
программы. Программа состоит из массы сложных элементов, поэто)
му небрежность здесь недопустима.
На стадии отладки происходит проверка соответствия ПО всем
требованиям. Проверяют все структурные элементы системы и такое
количество всевозможных вариантов ее работы, какое только позво)
ляют здравый смысл и бюджет [1, 6].
Сопровождение (или продолжающаяся разработка) состоит,
главным образом, в модификации ПО в соответствии с изменяющи)
мися требованиями и спецификациями. Кроме того, в ходе сопро)
вождения локализуют и исправляют ошибки, которые остались не)
выявленными в ходе разработки и проявились только в процессе эк)
сплуатации ПО.
По мере успешной эксплуатации, пользователи системы требуют
добавить к ней все новые и новые полезные возможности и готовы
финансировать эти доработки.
Конечно, в связи с изменившимися требованиями можно разрабо)
тать совершенно новое ПО. Но экономически более выгодным оказы)
вается другой подход – изменить, модифицировать уже существую)
щее ПО.
1.1.4. Роль дизайна программ в уменьшении стоимости
жизненного цикла программного обеспечения
Объем капиталовложений в одну из стадий жизненного цикла ПО
неизбежно влияет на средства, затрачиваемые на других его стади)
ях. Например, увеличение финансирования на стадии проектирова)
ния, безусловно, приведет к снижению стоимости и кодирования, и
отладки.
Но наибольшую долю стоимости жизненного цикла ПО состав)
ляет стоимость этапа сопровождения. Поэтому в первую очередь
необходимо принимать меры, которые будут способствовать сни)
жению стоимости сопровождения, т. е. обеспечивать модифици)
руемость ПО [10].
Неучет этих объективных факторов может привести к пагубным
последствиям.
Пусть, например, достигнуты следующие сокращения затрат на
отдельных стадиях этапа разработки:
– ненадлежащее определение требований и спецификаций сокра)
тило затраты на эту стадию в 3 раза: 10% => 3%;
– ненадлежащее проектирование сократило затраты на эту ста)
дию также в 3 раза: 23% => 7%;
8
– ненадлежащее кодирование сократило затраты на эту стадию
еще больше, в 10 раз: 17% => 2%.
Сократившиеся затраты составляют: 3 + 7 + 2 = 12% (вместо
50%). Кажется, что достигнута заметная экономия средств. Но это
впечатление не соответствует действительности.
Практика показывает, что ненадлежащее выполнение перечис)
ленных стадий этапа разработки приводит к резкому увеличению зат)
рат на стадии отладки – как минимум, в 10–15 раз (50% => 600%).
В результате общая стоимость этапа разработки увеличилась в 6 раз!
Теперь рассмотрим влияние ненадлежащего выполнения этапа раз)
работки в целом на общую стоимость полного жизненного цикла ПО:
– ненадлежащая разработка увеличивает стоимость этого этапа в
5–6 раз: 40 · 5 = 200%.
– ненадлежащая разработка увеличивает стоимость этапа сопро)
вождения, по самой скромной оценке, на 1–2 порядка, т. е. примерно
в 50 раз: 60 · 50 = 3000%.
В итоге, вследствие легкомысленной экономии капиталовложе)
ний на стадиях системного анализа, проектирования и кодирования
этапа разработки ПО общая стоимость жизненного цикла ПО увели)
чилась примерно в 30 раз.
Известна поговорка: «Скупой платит дважды». Коэффициент рас)
платы за скупость и непрофессионализм в области разработки ПО
существенно выше!
Таким образом, переоценить роль профессионального дизайна
программ в общем радикальном снижении затрат на разработку ПО
едва ли возможно. Профессиональный дизайн программ на порядки
снижает затраты на обеспечение и правильности, и модифицируемо)
сти ПО, т. е. его важнейших качеств.
1.2. Стиль программирования как элемент дизайна программ
1.2.1. Составные части стиля программирования
Одна из главных составных частей дизайна программ – использо)
вание надлежащего стиля программирования.
К элементам стиля относятся, в частности, следующие:
– следование проверенным практикой принципам надлежащей
разработки программ, в том числе – принципу простоты и принципу
явного формулирования;
– тщательный выбор имен данных и функций;
– продуманное комментирование исходного текста;
– последовательное использование стандарта кодирования.
9
Фундаментальный принцип, которым можно руководствоваться
буквально во всем, что касается стиля программирования, очень про)
стой:
всегда думайте о читателе!
Независимо от того, какой фрагмент программы вы пишите, в пер)
вую очередь задайте себе вопрос: насколько просто поймет другой
читатель то, что я имею в виду, и то, что делает этот фрагмент?
Наша главная задача при разработке ПО состоит не в том, чтобы
проинструктировать компьютер, а в том, чтобы объяснить другим
людям, какие действия компьютера нам нужны.
Немедленное понимание при чтении чрезвычайно важно. Любая,
даже незначительная, задержка внимания при чтении нежелатель)
на: она может привести к необходимости перечитывания и, может
быть, к ошибке.
1.2.2. Отламывание ломтей (chunking) и правило семи единиц
В связи с необходимостью учитывать особенности восприятия тек)
ста читателем рассмотрим основной метод, который использует че)
ловеческий мозг для увеличения количества усваиваемой информа)
ции. Этот метод называется отламыванием ломтей (chunking).
Один ломоть – это узнаваемая порция информации, которую мож)
но поместить в один из «слотов» кратковременной памяти человека.
Фактическое количество информации в одном ломте может варьиро)
ваться в весьма широких пределах; необходимо только, чтобы ло)
моть распознавался как единичный пакет.
Программа – это единичный ломоть, который разделен и под)
разделен на все меньшие и меньшие ломти. Каждый из них дол)
жен быть отчетливо различимым при чтении и логически обособ)
ленным. Хорошо осведомленный читатель может читать больши)
ми ломтями, а менее опытный должен работать с ломтями мень)
шего размера.
С отламыванием ломтей тесно связано существующее в природе
правило, известное как «правило семи единиц». Правило семи еди)
ниц проявляется при объединении разрозненных элементов в систе)
му. Например, управляемость оптимальна, когда количество управ)
ляемых объектов составляет семь.
Это правило действует в самых разных ситуациях: начиная с того,
как группируются листья растений, и кончая количеством уровней в
сетевой модели взаимосвязи открытых систем OSI (Open System
Interconnection) или количеством уровней иерархии католической
10
церкви. Правилу семи единиц подчиняется и количество ломтей ин)
формации, которыми может свободно оперировать кратковременная
память человека.
Одно из простых применений этого правила в программирова)
нии состоит в том, что в одной функции может быть примерно семь
ломтей кода, каждый из которых содержит семь инструкций. Это
определяет приемлемый размер одной функции – около 49 стро)
чек.
В конкретных ситуациях часто бывает затруднительно определить
предельно допустимое количество групп и элементов группы. Поэто)
му расширенное правило семи единиц выглядит так:
7 ± 2.
Применяя расширенное правило семи единиц к программам, по)
лучим, что приемлемый размер одной функции может составлять от
25 до 81 строки.
1.2.3. Принцип простоты
Один из главных принципов, входящих в понятие надлежащего
стиля программирования, – принцип простоты.
Он называется принципом KISS (Keep It Simple, Stupid!). Удач)
ный перевод названия этого принципа на русский язык – принцип
ПИСК (Простофиля, Избегай Сложных Конструкций!).
Как известно, не существует и никогда не будет существовать та)
кой язык программирования, который делал бы написание плохих
программ хоть сколько)нибудь более сложным, чем написание хоро)
ших.
Правда, на языке С/С++ подозрительно легко написать такой код,
который, несмотря на краткость и выразительность, будет понятен
только автору. Но после некоторого (зачастую непродолжительного)
размышления то же самое можно написать намного более понятно.
Сложность растет с увеличением количества переменных и опера)
ций, использованных в одном выражении. Глубокая вложенность
также увеличивает сложность.
Конечно, добиваясь простоты, не следует впадать в крайности.
Если понимать простоту слишком буквально, то следует использо)
вать однобуквенные идентификаторы и избегать комментирования.
Но тогда исчезает ясность [6].
Таким образом, простота и сложность связаны между собой так
же тесно, как две стороны одной медали. Удачное решение зависит от
конкретной программы.
11
1.2.4. Тщательный выбор имен данных и функций
Выбор подходящих имен для всех объектов в программе – одна из
наиболее сложных задач, которые решает программист в ходе разра)
ботки. Одновременно эта задача оказывается и одной из наиболее
типовых.
Как сделать имя осмысленным?
Хорошо, если имя описывает, что делает названный им объект,
для чего он нужен. Желательно, чтобы имя естественно звучало при
чтении. Но главная цель, которую преследуют и эти, и все другие
рекомендации, состоит в следующем:
имя должно помочь читателю
понять назначение названного им объекта сразу,
еще ДО прочтения остального кода.
1.2.5. Продуманное комментирование исходного текста
Продуманное комментирование программ – настоящее искусство.
Как и любому искусству, научить продуманному комментированию
невозможно, но ему можно научиться.
В идеальном случае программа не нуждается в каких)либо ком)
ментариях. Назначение кода становится понятным просто по назва)
ниям переменных и функций, исходя из структуры программы и упот)
ребленных конструкций языка.
Обычно это и в самом деле так – для автора программы. Но сам
программист может судить о понятности своего кода не больше, чем
художник – о ценности своей картины; он может только догадывать)
ся. Единственно верный критерий – это легкость понимания програм)
мы другим читателем.
На какого читателя нужно ориентироваться?
Можно считать, что он знает английский язык, а также использо)
ванный язык программирования. Этот человек умеет читать програм)
мы и при чтении способен понимать, с какой целью написан код,
какие действия выполняют отдельные конструкции и функции, а
также, почему эти действия осуществляются именно указанным спо)
собом.
Если такое понимание возможно без комментирования, то ком)
ментарии не нужны. Но большинство программ выигрывает в глазах
читателя, если код сопровождается некоторыми пояснениями.
Кроме всего прочего, не забывайте о следующем: очень вероятно,
что читателем вашей программы окажетесь вы сами. Представьте
12
себе, как приятно вам будет читать, испытывая чувство внутреннего
удовлетворения от хорошо сделанной работы!
1.2.6. Последовательное использование стандарта кодирования
Стандарт кодирования – это совокупность требований и рекомен)
даций, которые относятся к употреблению изобразительных средств
языка и размещению текста программ.
К элементам стандарта кодирования относятся, в частности, сле)
дующие:
– правила написания имен данных и функций;
– использование отступов и величина шага отступа;
– использование пробелов и пустых строчек;
– употребление и размещение в тексте фигурных скобок;
– употребление и размещение в тексте круглых скобок;
– размещение в тексте знаков препинания.
Следование стандарту кодирования помогает разрабатывать на)
дежные и понятные программы. Но не стоит переоценивать роль стан)
дарта – это лишь один из инструментов разработчика. Само по себе
неукоснительное следование стандарту еще не гарантирует получе)
ния хорошего результата.
Конечно, даже слабый или невнимательный программист может
многому научиться, если будет использовать стандарт в своей рабо)
те. И все же, стандарт предназначен, главным образом, для хороших
программистов. Он должен помочь им выработать последовательный
подход к программированию вообще и к написанию текстов программ
в частности.
1.2.7. Различие между требованиями и рекомендациями
Некоторые требования стандарта кодирования необходимо соблю)
дать строго. Другие требования могут быть не такими жесткими.
Поэтому важно обращать внимание на формулировки стандарта.
Например:
NEVER – не делайте никогда;
AVOID – не делайте почти никогда;
PREFER – отклонения допустимы в редких случаях;
RIGHT – делайте только так.
Когда возникает настоятельная необходимость игнорировать ка)
кие)то требования, то причины должны иметь «негативную» форму)
лировку. Иначе говоря, нарушить требования можно, когда причи)
на такая: «Если я буду следовать стандарту, то может произойти
13
нечто плохое». Но нельзя отступать от стандарта, если «при этом
произойдет нечто хорошее». Следование приведенному правилу по)
могает предотвратить появление запутанного кода.
1.3. Выполнение учебных работ
1.3.1. Учебные работы и их цель
Прежде всего, необходимо иметь в виду, что лабораторные и кур)
совые работы – это учебные работы. Цель каждой работы состоит в
том, чтобы студенты приобрели знания и умения, которые окажутся
полезными при решении других, гораздо более сложных задач.
Поэтому само по себе решение поставленной задачи еще не явля1
ется достаточным условием для получения зачета. Отчет о выпол)
ненной работе должен демонстрировать знание и практическое уме)
ние использования надлежащих методов разработки и документиро)
вания ПО [5, 6, 9].
Здесь уместно провести параллель с музыкальным исполнитель)
ством. Роль учебных работ по программированию для студентов со)
ответствует роли гамм и этюдов для музыкантов.
Характерная деталь: освоив азы мастерства, музыканты любого
уровня продолжают ежедневно играть и гаммы, и этюды; усидчиво
работают над, казалось бы, мелкими деталями исполнения. Так же
обстоят дела и в программировании – в нем нет второстепенных под)
робностей, важную роль играет любая мелочь.
Лабораторные и курсовые работы связаны с изучением так назы)
ваемой «науки программирования». В это понятие входят следую)
щие темы.
1) работа с массивами (на примере сортировки);
2) рекурсивные методы;
3) обработка списков;
4) работа с таблицами;
5) обработка файлов;
6) обработка текстов.
Владение перечисленным кругом вопросов образует фундамент про)
фессиональной квалификации разработчика ПО любого профиля.
Содержательная часть конкретного задания обсуждается инди)
видуально с каждым студентом.
1.3.2. Язык программирования С/С++
Все работы выполняются с использованием языка программирова)
ния С/С++. Термин «язык С/С++» означает: «подмножество изобрази)
14
тельных средств языка С++, которое совпадает с языком Си и добавля)
ет к нему незначительные расширения, которые не выходят за рамки
средств функционально)ориентированного программирования».
В частности, могут быть использованы только стандартные
функции библиотеки Си, но не средства библиотеки С++.
Вышеупомянутые расширения языка Си в языке С++ следующие.
Данные типа bool и литералы false и true.
Данные типа ссылка.
Возможность определения типов структур (structs), объедине)
ний (unions) и перечислений (enums) без использования инструкции
typedef.
Операции управления памятью в куче: new, delete, new[ ], delete[ ].
1.3.3. Обязательное использование стиля программирования
Практика показывает, что текст программы, который с самого
начала спроектирован с использованием надлежащего стиля програм)
мирования, содержит существенно меньше ошибок – как минимум,
в 10–15 раз. Затраты на отладку и сопровождение ПО также ради)
кально уменьшаются – как минимум, на 1–2 порядка.
Поэтому следует выработать привычку использовать надлежащий
стиль (в том числе, надлежащее форматирование текста) с первых
шагов работы над программой, а не только при подготовке отчета
(так сказать, «для преподавателя»).
Использование излагаемого в этой книге стиля программирова)
ния в лабораторных и курсовых работах является обязательным.
Оформление программы без учета этого требования автоматически
влечет необходимость переработки текста до начала обсуждения про)
деланной работы с преподавателем.
1.3.4. Использование визуального подхода к разработке программ
Одна из важнейших целей обучения – формирование и развитие
образных, визуальных представлений в мышлении разработчика.
Практика показывает, что именно образное мышление оказывается
наиболее действенным инструментом при разработке ПО (хотя логи)
ческое мышление, конечно же, сохраняет свое значение) [4].
Поэтому на занятиях в аудитории рассматриваются многочислен)
ные рисунки. Подобные рисунки необходимо включить в отчет о вы)
полненной работе.
Необходимо самостоятельно принять решение о том, какие имен)
но рисунки оказались полезными при выполнении конкретного за)
дания.
15
а)
12345 67285 9
9 67285
592 24
14925
2
59 24
18945 2
224 56
9
б)
38
3
789
789
3
789
35
3
34
36
789
5
3
3
3
Рис. 1.2. Схемы алгоритмов: а – основные обозначения; б – пример
использования
Одно из визуальных средств представления ПО – схемы алгорит)
мов (СхА). Основные обозначения и пример использования СхА пред)
ставлены на рис. 1.2.
Практика разработки ПО показывает, что СхА оказываются не
особенно полезным инструментом разработки. Если стандарты орга)
низации)производителя ПО требуют наличия схем алгоритмов, то
их практически всегда рисуют после завершения проектирования.
Но одна из целей обучения состоит в том, чтобы студенты приоб)
рели знание правил оформления схем алгоритмов и умение их ис)
пользовать. Поэтому в состав отчета необходимо включать 1–2 схе)
мы алгоритмов для логически сложных фрагментов программы.
16
1.3.5. Обязательные и необязательные комментарии
Поскольку выполняемые работы ориентированы на достижение
учебных целей, некоторые элементы комментирования являются
обязательными. Их отсутствие автоматически влечет необходимость
переработки текста до начала обсуждения проделанной работы с пре)
подавателем.
Следует выработать привычку разрабатывать комментарии парал)
лельно с разработкой текста на языке программирования, а в неко)
торых случаях – даже прежде этого текста.
На ранних этапах работы над программой программист разрабаты)
вает алгоритмы и структуры данных с использованием естественного
языка (например, русского). Эти действия программиста выполняются
на стадиях системного анализа и проектирования этапа разработки ПО.
Здесь должно проявиться главное умение программиста – уме1
ние формулировать, формулировать кратко и точно.
По мере продвижения работы программист заменяет формули)
ровки на естественном языке соответствующим текстом на языке
программирования. В интересах последующего сопровождения
ПО формулировки на естественном языке желательно оставить в
тексте программы – в форме комментариев)заголовков блоков
кода.
Повторим: это необходимо для уменьшения затрат на разработку
текста (так называемого «кодирования»), отладку и сопровождение
ПО, а не потому, что «этого требует преподаватель».
Таким образом, работа над текстом программы может начаться с
того, что в него включают подобные комментарии)заголовки, а текст
на языке программирования появляется позже.
Обязательные комментарии следующие.
Комментарий)заголовок файла (образцы см. разд. 7). Коммента)
рий)заголовок главного модуля должен содержать дополнительные
реквизиты.
Комментарий)заголовок функции (разд. 8).
Сопровождающий комментарий для каждого параметра в заго)
ловке функции с параметрами (разд. 7).
Сопровождающий комментарий для каждого описания (definition
или declaration) элемента данных (разд. 7).
Необязательные комментарии следующие.
Комментарий)заголовок ломтя (chunk) кода (разд. 7).
Сопровождающий комментарий в конце строчки (как правило,
со сложной для немедленного понимания инструкцией; вообще гово)
ря, подобных инструкций следует избегать).
17
Констатирующий комментарий в конце ломтя (chunk) кода (разд. 7)
и, возможно, другие.
Вести комментирование можно или на английском языке, или на
«смеси» английского и русского языков. Первый вариант – предпочти)
тельный, поскольку английский язык является языком межнациональ)
ного общения в области информатики. Однако при недостаточном вла)
дении английским языком следует использовать второй вариант.
1.3.6. Страничное размещение текстов программ
Тексты программ должны быть приспособлены для продуманного
вывода на бумагу.
При чтении программ часто бывает, что нужно смотреть то впе)
ред, то назад, сосредотачиваясь на разных частях текста. Все эти ча)
сти желательно расположить на одной и той же странице, чтобы сде)
лать их видимыми одновременно. Если листать страницы вперед и
назад не нужно, то внимание не рассеивается, а работа становится
проще и выполняется быстрее.
Инструкции, которые образуют текст программы, «самопроиз)
вольно» проявляют тенденцию объединяться в группы. Каждая та)
кая группа (ломоть текста) должна быть размещена в пределах од)
ной и той же страницы.
Еще до начала обсуждения с преподавателем результатов любой
работы, проделанной с использованием компьютера, необходимо сде)
лать следующее.
Должен быть использован шрифт Courier New размером 10 пун)
ктов.
Текст должен быть разбит на функционально обособленные ча)
сти, каждая из которых умещается на одну страницу формата А4
(210 297 мм). Одна такая часть должна состоять из одного или
нескольких полных ломтей текста.
При использовании шрифта Courier New размером 10 пунктов
размер страницы не должен превышать 64 строчек, а размер одной
строчки – 72 символов.
Каждая страница текста файла, начиная со второй, должна
содержать в строчке 1 (в позиции 37) заголовок страницы, кото)
рый включает имя файла и номер страницы (примеры приведены в
разд. 7).
Роль заголовка первой страницы файла играет комментарий)
заголовок файла в целом.
Перед началом очередной (т. е. не первой) страницы в файле с
текстом программы необходимо оставить 3 пустые строчки.
18
Постраничную печать можно осуществить с помощью режима «пе)
чать выделенного фрагмента текста».
Во избежание проблем при печати текста с использованием раз)
личных текстовых редакторов необходимо отказаться от использо)
вания в тексте символов горизонтальной табуляции. Работая в IDE
MS Visual C++ 6.0, для этого нужно сделать следующее.
Выйти в пункт меню Tools.Options (настройка параметров про)
граммных инструментов).
Выбрать в появившемся многостраничном диалоговом окне зак)
ладку Tabs (настройка использования символов табуляции и отступов).
Установить величину шага табуляции (Tab size) 4 позиции и
режим вставки пробелов вместо использования символов табуляции
(Insert spaces).
Отказаться от автоматической вставки отступов (Auto
indent.None).
1.3.7. Результаты деятельности: приобретенные знания и умения
Изучая опыт лучших специалистов, разрабатывая программные
проекты и критически оценивая свои разработки, можно научить1
СЯ программированию. Возвратная частица «)ся» в слове «научить)
ся» подчеркивает необходимость активного участия обучаемого че)
ловека в овладении предметом. Конечно же, это справедливо приме)
нительно к любому обучению, а не только к обучению программиро)
ванию.
В результате успешного выполнения лабораторных и курсовых
работ можно приобрести следующие знания и умения (перечислены
только самые важные).
1. Умение формулировать – формулировать кратко и точно.
2. Знание методов иерархической декомпозиции (в том числе фай)
ловой и функциональной) и умение использовать ее на практике.
3. Умение использовать «упрятывание данных» (data hiding) и
понимание полезности «упрятывания».
4. Знание «науки программирования» и умение применять это
знание на практике.
5. Умение создавать программы с профессиональным дизайном.
6. Умение разрабатывать самодокументированные тексты про)
грамм.
Опыт лучших специалистов показывает, что перечисленные зна)
ния и умения хорошо зарекомендовали себя на практике как дей)
ственные средства преодоления сложности больших программных
проектов [4, 7, 8].
19
2. КОММЕНТИРОВАНИЕ
Основу хорошего комментирования составляют немногочислен)
ные принципы, которые рассмотрены в этом разделе. Ими можно ру)
ководствоваться практически во всех случаях.
2.1. Основы комментирования
2.1.1. Комментарии должны быть ясными и краткими
Комментарии должны помочь читателю понять код. Они не долж)
ны быть слишком краткими, или слишком зашифрованными, или
чересчур многословными. Искусство комментирования (а это – самое
настоящее искусство) состоит в том, чтобы коротко и ясно сформу)
лировать: что происходит (и, возможно, почему происходит).
В качестве примера рассмотрим три варианта комментария к од)
ной и той же инструкции.
Variant1:
DoorStatus = OPEN;
Variant2:
DoorStatus = OPEN;
Variant3:
DoorStatus = OPEN;
// пароль не нужен
// теперь можно входить
// без ввода секретного пароля
// теперь двери открыты;
// это значит,
// что можно входить в систему
// без ввода какоголибо
// секретного пароля
Все три комментария поясняют, что происходит при открывании
двери, но первый из них несколько туманный, а последний – черес)
чур подробный.
Чувствуется, что существует «золотая середина» между предель)
ной краткостью и ненужным многословием вокруг мелких подробно)
стей. Однако найти ее непросто. К тому же не существует коммента)
рия, который покажется оптимальным всем. То, что один читатель
назовет исчерпывающим, окажется для другого недостаточным, а для
третьего – излишним объяснением и без того очевидного.
Комментарии можно сделать короче, если использовать аббреви)
атуры и выбросить часть слов. Но при этом нужно быть очень осмот)
рительным, чтобы не внести двусмысленности и не повредить немед)
ленному пониманию при чтении. Даже незначительная неясность
20
будет приводить к тому, что читателю придется переключать кон)
текст на время, которое потребуется для обдумывания значения ком)
ментария.
Рассмотрим сокращенный вариант комментария к инструкции с
меткой Variant2.
Variant2:
DoorStatus = OPEN;
// можно входить б/ввода секр.
// парл.
Слово «теперь» в начале комментария выброшено без заметного
ущерба. Сокращение «б/ввода» сравнительно легко расшифровыва)
ется как «без ввода»; «секр.» приводит к краткому колебанию («сек)
ретарский» подходит, но это же абсурд, это должно быть слово «сек)
ретный»). Сокращение «парл.» вызывает задержку внимания (не)
ужели это «парламент»?).
Хорошие комментарии всегда дают полезную дополнительную
информацию. Они не должны повторять то, что уже написано на язы)
ке программирования, как в следующем примере:
DoorStatus = OPEN;
// теперь дверь находится
// в состоянии «открыто»
Разумно придерживаться такого правила: короткие комментарии
поясняют, что происходит, а более длинные – почему происходит.
2.1.2. Комментарии должны быть правильными
Хотя это и кажется очевидным, хочется еще раз напомнить: ком)
ментарии должны по смыслу совпадать с кодом! Вот пример несоблю)
дения этого требования:
DoorStatus = OPEN;
// дверь открыта,
// но нужно ввести секретный пароль
Этот комментарий содержит неверные сведения и тем самым дез)
ориентирует читателя.
Такие вещи часто случаются, если исправления вносятся в спеш)
ке: текст на языке программирования исправляют, забывая испра)
вить также и комментарий. В результате многие специалисты, кото)
рые занимаются сопровождением ПО, вообще перестают читать ком)
ментарии, пытаясь разобраться в программе только по ее коду.
Видеть это довольно грустно. Но, в конце концов, восстановление
доверия к комментариям зависит от создателей ПО, то есть от нас
самих.
21
Овладеть искусством адекватного и понятного комментирования
непросто. Но сделать это необходимо, поскольку умение комменти)
ровать – один из определяющих элементов профессиональной ква)
лификации программиста.
2.1.3. Комментарии должны быть легко отличимыми от кода
При чтении программ иногда требуется читать только коммента)
рии, например, просматривая текст в поисках какого)нибудь конк)
ретного действия. Аналогично, бывает нужно читать только код.
В обоих случаях очень удобно, если комментарии и код легко от)
личить друг от друга. Тогда отфильтровывание тех частей текста,
которые нужно прочитать, происходит практически мгновенно:
// Reset system status flags to null values,
// SetFlags (later) will be used to set default values
//
ResetFlags( SystemStatus );
// Activate database again
//
DB_Restart( );
При отсутствии четкого различия между кодом и комментариями
легко принять фрагмент кода за фрагмент комментария:
// Reset system status flags, SetFlags (later) will be used
/* to set default values */ ResetFlags( SystemStatus )
// Activate database again
*/
DB_Restart( );
Также возможно принять фрагмент комментария за фрагмент
кода, хотя это случается реже:
/* Reset system status flags to null values,
SetFlags (later) will be used to set default values */
ResetFlags( SystemStatus );
DB_Restart( );
Употребление комментариев, которые «изображают» горизон)
тальные линии – спорный вопрос. Излагаемый в этой книге стан)
дарт запрещает использование таких комментариев по следующим
причинам. Во)первых, в результате их использования текст стано)
вится более «плотным» и, следовательно, менее удобным для чте)
ния. Во)вторых, пустая строчка в тексте программы зрительно раз)
деляет фрагменты текста ничуть не хуже, чем горизонтальная «ква)
зи)линия».
22
2.2. Типы комментариев
В разных частях программы комментарии используют в разных
целях. В зависимости от конкретной цели комментарий можно отне)
сти к одному из следующих трех типов: комментарий)заголовок, ком)
ментарий блока текста или сопровождающий комментарий.
Комментарий1заголовок помещают в начале значительного фраг)
мента программы. Такие комментарии, как правило, сравнительно
длинные (обычно от 10 до 50 строк). Их текст структурируют – раз)
бивают на секции (например, описание исходных данных, описание
результатов и т. п.).
Приемлемый размер заголовка меняется в зависимости от размера
и сложности комментируемого кода. Например, вполне разумно снаб)
дить довольно обширным заголовком большую и сложную функцию.
Но требовать того же для функции длиной в пять строчек, по мень)
шей мере, странно.
Здесь сложно давать количественные рекомендации. Заголовок
имеет приемлемый размер, когда интуиция подсказывает, что пояс)
нений достаточно. Пробным камнем, как и всегда при комментиро)
вании, остается простота понимания текста будущим читателем.
Комментарий блока текста связан с небольшим фрагментом кода
(например, очередным ломтем). Соответственно, размер таких ком)
ментариев значительно меньше (обычно от 1 до 5 строчек), и они со)
держат неструктурированный текст.
Предваряющий комментарий блока текста детализирует дей)
ствия, которые будут выполнены кодом, который расположен ниже,
вслед за комментарием.
Констатирующий комментарий блока текста описывает состо)
яние программы, полученное в результате выполнения кода, кото)
рый расположен выше.
Сопровождающий комментарий поясняет назначение единствен)
ной строки кода. Обычно такие комментарии размещают в той же
самой строчке текста и пишут в настоящем времени.
2.3. Комментарии – заголовки файлов
Когда читатель видит программу впервые, то прежде всего он смот)
рит на комментарий)заголовок в начале файла. Этот заголовок дол)
жен помочь ему понять, для чего предназначен файл, что и зачем он
делает, etc.
Заголовок файла должен содержать следующие разделы.
Название файла.
23
Название проекта.
Описание назначения файла.
Сведения об авторе и дате создания.
Сведения о фирме)производителе программы.
Сведения о внесенных изменениях.
Необязательные дополнительные сведения.
Название исходного файла, подобно названию книги, формули)
рует основную идею. Кроме того, оно позволит читателю при необхо)
димости отыскать файл на диске. Если название файла – аббревиату)
ра, то нужно ее раскрыть.
Название проекта играет справочно)регистрационную роль. Оно
также оказывается ссылкой на документацию по проекту в целом.
Описание назначения файла должно в самых общих чертах подго)
товить читателя к восприятию последующего текста. Если файл –
связный и функционально законченный, то достаточно указать, ка)
кие функции он выполняет или какие объекты он содержит.
Сведения об авторе идентифицируют вас. Указывать их приятно,
если вы испытываете чувство профессиональной гордости своей хо)
рошо выполненной работой (а такое чувство должно быть обязатель)
но!). Если у читателя возникнут вопросы, он будет знать, кто может
на них ответить. Но даже если обратиться к вам невозможно, сведе)
ния об авторе небесполезны: они подготовят читателя к особеннос)
тям вашего стиля программирования.
Сведения о фирме)производителе программы и утверждение
Copyright, вероятно, не нуждаются в пояснениях.
В конце помещают информацию об изменениях. После того, как
код «заморожен», любое изменение должно быть зарегистрировано в
соответствующей секции заголовка. Такая регистрационная запись
должна содержать следующие реквизиты.
Идентификационный номер изменения. Если изменение затра)
гивает несколько файлов, то все измененные файлы должны содер)
жать ссылку на один и тот же номер.
Дату внесения изменения (в формате ДД.ММ.ГГГГ).
Фамилию (или инициалы) лица, вносившего изменения.
Название измененной функции (комментарии по поводу деталь)
ных подробностей лучше поместить рядом с измененным фрагмен)
том кода).
Могут найтись и другие полезные сведения, которые трудно отне)
сти к одному из перечисленных разделов. К ним относятся различ)
ные предупреждения, указания, предположения, ограничения, etc.
Если вы считаете, что эти сведения смогут помочь читателю лучше
понять ваш код, их также нужно включить в заголовок.
24
Таким образом, комментарий – заголовок файла должен иметь
следующий вид:
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
FILE
: Lex.cpp
LEXical analysis
PROJECT
: SQL Syntax Checker
PURPOSE
: implementation of lexical analysis
NOTES
: none
AUTHOR
DATE
: Michael F.LEKAREV
: 23.04.1996
(C) Copyright
CHANGES
RefNo.
L08/01
Firm Software Ltd., 1996. All rights reserved
Date:
Who:
dd.mm.yyyy
05.06.1996 LMF
Detail:
OM «LexStart» corrected
2.4. Комментарии – заголовки функций
Когда читатель приступает к чтению кода функции, у него уже
должно быть ясное представление о том, что и как делает эта функ)
ция, и о том, какие проблемы встретятся при чтении. Сформировать
это представление должен заголовок функции.
Заголовок функции должен содержать следующие разделы.
Раскрытие аббревиатуры, использованной в качестве названия
функции.
Описание назначения функции.
Описание исходных данных.
Описание результатов.
Описание внесенных изменений и дату изменения.
Лучшим описанием назначения функции должно быть ее назва)
ние. Дополнительное краткое описание выполняемой операции, как
правило, способствует правильному пониманию.
Обычно функция обрабатывает исходные данные (inputs), чтобы
сформировать результаты (outputs). Сведения об этом – важная часть
заголовка.
Исходные данные включают не только параметры, но и глобаль)
ные переменные и, в некоторых случаях, константы. Полезно ука)
зать допустимые значения исходных данных. Эти сведения не обяза)
тельно давать явно: можно ограничиться ссылкой на соответствую)
щее описание.
25
Результаты включают измененные параметры, измененные гло)
бальные переменные и возвращаемое значение функции. Так же, как
и для исходных данных, полезно указать, какие значения могут при)
нимать результаты.
После того, как код «заморожен», любое изменение кода функции
должно быть зарегистрировано. Такая регистрационная запись дол)
жна содержать следующие реквизиты.
Идентификационный номер изменения.
Дату внесения изменения (в формате ДД.ММ.ГГГГ).
Фамилию (или инициалы) лица, вносившего изменения.
Описание изменений.
Изменения, внесенные в код функции, обычно достаточно сопро)
водить идентификационным номером изменения.
Может потребоваться включить в комментарии – заголовки фун)
кций и другие полезные сведения. Их нужно объединить в раздел
«Замечания» (notes).
Для того чтобы избежать ненужного дублирования, лучше всего
интегрировать комментарий – заголовок функции с заголовком фун)
кции на языке программирования:
// GET source text LINE:
// ввести очередную строчку исходного текста
// ответ: SUCCESS => строчка успешно введена
//
FAILURE => вместо очередной строчки
//
прочитан конец файла
// Замечания:
// предельно допустимая длина строки
// определена константой LINESIZE
//
int GetLine
(
// ИСХОДНЫЕ ДАННЫЕ:
FILE *
SysIn,
// SYStem INput:
// открытый поток
// с исходным текстом
char *
)
{
. . .
} // end function
26
InLine
// РЕЗУЛЬТАТЫ:
// INput LINE:
// массив, заполняемый литерами
// вводимой строки
2.5. Комментарии блоков текста
Комментарии блоков текста обычно требуются внутри функций в
связи с ломтями (chunks) кода. Такой ломоть содержит либо функ)
ционально законченную группу инструкций, либо обособленную груп)
пу описаний данных.
2.5.1. Размер
В зависимости от размера комментарии блока текста можно раз)
делить на следующие категории:
однострочный комментарий, содержащий простое пояснение;
многострочный комментарий (до 5 строчек), поясняющий слож)
ный фрагмент программы.
Если размер комментария превышает 5 строчек, то лучше всего по)
местить его не в теле функции, а в ее заголовке. Разумно также при)
держиваться такого правила: количество строчек комментария не дол)
жно превышать количества строчек комментируемого ломтя текста.
2.5.2. Выделение текста комментария
Излагаемый в этой книге стандарт требует использования следу)
ющих правил.
Ограничители многострочных комментариев блоков текста сле)
дует располагать в форме вертикальной полосы слева:
// Найти в таблице запись об обозреваемом символе
// и увеличить счетчик повторений символа
//
Отсутствие ограничителя справа делает текст в целом менее «плот)
ным» и упрощает его ввод. Ограничители комментария вертикально
выравнены и потому при чтении легче ассоциируются друг с другом.
Однострочные комментарии блоков текста рекомендуется давать
в том же формате, что и многострочные:
// Закрыть все открытые окна
//
for ( IxWin = 0; IxWin < WinNum; ++IxWin )
{
CloseWin( Win[ IxWin ] );
}
2.5.3. Размещение по горизонтали
Комментарий должен быть сдвинут вправо по отношению к соот)
ветствующему блоку кода на один шаг отступа. Следование этому
правилу позволяет легко различать комментарий и код.
27
2.5.4. Размещение по вертикали
Комментарии должны примыкать к соответствующему блоку кода,
а от соседних блоков должны быть отделены пустой строчкой. Это
требуется для того, чтобы читателю было проще ассоциировать ком)
ментарий и код.
Таким образом, предваряющий комментарий должен примыкать
к коду сверху, а констатирующий комментарий должен примыкать к
коду снизу:
// Найти в таблице запись об обозреваемом символе
// и увеличить счетчик повторений символа
//
for ( IxTab = 0; IxTab <= Table.IxLast; ++IxTab )
{
if ( Table.Body[ IxTab ].Value == NewSym )
{
++Table.Body[ IxTab ].Repeat;
return;
}
}
//
// Здесь: обозреваемый символ не найден
// Поместить в таблицу
// запись об обозреваемом символе
//
if ( Table.IxLast == (TABSIZE1) )
{ ...
Если текст, к которому примыкает снизу констатирующий ком)
ментарий, завершается несколькими фигурными скобками (как в
приведенном примере), то глаз читателя воспринимает «примыка)
ние» легче, если после комментария оставлено увеличенное свобод)
ное пространство (три строчки).
Если комментарий в равной степени относится к обоим ломтям
кода, которые расположены сверху и снизу от него, то он должен
быть выделен пустыми строчками с обеих сторон.
2.6. Сопровождающие комментарии
Сопровождающие комментарии поясняют единственную строчку
кода. Они обычно начинаются (и заканчиваются) в той же самой
строчке, которая содержит комментируемый код.
28
2.6.1. Размещение по горизонтали
Принято сдвигать начало сопровождающего комментария доволь)
но сильно вправо относительно комментируемого кода. Это позволя)
ет легко различать комментарии и код. При таком размещении стра)
ница оказывается разделенной по вертикали на две колонки, как га)
зетный текст.
Подходящая позиция начала «зоны комментария» находится
приблизительно в середине строчки. Но экран персонального компь)
ютера не позволяет видеть 80 символов в строчке одновременно, без
горизонтальной прокрутки. Поэтому лучше поместить левый огра)
ничитель сопровождающего комментария не в позиции 41, а в пози)
ции 37 (в обе позиции приводит табуляция с шагом 4).
2.6.2. Перенос на другую строчку
Разделение страницы по вертикали на зоны кода и комментария
создает проблемы, если длина строки кода или строки комментария
превышает ширину соответствующей зоны.
Излагаемый в этой книге стандарт требует прервать слишком длин)
ную строку (кода или комментария) и продолжить ее в следующей
строчке исходного текста. Для немедленного понимания при чтении
того обстоятельства, что новая строчка текста содержит продолжение
предыдущей, продолжающий текст должен быть сдвинут вправо.
Продолжение кода сдвигается вправо на один шаг отступа (4 по)
зиции). Продолжение комментария сдвигается вправо на две пози)
ции (поскольку зона комментария более узкая). Если строчек про)
должения несколько, то сдвиг сохраняется до тех пор, пока продол)
жается разорванная переносом строка.
Если разрывать строку кода переносом почему)либо крайне неже)
лательно, можно продлить код в зону комментария, а комментарий
полностью перенести на следующую строчку:
SelectSides( Players );
// Выбрать места партнеров
ResetGame( Players, PlayBoard, ScoreBoard );
// Начать новую партию
MakeAMove( White, PlayBoard );// Получить корректный ход
//
и передвинуть фигуру
Перенос комментария на следующую (а не на предыдущую) строч)
ку продиктован тем, что сопровождающие комментарии, скорее, до1
полняют, а не предваряют код. Эта особенность отразилась даже в
их названии.
Следует сразу отметить, что сопровождать комментарием каждую
строку кода не обязательно. Давать сопровождающие комментарии
29
нужно только при необходимости. В отсутствие этих комментариев
разделение страницы по вертикали исчезает, так что строки кода могут
занимать более широкое пространство.
2.6.3. Сопровождающие комментарии и комментарии блоков текста
Использовать сопровождающие комментарии довольно трудно,
поскольку их длина жестко ограничена шириной «зоны коммента)
рия». Это обстоятельство предъявляет повышенные требования к
точности и краткости формулировок. Кроме того, размещение кода и
комментариев в одной строчке исходного текста усложняет разделе)
ние кода и комментариев при чтении.
Достоинства сопровождающих комментариев – возможность дать
более детальное комментирование кода и экономное использование
вертикального пространства на странице.
Комментарии блоков текста занимают больше места по верти)
кали. Зато их легко отличить от кода, и потому ими пользуются
чаще.
2.7. Комментирование данных
Комментирование данных играет особенно важную роль. Хоро)
шее комментирование данных намного важнее для понимания про)
граммы, чем хорошее комментирование кода.
2.7.1. Общие замечания
Непросто разбирать плохо прокомментированные инструкции и,
тем более, инструкции без каких)либо комментариев вообще. Тем не
менее, тщательное изучение такого кода в большинстве случаев по)
зволяет догадаться о том, что он делает. Основой для догадок оказы)
ваются выполняемые действия.
Но данные не выполняют никаких действий. Действия произво)
дятся ради данных и определяются данными, но не осуществляются
ими самими. Поэтому разгадывание назначения непрокомментиро)
ванных данных – нелегкая задача, особенно, если они названы не)
удачно и используются во многих местах.
Определения данных важны не только при чтении программы, но
и при ее создании. Сначала должны быть разработаны определения
структур данных вместе с текстом, помогающим их понять. А когда
они понятны, сама программа часто становится настолько очевид)
ной, что комментировать выполняемые инструкции уже (почти) не
требуется.
30
Наиболее важная стратегия работы с переменными состоит в сле)
дующем: перед использованием переменных определите их, а затем
убедитесь, что их использование соответствует их определению.
С точки зрения комментирования, определения данных можно
рассматривать как «сложный код». Поэтому следует использовать и
комментарии блоков кода, и сопровождающие комментарии.
2.7.2. Определяйте назначение переменных
Идеальным описанием переменной должно быть продуманное на)
звание. Однако может оказаться, что название, даже хорошо согла)
сованное с программным контекстом, заставляет читателя мыслен)
но спрашивать: «Да, но для чего предназначена эта переменная?»
Рассмотрим пример:
int
StdFlex;
Это название можно расшифровать как «standard flex» (стандар)
тная эластичность). Возможно, это какой)то промежуточный резуль)
тат вычислений. Полностью понять назначение переменной по это)
му определению сложно.
В результате непонятно, правильно ли используется переменная
в инструкциях программы. Например:
StdFlex = FlexFactor( Plastic.Flex );
Комментарий определения переменной формулирует намерения
автора программы (как предполагается использовать переменную) и
помогает правильно понять каждое использование переменной:
int
StdFlex; // STanDard FLEX:
//
промежуточное значение
//
при вычислении эластичности
// Учесть эластичность пластика в «StdFlex»
//
StdFlex = FlexFactor( Plastic.Flex );
Употребление названия переменной в комментарии служит для чи)
тателя ссылкой на комментарий, данный в определении переменной.
Этот прием сокращает текст комментария без ущерба для понимания.
2.7.3. Комментируйте все определения данных
Следуя принципу последовательности в употреблении стиля про)
граммирования, необходимо сопровождать комментариями все без
исключения определения данных. Каждая переменная должна иметь
четко сформулированное назначение, и притом – единственное.
31
Сопровождающий комментарий определения элемента данных
должен содержать следующие сведения.
Раскрытие аббревиатуры, использованной в качестве идентифи)
катора (буквы, которые вошли в аббревиатуру, должны быть набра)
ны буквами верхнего регистра).
Формулировку назначения переменной.
Необязательные сведения о возможных значениях переменной.
Сюда же относится комментирование директив препроцессора
#define:
#define
FILENO
6
/* FILE NO:
*/
/* количество рабочих файлов */
2.7.4. Уделяйте особое внимание комментированию таблиц
Комментирование таблиц имеет еще более важное значение, чем
комментирование отдельных переменных. Таблица без пояснений
становится просто беспорядочной кучей констант.
Рассмотрим следующий пример:
int
AgeFactor[ ] =
{
0, 10, 22, 25, 15,
10,
8, 2
};
Что могут означать эти числа? В чем состоит разница между пер)
вым и вторым элементами массива? На эти вполне естественные воп)
росы может и должен ответить комментарий.
Таблицы обычно требуют сравнительно длинных комментариев, ко)
торые не помещаются в одной строчке. Поэтому вместо сопровождаю)
щих комментариев удобнее использовать комментарий блока текста:
// AGE FACTOR:
//
возрастной коэффициент AgeFactor[ AgeBand ]
//
определяет коэффициент коррекции производительности
//
для каждой из принятых в компании
//
стандартных возрастных групп;
//
//
он используется при расчете
//
прогнозируемого продвижения по службе
//
int
AgeFactor[ ] =
{
0, 10, 22, 25, 15,
10,
8, 2
};
32
Обратите внимание на полезные указания, помещенные в этот
комментарий.
Комментирование с помощью примера использования элемента
таблицы.
Комментирование с помощью указания на то, в каких вычисле)
ниях используется таблица.
2.8. Комментирование определений типов данных
В таком же ключе необходимо комментировать и определения ти)
пов данных (структур, объединений, перечислений). Например:
struct TStream
{
char *
Title;
FILE *
File;
// Type STREAM:
//
stream either for input
//
or for output
// TITLE:
// operating system’s
// data set name
// FILE:
//
pointer to
//
C RunTime library’s
//
File Control Block
};
union TFloatCode
{
float
unsigned char
// Type FLOAT CODE:
//
to operate on separate bytes
//
of Single Precision format
Value;
// VALUE:
//
code format as a whole
Byte[ 4 ];// BYTE:
//
the same as separate bytes
};
enum TCharType
{
CharBlank
= 0,
CharLetter = 1,
CharFig10
= 2,
CharEOF
= 3,
CharUnknown = 4,
//
//
//
//
//
//
//
//
//
//
//
//
//
Type CHARacter TYPE:
kind of
a source text character
CHARacter BLANK:
blank character
CHARacter LETTER:
latin letter
CHARacter FIGure 10:
decimal figure
CHARacter End Of FILE:
virtual EOF character
CHARacter UNKNOWN:
not an alphabet member
};
33
3. ИМЕНОВАНИЕ
Повторим, что главная цель, которую преследуют все рекоменда)
ции по именованию любых объектов в программе, состоит в следую)
щем: имя должно помочь читателю понять назначение названного
им объекта сразу, еще ДО прочтения остального кода.
3.1. Общие замечания
3.1.1. Ограничения, связанные с именованием
Как известно, в состав идентификаторов могут входить латинс)
кие буквы верхнего и нижнего регистров (‘A‘..‘Z‘, ‘a‘..‘z‘), деся)
тичные цифры (‘0‘..‘9‘) и символ подчеркивания (‘_‘), который
синтаксически эквивалентен букве. Буквы верхнего и нижнего реги)
стров считаются отличными друг от друга (буква ‘A‘ не совпадает с
буквой ‘a‘).
Ограничение длины идентификатора небольшим количеством
символов (например, 8 или меньше) может помешать конструиро)
ванию осмысленных имен. К счастью, в современных стандартах
языков Си и С++ предельно допустимая длина идентификатора
составляет 31 символ. Это существенно больше, чем требуется на
практике.
Важность продумывания удачных имен часто недооценивают. В
результате в программах появляются «безликие» идентификаторы,
которые несут явно недостаточную смысловую нагрузку. Например:
a,
b,
buf,
ptr,
temp,
Другую крайность составляют очень длинные имена, которые со)
стоят почти что из целых предложений. Например:
int
CurrentWordEnhancementCode;
Взятые отдельно, такие имена понятны. Но при употреблении в
конструкциях языка даже простые выражения становятся сложны)
ми для чтения и понимания. Например:
CurrentWordEnhancementCode =
StandardWordEnhancementCode[ CurrentWordIndex ] +
WordEnhancementCorrection;
Таким образом, нужно избегать крайностей и стремиться к «золо)
той середине», которая находится между безликими аббревиатура)
ми и непрактичными «именами)предложениями». Длина удачных
имен сравнительно невелика, их легко понять и удобно использо)
вать.
34
На практике предельно допустимая длина имени составляет 14–
15 символов. С использованием удачно выбранных имен вышепри)
веденный пример примет следующий вид:
WordEnhCode = STDW_EnhCode[ IxWord ] + WordEnhCorr;
3.1.2. Использование аббревиатур
Сократить длину имен помогает продуманное использование аб)
бревиатур. В результате имена становятся более удобными в упот)
реблении. Парадоксальным образом удачные аббревиатуры также
улучшают читабельность имен.
В 1967 году Майкл Джексон (M. Jackson) предложил использо)
вать для получения удачных аббревиатур набор правил сокращения.
Этот набор, с некоторыми изменениями, следующий.
Рекомендуется оставить в имени не более трех значащих слов.
В аббревиатуру всегда должна входить первая буква каждого
значащего слова.
Рекомендуется оставить все буквы, которые соответствуют глав)
ным звукам слова.
Согласные важнее гласных.
Начало слова важнее его конца.
Фрагменты аббревиатуры должны следовать в том же порядке,
что и слова в раскрытии аббревиатуры.
Рекомендуется составлять аббревиатуры длиной от 6 до 15 сим)
волов.
Аббревиатуры должны быть понятными сами по себе, в отрыве от
контекста. Например, аббревиатура «Wgt» непонятна: она может
означать и «Widget», и «Weight», и что)нибудь еще.
Хороший тест для аббревиатуры: а можно ли ее произнести? По1
настоящему удачная аббревиатура заметно уменьшает количество
букв в имени, оставаясь понятной и при чтении, и на слух. Напри)
мер:
Curs,
Len,
Pos.
После того как аббревиатура составлена, необходимо использо)
вать ее в соответствии с принципом последовательности. Наруше)
ния этого правила могут стать причиной путаницы.
Например, пусть для имени «WindowHeight» использована аб)
бревиатура «WinHeight». Начало аббревиатуры («Win») – это сокра)
щение слова «Window». Тогда для имени «WindowWidth» неправиль1
но использовать аббревиатуру «WndWidth», в которой слово
«Window» сокращено по)другому.
35
3.1.3. Использование коротких имен
Существуют ситуации, когда использование коротких имен луч)
ше, чем использование длинных. Если назначение соответствующих
переменных настолько очевидно, что содержательные имена не нуж)
ны, то короткие имена (длиной 1–2 символа) улучшают читабель)
ность кода.
Однако в учебных работах студентов ставятся учебные цели:
формирование и развитие умения формулировать;
формирование отвращения к небрежному программированию во)
обще и к авторам небрежно спроектированных программ в частности.
Поэтому, среди прочего, использовать короткие имена в учебных
работах запрещается.
Как и всегда, правил без исключений нет. Бесспорно лучшим яв)
ляется употребление однобуквенных идентификаторов в следующих
случаях:
x – обозначение для абсциссы точки или для аргумента математи)
ческой функции;
y – обозначение для ординаты точки;
z – обозначение для аппликаты точки.
Во всех без исключения остальных случаях употребление корот)
ких имен должно быть согласовано с преподавателем.
Важно следить за тем, чтобы область видимости объектов с корот)
кими именами всегда оставалась узкой. Если пренебрегать этим пра)
вилом, то читабельность кода исчезает.
3.1.4. Разделение слов в многословных идентификаторах
Если идентификатор «сконструирован» из нескольких слов, то
эти слова должны быть легко различимыми при чтении.
Излагаемый в этой книге стандарт требует, чтобы соответствую)
щие части идентификатора начинались с большой буквы. Например:
ReadWord( );
++WordCnt;
Это правило называется правилом верхнего регистра в начале
слова.
Имена, составленные по этому правилу, намного нагляднее при
чтении, чем в отсутствие выделения слов:
readword( );
++wordcnt;
При разделении слов символом подчеркивания хорошая различи)
мость достигается, но за счет увеличения длины имени:
read_word( );
36
++word_cnt;
Таким образом, правило верхнего регистра в начале слова оказы)
вается наилучшим методом.
Единственная проблема возникает, если применять это правило к
однобуквенным идентификаторам, например:
X,
Y,
Z.
В результате они оказываются похожими на имена констант, оп)
ределенных с помощью директивы препроцессора #define (такие имена
традиционно состоят только из букв верхнего регистра и подчерки)
ваний). Поэтому к однобуквенным именам правило верхнего регист)
ра в начале слова не применяют.
3.1.5. Использование символов подчеркивания в начале
и в конце имен
Подчеркивания в начале или в конце имен принято использовать
только в специальных случаях. Как правило, такие имена порожда)
ются обрабатывающими программами (компилятором, библиотека)
рем, etc.) или входят в состав определения языка. Например:
_fscanf
__FILE__
Поэтому излагаемый в этой книге стандарт запрещает употребле)
ние таких имен в качестве имен объектов программиста.
3.1.6. Использование цифр в составе имен
Цифры в составе имен могут привести к путанице, поскольку не)
которые цифры похожи на буквы:
Channel10,
ChannelIO.
Поэтому излагаемый в этой книге стандарт предписывает следующее.
Рекомендуется избегать употребления цифр в составе имен.
Если цифры все же используются в составе имени, то они долж)
ны быть в конце, после всех букв.
3.2. Именование функций
3.2.1. Формулирование и состав имени
Функции выполняют действия (приказы). Поэтому для именова)
ния функций необходимо использовать глаголы (в повелительном
наклонении):
Check( ),
Reset( ),
Find( ).
В простых случаях может оказаться очевидным, что именно тре)
буется проверить, сбросить или найти. Но в программах сколько)
37
нибудь заметного размера одного только глагола в названии функ)
ции оказывается недостаточно. Поскольку действия выполняются с
предметами, разумно добавить в название функции существитель)
ное, которое называет «объект воздействия»:
CheckWidget( ),
ResetParagraph( ),
FindPointer( ).
Тем не менее, даже употребление существительного не всегда по)
зволяет добиться необходимой ясности в сложных случаях. Тогда
следует добавить в название функции прилагательное:
CheckPrimaryWidget( ),
ResetFirstParagraph( ).
Таким образом, удачное имя имеет определяющее влияние на яс)
ное понимание назначения функции. Хотя создание таких имен и
требует довольно значительных усилий, они оказываются необходи)
мым элементом по)настоящему хороших программ.
3.2.2. Включение в имя признака принадлежности группе функций
Функции довольно часто «проявляют склонность» образовывать
группы функций. В подобных случаях удобно включить в название
функции признак принадлежности такой группе. Этот прием позво)
ляет добиться немедленной ясности при чтении: название функции
прямо указывает на функциональную группу.
Например, имена функций подсистемы управления экраном мо)
нитора (screen management) разумно снабдить префиксом «SM»:
SM_ReadField( ),
SM_CreateWindow ( ).
Признак принадлежности группе функций не обязательно должен
быть префиксом, его можно включить в любое место имени. Но лучше
всего использовать именно префикс – он буквально бросается в глаза и
при необходимости позволяет автоматически выделить из множества
функций все функции данной группы путем сортировки имен.
Излагаемый в этой книге стандарт требует, чтобы групповой пре)
фикс состоял только из букв верхнего регистра и отделялся от ос)
тальной части имени символом подчеркивания (как в приведенных
выше примерах).
Эти правила именования можно распространить на все элементы
данной подсистемы: имена типов, констант, etc. Например:
struct SM_TCursor
{
. . .
};
38
// Screen Management Type CURSOR:
//
mouse cursor on the screen
3.3. Именование переменных
Если функции выполняют действия, то переменные представля)
ют «объекты воздействия» и хранят «информацию». Это определяет
специфику именования переменных.
3.3.1. Общие правила
Как правило, назначение переменной можно описать с помощью
абстрактного существительного (абстрактное существительное обо)
значает нечто неосязаемое):
Status,
Speed,
Count.
Хотя не совсем понятно, например, чей статус представляет зна)
чение переменной «Status», имена такого типа вполне приемлемы.
Это особенно верно в отношении имен с ограниченной областью ви)
димости (и логической, и визуальной).
Конкретизировать объект, информацию о котором хранит пере)
менная, позволяет употребление в имени еще одного существитель)
ного:
DoorStatus,
WindSpeed,
NodeCount.
В очень сложных случаях внести окончательную ясность позво)
ляет добавление в имя прилагательного:
FrontDoorStatus,
EastWindSpeed,
FreeNodeCount.
Здесь, как и при именовании функций, необходим компромисс
между длинными именами, призванными улучшить понятность, и
короткими именами, улучшающими читабельность. Приемлемое ре)
шение обычно удается найти с помощью аббревиатур.
3.3.2. Именование структур
Если рассмотренные правила использовать при именовании эле)
ментов структур, то может возникнуть ненужная избыточность.
Рассмотрим пример:
struct TPerson
{
char *
int
// Type PERSON:
// сведения о служащем
PersonName; // Фамилия
PersonBirthYear;
// Год рождения
};
TPerson
Manager; // Управляющий
Age = CurrYear Manager.PersonBirthYear;
39
Здесь проявляется различие между структурами и простыми пере)
менными – структуру можно рассматривать как сложный объект.
Отдельные поля структуры хранят обособленные фрагменты инфор)
мации.
Таким образом, рассмотренные в подподразд. 3.3.1 правила име)
нования нужно применять к полному составному имени элемента
структуры, стремясь получить наиболее удобное для чтения сочета)
ние имен. Например:
struct TPerson
{
char *
int
};
TPerson
Name;
BirthYear;
Manager;
// Type PERSON:
//
сведения о служащем
// Фамилия
// Год рождения
// Управляющий
Age = CurrYear Manager.BirthYear;
3.3.3. Именование булевских переменных
Современная версия языка С++ предусматривает встроенный бу)
левский тип данных bool с возможными значениями переменных это)
го типа false и true.
Без таких данных не обходится ни одна программа. Эти данные
играют особую, весьма существенную роль, которую часто недооце)
нивают. Таким образом, очень важно выбрать для булевских пере)
менных имена, понятные и удобные при чтении.
Рассмотрим следующие примеры.
1. if ( Door )
Здесь название переменной плохо помогает понять, что же про)
изошло с дверью. Ее могли сломать, открыть, закрыть или запереть.
Одним словом, требуется дополнительная информация.
2. if ( DoorOpen )
Новый вариант заметно лучше. Понятно, что значение перемен)
ной показывает, открыта дверь или закрыта. Тем не менее, можно
предложить еще одно улучшение.
3. if ( DoorIsOpen )
Маленькое слово «Is» помогает окончательно прояснить намере)
ния автора программы. Предыдущий вариант имени оставлял неко)
торую неопределенность: например, переменная DoorOpen могла со)
держать количество открытых дверей. Теперь же недоразумения при
чтении исключены.
40
3.3.4. Включение в имя сведений о типе (венгерская запись)
Определенные преимущества дает включение в имя переменной
сведений о ее типе. Наиболее распространенный подход такого вклю)
чения называется венгерской записью. Это название принято в честь
автора подхода – программиста из фирмы Microsoft Чарльза Симо)
наи (Charles Simonyi), венгра по национальности.
Венгерская запись заключается в том, что имя снабжается пре)
фиксом, который состоит из букв нижнего регистра и показывает тип
переменной. Этот префикс называют индикатором типа. Например,
имена указателей снабжают индикатором типа ‘p‘ (pointer):
pPara,
pFirstFreeBlock.
При чтении такие имена прочитываются вполне естественно:
«Pointer to PARAgraph»,
«Pointer to the FIRST FREE BLOCK».
Подобно групповому префиксу функций (подподразд. 3.2.2), ин)
дикатор типа должен быть легко отличимым от остальной части име)
ни. Хорошо подходит использование в префиксе букв нижнего реги)
стра, за которыми следует заглавная буква в начале собственно име)
ни.
Можно предложить следующие правила.
Префикс
i
l
u
f
d
c
Значение
integer
long
unsigned
float
double
character
etc.
Этим способом можно показать тип любой переменной. В более
сложных случаях из элементарных префиксов, которые соответству)
ют отдельным спецификаторам типа, конструируют комбинирован1
ные префиксы. Например:
unsigned int *
puiBitString;
Необходимыми условиями должны быть последовательность в
использовании и наличие ясного документирования правил префик)
сации.
Но использование венгерской записи содержит скрытую опас)
ность: номенклатура префиксов может оказаться слишком широкой.
В этом случае обязательное наличие префиксов в любых именах не
столько прояснит, сколько запутает программу.
41
Подводя итог, можно констатировать, что если область видимос)
ти переменной относительно узкая, то венгерская запись не дает за)
метного эффекта.
Излагаемый в этой книге стандарт предписывает ограничиться
только двумя следующими венгерскими префиксами.
p – Pointer (указатель).
h – Handle (манипулятор).
Использование префикса ‘p‘ отражает «особое место», занимае)
мое указателями в любых программах на языке С/С++. Использова)
ние префикса ‘h‘ существенно повышает наглядность текстов окон)
ных приложений, которые спроектированы с использованием низ)
коуровневых средств API.
42
4. ОСНОВЫ РАЗМЕЩЕНИЯ КОДА
Используемый стиль программирования наиболее заметно прояв)
ляется в способе размещения кода. Именно этот аспект стиля чаще
всего обсуждают, упорно защищая свои собственные правила разме)
щения кода и отвергая любые другие.
Это различие мнений обусловлено особенностями процесса распо)
знавания образцов. При распознавании во время чтения мозг интенсив)
но использует комплект привычных образцов, которые хранятся в ус)
тойчивой долговременной памяти человека в мельчайших подробнос)
тях. Именно поэтому стиль, который хотя бы незначительно отличает)
ся от привычного, воспринимается читателем как «нехороший».
4.1. Использование пробелов
4.1.1. Общие замечания
Правила языка С/С++ требуют использования пробелов только в
тех случаях, когда в отсутствие разделяющего пробела два соседних
элемента текста сливаются в один. Поэтому корректный код можно
написать, используя очень небольшое количество пробелов. Например:
if(pViewNode>Value>=(Limit.Value+Delta)) . . .
Главное назначение пробелов состоит в том, чтобы улучшить чи)
табельность текста. Мы могли бы поместить пробелы между всеми
элементами предыдущего примера:
if ( pViewNode > Value >= ( Limit . Value + Delta ) ) . . .
Этот прием позволяет отделить друг от друга все элементы текста
и сделать их ясно различимыми. Но теперь некоторые из них оказа)
лись слишком разделенными.
Таким образом, необходимо найти баланс: в каких случаях встав)
лять пробелы для улучшения читабельности, и в каких – не делать
этого, подчеркивая тесную взаимосвязь. Здесь можно воспользоваться
следующими простыми правилами.
Не должно быть пробелов внутри ссылки на объект.
Не должно быть пробелов между знаком и операндом одномест)
ной операции.
Если знак двуместной операции выделяется пробелами, то их
количество с обеих сторон должно совпадать.
Применяя эти правила к рассмотренному примеру, получим сле)
дующий вариант размещения текста:
if ( pViewNode>Value >= (Limit.Value + Delta) ) . . .
43
4.1.2. Пробелы и ссылки на объекты
Хотя ссылка на элемент структуры или элемент массива состоит
из нескольких элементов текста, все они тесно взаимосвязаны. По)
этому разумно не разделять пробелами идентификаторы и следую)
щие операции:
«.»,
«>«,
«[ ]».
Сравнивая ссылку на элемент структуры и ссылку на элемент мас)
сива, можно сказать, что отдельные части составного имени связаны
между собой более тесно, чем имя массива и индексное выражение
внутри квадратных скобок. Поэтому пробелы вокруг индексного вы)
ражения можно считать не только допустимыми, но и желательны)
ми. Например:
Word.Length,
pWindow>Size,
EmpName[ IxName ].
4.1.3. Пробелы и одноместные операторы
Одноместные операторы тесно связаны лишь с одним элементом
текста – со своим операндом. Поэтому разумно не разделять пробе)
лом знак операции и операнд следующих операций:
!
– логическое отрицание;
~
– поразрядная инверсия;
++
– увеличение;
—
– уменьшение;
N
– унарный минус;
*
– разыменование;
&
– взятие адреса;
(cast) – преобразование типа;
sizeof – размер.
Если одноместная операция – не единственная в выражении, то
знак операции вместе с операндом необходимо выделить пробелами с
обеих сторон. Это способствует ясному пониманию при чтении, а так)
же позволяет исключить некоторые досадные недоразумения, вроде
следующего:
*pCurrent = *pVolts/*pResistance;
/* ??? */
(здесь знаки операций деления и разыменования соединились в ле)
вый ограничитель комментария).
Приведенный пример наводит на мысль, что одноместные опера)
ции в выражениях лучше всегда заключать в круглые скобки:
(*pCurrent) = (*pVolts) / (*pResistance);
44
4.1.4. Пробелы и двуместные операторы
Двуместные операторы связаны со своими операндами столь же
тесно, как и одноместные. Но если с обеих сторон от знака операции
поставить пробелы, то его видно лучше. Это особенно заметно, если
вокруг находятся длинные идентификаторы или другие операции:
WordNo = FirstWordNo – (*pCurrWordOffset);
4.1.5. Пробелы и знаки препинания
Знаки препинания (точка с запятой и запятая) более тесно связа)
ны с предшествующим элементом текста, чем с последующим. Прав)
да, связь с предшествующим элементом довольно слабая, но связь с
последующим элементом практически отсутствует совсем.
Таким образом, пробел не ставится перед знаком препинания и
ставится после него:
for ( IxView=0; IxView < Len; ++IxView ) . . .
Это тем более удобно, что такое правописание принято в «есте)
ственных» языках: английском, русском, немецком и многих дру)
гих.
4.1.6. Пробелы и круглые скобки
Можно предложить несколько равноценных вариантов употреб)
ления пробелов в связи с круглыми скобками.
Пробел ставится снаружи скобок и не ставится внутри:
(CurrPage – LastPage)
Пробелы ставятся с обеих сторон каждой скобки:
( CurrPage – LastPage )
Пробелы ставятся внутри самых внешних скобок и не ставятся
внутри вложенных скобок:
( FirstPage – (CurrPage – LastPage) )
etc.
Настаивать на одном из этих вариантов необязательно. Важно
учесть, что соответствующие друг другу скобки легче ассоциировать,
если пробелы вокруг них поставлены единообразно. Поэтому необхо1
димо требовать только единообразия употребления пробелов во)
круг конкретной пары скобок.
Рассмотрим круглые скобки вокруг списка аргументов при вызове
функции. Открывающая скобка помогает понять, что идентифика)
тор перед ней – имя функции. Эту тесную связь можно подчеркнуть
45
отсутствием пробела между именем функции и открывающей скоб)
кой. Если функция реализована как макрос, то этот пробел вообще
недопустим.
Таким образом, открывающая скобка списка аргументов всегда
пишется слитно с именем функции. Внутри скобок отдельные аргу)
менты выделяются пробелами:
CalcWindowSize( Window, MAXSIZE );
4.1.7. Пробелы и ключевые слова
Ключевые слова – не то же самое, что имена функций. Это можно
подчеркнуть, употребляя вокруг ключевых слов пробелы. Например:
if ( DoorIsOpen ) . . .
Отдельного обсуждения требует sizeof. Правила записи операции
sizeof различны в зависимости от вида операнда. Если операнд – тип,
то он должен быть заключен в скобки. Если же операнд – объект, то
заключать его в скобки необязательно.
Это приводит к путанице, так что проще всего всегда заключать
операнд sizeof в круглые скобки. Но поскольку мы имеем дело с
одноместной операцией, рассмотренные ранее основания (подпод)
разд. 4.1.3) требуют писать знак операции слитно с операндом.
Поэтому ключевое слово sizeof необходимо писать слитно с пос)
ледующей круглой скобкой. Употреблять пробелы внутри скобок
нужно в соответствии с правилами для круглых скобок (подпод)
разд. 4.1.6):
sizeof( int )
или
sizeof(int)
4.2. Размещение по строчкам
4.2.1. Пустые строчки
Возвращаясь к пробелам, можно сказать, что пробелы употребля)
ют внутри строк для того, чтобы улучшить читабельность посред)
ством выделения отдельных слов и показать степень взаимосвязан)
ности слов. Пустые строки выполняют те же самые функции, но на
уровне инструкций: они разделяют ломти информации более круп)
ного размера, чем отдельные слова.
Эти перерывы для «передышки» при чтении кода – очень подхо)
дящие места для комментариев, которые либо подводят итог тому,
что уже произошло, либо сообщают о том, что должно произойти.
Фактически, пустая строчка и сама по себе оказывается коммен)
тарием. Она как бы говорит: «Стоп, передышка! Посмотрим назад,
46
на только что прочитанный фрагмент. Все понятно? Может, нужно
прочитать еще разок? Так, а теперь заглянем вперед. Как там, видно
конец очередного «ломтика»? Тогда приготовимся его восприни)
мать». Например:
MovePaperUp( CharHeight );
MoveCarriage( C_LEFT );
GetLine( LineBuf );
PrintLine( LineBuf );
// конец ломтя #1
// начало ломтя #2
Инструкции «сами по себе» проявляют тенденцию группировать)
ся в ломти, причем небольшие. Если же ломоть получается большим
(больше, чем примерно семь строчек), то нужно подумать, в каком
месте разумно вставить пустую строчку и/или комментарий.
С помощью пропусков можно не только разделять ломти, но и
показывать отношения между ними. Объекты, которые размещены
поблизости друг от друга, невольно воспринимаются как взаимосвя)
занные. Если же между ними есть некоторое расстояние, то они ка)
жутся связанными слабее. Если объект расположен между двумя дру)
гими объектами, то он выглядит теснее связанным с тем из своих
соседей, к которому находится ближе.
Пробелы и символы табуляции служат для разделения ломтей по
горизонтали, в отдельных строчках текста. Пустые строчки помога)
ют разделить группы инструкций по вертикали. Табуляция также
может способствовать распознаванию ломтей в вертикальном направ)
лении, если взаимосвязанные объекты из разных строчек начинают)
ся в одном и том же столбце.
Хотя пропуски в тексте ничего не содержат, нельзя сказать, что
они ничего не стоят. Пробелы и табуляция ведут к затратам гори)
зонтального пространства, размер которого весьма ограничен – по)
рядка 80 позиций в строчке. Пустые строчки ведут к затратам вер)
тикального пространства, также жестко ограниченного: экран мо)
нитора вмещает всего 24 строчки, а напечатанная страница – около
60 строчек.
4.2.2. Одно действие – одна строчка
Размещая в каждой строчке только одно «действие», мы тем са)
мым превращаем каждую строчку в узнаваемый унифицированный
ломоть кода.
Рассмотрим пример:
PrintArray( Vector ); NewLine( );
47
Эта строчка содержит два действия: печать значений элементов
массива и переход к новой строчке. Размещая эти действия на от)
дельных строчках, получим:
PrintArray( Vector );
NewLine( );
Исключения из этого правила допустимы, если два «действия»
связаны между собой очень тесно, так, что при их размещении в од)
ной строчке наглядность не только не ухудшается, но даже улучша)
ется. Например:
if ( CharCount >= LineLen ) break;
4.3. Вертикальное выравнивание и отступы по горизонтали
Вертикальное выравнивание показывает взаимосвязь объектов,
расположенных в разных строчках. Строчки вовсе не обязательно
должны быть по соседству друг с другом: глаза видят связь даже на
расстоянии нескольких строчек.
Типичным использованием вертикального выравнивания явля)
ются отступы кода. То же самое правило принято использовать при
записи определений данных.
При наличии нескольких присваиваний подряд можно требовать
выравнивать по вертикали знаки присваивания:
BoxHeight = CalcBoxHeight( Box );
BoxWidth = CalcBoxWidth( Box );
BoxLenght = CalcBoxLenght( Box );
Основное назначение горизонтальных отступов текста состоит в
том, чтобы отличить часть строчек (с совпадающим отступом) от всех
остальных строчек (с другой величиной отступа).
Практика показывает, что оптимальная величина шага отступа
составляет 4 позиции. Отступ такого размера отчетливо различается
при чтении, а горизонтальное пространство расходуется достаточно
экономно.
Такой отступ можно включать в текст и с помощью пробелов, и с
помощью символа горизонтальной табуляции (практически все со)
временные редакторы текстов позволяют установить необходимый
шаг табуляции). Однако наличие в тексте символов табуляции мо)
жет стать причиной возникновения проблем, поскольку разные ре)
дакторы текста трактуют этот символ по)разному. Альтернатива со)
стоит в том, чтобы использовать только символ «пробел».
Современные редакторы текста, как правило, позволяют устанав)
ливать величину шага табуляции и содержат встроенную утилиту
48
для замены всех символов табуляции соответствующим количеством
пробелов. Это снимает проблемы печати на любом принтере.
4.4. Размещение фигурных скобок
Размещение в тексте фигурных скобок, вероятно, наиболее замет)
ный элемент стиля в программе на языке С/С++. Имеется несколько
распространенных стилей размещения фигурных скобок.
Стиль Олмана (Allman style) отличается от остальных стилей тем,
что открывающая фигурная скобка размещается на следующей строч)
ке после инструкции, в который вложена составная инструкция.
Начало обеих строк выравнивается по вертикали, а тело составной
инструкции размещается с отступом вправо:
if ( PageWidth > PaperWidth )
{
SetPageWidth( PaperWidth );
TellUser( MSG_PAGE_CHANGED );
}
Благодаря вертикальному выравниванию скобок распознать со)
ответствие скобок легко. Столь же легко увидеть скобки как тако)
вые, поскольку они расположены на отдельных строчках.
Поэтому излагаемый в этой книге стандарт предписывает разме)
щение фигурных скобок в стиле Олмана.
Фигурные скобки появляются в тексте не только в связи с состав)
ными операторами, но и в других конструкциях С/С++, например в
описаниях структур, объединений и перечислений. Во всех таких
конструкциях необходимо последовательно придерживаться одно)
го, выбранного стиля (в данном стандарте – стиля Олмана).
4.5. Объявления данных
Во многих случаях получение ясного понимания о данных в про)
грамме представляет определенные трудности. Поэтому продуман)
ное размещение объявлений данных играет особенно важную роль.
4.5.1. Порядок задания спецификаторов
Как известно, в состав объявления могут входить «спецификато)
ры» следующих категорий.
Спецификаторы класса памяти (storage class specifier): auto,
register, extern, static.
Квалификаторы типа (type qualifier): const, volatile.
49
Спецификаторы типа (type specifier): signed, unsigned, char,
short, int, long, float, double, struct, union, enum,
typedef.
Порядок задания спецификаторов может быть произвольным.
Излагаемый в этой книге стандарт предписывает следующий поря)
док задания, который считается наиболее ясным.
На первом месте в списке указывается спецификатор класса па)
мяти.
Далее следует квалификатор типа, если он требуется.
В конце списка помещается спецификатор типа (или перечень из
нескольких спецификаторов).
Например:
static long
AuthorNo;
Отдельного обсуждения заслуживает объявление указателей.
При объявлении указателей символ ‘*‘ довольно часто размеща)
ют вплотную к имени переменной, мотивируя это тем, что так будет
выглядеть использование указателя в операторах программы:
char
*ScreenName;
Но такое использование пробелов по горизонтали приводит к тому,
что при чтении символ ‘*‘ тесно ассоциируется со следующим за ним
идентификатором. Это «провоцирует» читателя на следующую ин)
терпретацию:
«*ScreenName – это символ».
Излагаемый в этой книге стандарт требует рассматривать символ
‘*‘ как один из спецификаторов и, соответственно, помещать его в
зоне спецификаторов:
char *
ScreenName;
Теперь символ ‘*‘ ассоциирован с другими спецификаторами, так
что интерпретация читателя соответствует истине:
«ScreenName – это указатель на символ».
4.5.2. Одна строчка – одно объявление
По правилам С/С++ можно объявлять переменные одного типа в
одной и той же строке кода. Например:
int
*pFirstPrime, IxMax;
Такое размещение имеет целый ряд недостатков. Во)первых,
объявление переменной «IxMax» размещено вплотную к объявлению
«pFirstPrime», хотя тесной логической связи между ними, возмож)
но, и нет. Во)вторых, разглядеть объявление «IxMax» непросто.
50
В)третьих, такое размещение не позволяет прокомментировать каж)
дую переменную в отдельности.
Наконец, при таком «вынесении влево» общих «спецификаторов»
легко наделать ошибок. В приведенном примере спецификатор int
относится к обеим переменным, а символ ‘*‘ (который при чтении
воспринимается как спецификатор) относится только к одному
объявлению переменной «pFirstPrime».
Самый ясный способ, который предписывается излагаемым в этой
книге стандартом, состоит в раздельном объявлении каждой пере)
менной. Символ ‘*‘, который идентифицирует указатели, следует
рассматривать как один из спецификаторов (обычно он располагает)
ся в конце списка). Список спецификаторов в целом образует отдель)
ный «ломоть» и потому отделяется от идентификатора переменной
заметным пустым промежутком по горизонтали. Например:
int *
int
pFirstPrime;
IxMax;
4.5.3. Выравнивание объявлений данных
Каждое объявление содержит, как минимум, две отдельные час)
ти: тип объявляемого элемента данных и его идентификатор. Чита)
телю текста часто требуется просматривать объявления данных в
поисках нужного имени. Вертикальное выравнивание помогает про)
сматриванию; оно также придает объявлениям более аккуратный вид.
Например:
int
IxSubChar;
int
SubChar;
// IndeX of processing
//
SUBstring’s CHARacter
// processing SUBstring’s character
Строгое следование этому правилу означает, что имена перемен)
ных всегда начинаются в одной и той же позиции. Каждый из трех
ломтей, составляющих объявление (список спецификаторов, имя
переменной, сопровождающий комментарий), занимает фиксирован)
ное пространство по горизонтали, своеобразную зону.
Излагаемый в этой книге стандарт требует следующей разбивки
на зоны строчки, которая содержит объявление данных.
Зона спецификаторов – от начала строчки до позиции 23. Обра)
тите внимание: строчка с объявлением на внешнем уровне начинает)
ся в позиции 1, а строчка с объявлением на внутреннем уровне вло)
жена в тело функции и начинается с отступом на один шаг – в пози)
ции 5.
Позиция 24 – разделяющий пробел.
51
Зона имени переменной – позиции 25–35 (это пространство вклю)
чает также одну позицию для точки с запятой в конце объявления).
Позиция 36 – разделяющий пробел.
Зона сопровождающего комментария: позиции 37–72 (это про)
странство включает также ограничители комментария).
Таким образом, зоны начинаются в начале строчке, в позиции 25
и в позиции 37 (в эти позиции приводит табуляция с шагом 4). Это
оставляет ломтям типового размера достаточное пространство. На)
пример, идентификатор может занимать 10 позиций (25–34).
Если один из трех ломтей не умещается в отведенную зону, ис)
пользуют следующие правила переноса.
Слишком длинный список спецификаторов продолжается до кон)
ца строчки, а имя переменной начинается в своей зоне на следующей
строчке.
Слишком длинное имя продолжается до конца строчки, а сопро)
вождающий комментарий начинается в своей зоне на следующей
строчке.
Слишком длинный сопровождающий комментарий переносится
по обычным правилам (подподразд. 2.6.2).
Например:
static unsigned char *
pViewChar;
unsigned int
//
//
Указатель
на обозреваемый символ
SynTerm[ 256 ];
// SYNtax TERM:
//
принадлежность литер
//
классам литер
4.6. Определения и прототипы функций
4.6.1. Определения функций
В соответствии с принципом последовательности заголовок функ)
ции должен выглядеть подобно описанию структуры. Круглые скоб)
ки вокруг списка параметров занимают отдельные строчки, как и
фигурные скобки в объявлении структуры.
Для того чтобы избежать выравнивания по вертикали круглых
скобок вокруг списка параметров и фигурных скобок вокруг тела
функции, следует сдвинуть круглые скобки вправо на один шаг от)
ступа. Например:
// extract SUBSTRing of the given source string
// to the target string
52
//
// source must be a correct Cstring,
// target must be long enough to receive the extracted substring
//
void Substr
(
const char *
Source,
// the given string
int
IxSubStart, // IndeX of SUBstring’s START
//
in “Source”
int
SubLen,
// SUBstring’s LENgth
char *
Target
// receiver
//
of the extracted substring
)
{
. . .
return;
} // end function
Несмотря на некоторую необычность, такое размещение имеет ряд
достоинств. Во)первых, объявления параметров расположены так же,
как и другие объявления данных. Во)вторых, все три ломтя инфор)
мации о параметре (список спецификаторов, имя, комментарий) рас)
положены рядом. В)третьих, отсутствует избыточность: все сведе)
ния о параметре даются один раз. Наконец, такое размещение ото)
бражает фактическую сторону организации передачи аргументов при
вызове функции: будет создан временный интерфейс функции с окру)
жающей средой (стековый фрейм), структура которого идентична
«обыкновенным» структурам (structs).
Если же список параметров пустой, то весь заголовок функции
размещается в одной строчке. Например:
static void ShowDoubles( void )
{
. . .
return;
} // end function
4.6.2. Прототипы функций
Прототип функции в определенном смысле представляет собой
ссылку на ее определение. Соответственно, полные сведения о функ)
ции не нужны – при необходимости их можно получить из определе)
ния функции.
53
Тем не менее, рекомендуется оставлять в прототипе имена пара)
метров: хотя они и не требуются компилятору, они могут помочь чи)
тателю, напоминая о том, что же делает функция. Например:
int GetLine( FILE * Input, char * InLine );
Если список параметров длинный, то наглядность улучшается при
размещении прототипа в нескольких строчках. К тому же такое раз)
мещение оставляет возможность при необходимости использовать
сопровождающий комментарий. Например:
void SymValueToBinValue
(
const TSymByte *
int
unsigned char *
);
54
Source,
NumBytes,
Target
// comment, where useful
5. ИНСТРУКЦИИ: РАЗМЕЩЕНИЕ И УПОТРЕБЛЕНИЕ
5.1. ИнструкцияNвыражение
Одна строчка исходного текста должна содержать не более одной
инструкции)выражения.
Если инструкция)выражение не умещается в одной строчке, то ее
следует разделить на несколько более коротких инструкций, каждая
из которых умещается в одной строчке и формирует так называемый
«промежуточный результат». Например:
// NEVER
//
LongName1 = LongName2 * ( LongName3 + LongName4 – LongName5 ) +
4 * LongName6;
// RIGHT
//
Temp1
= LongName3 + LongName4 – LongName5;
Temp2
= 4 * LongName6;
LongName1 = LongName2*Temp1 + Temp2;
При этом желательно обособить в форме «промежуточных резуль)
татов» такие подвыражения, которым можно дать содержательные
(а не формальные) имена. Например:
// AVOID
//
Temp1
= LongName7 + LongName8 – LongName9;
Temp2
= LongName10 * LongName11;
LongName1 = Temp1 / Temp2;
// PREFER
//
Dividend
= LongName7 + LongName8 – LongName9;
Divisor
= LongName10 * LongName11;
LongName1 = Dividend / Divisor;
Исключение из этого правила составляет вызов функции, кото)
рый не умещается в одной строчке из)за большой длины списка аргу)
ментов. Используйте следующее:
// NEVER
//
Result = FunctionName( LongArgumentName1, LongArgumentName2,
LongArgumentName3, LongArgumentName4 );
// RIGHT
55
//
Result = FunctionName(
LongArgumentName1,
LongArgumentName2,
LongArgumentName3,
LongArgumentName4
);
// comment, where useful
Каждая инструкция)выражение не должна иметь более одного
побочного эффекта. Например:
// NEVER
//
Result = Body[ Top ];
// RIGHT
//
Result = Body[ Top ];
Top;
Исключение из этого правила составляет вызов функции, кото)
рой передаются по ссылке несколько аргументов (в результате такая
функция может не только вернуть значение, но также изменить зна)
чения некоторых аргументов).
5.2. Составная инструкция (инструкцияNблок)
Составная инструкция должна иметь следующее форматирование:
{
инструкции;
}
Фигурные скобки должны быть выровнены по вертикали, а инст)
рукции, которые заключены между ними, должны быть сдвинуты
вправо на один шаг отступа (4 позиции).
5.3. Условная инструкция if
Инструкция if должна иметь следующее форматирование
if ( условие )
{
инструкции;
}
else
{
инструкции;
}
56
Выражение, которое использовано в качестве условия, не должно
иметь побочных эффектов. Желательно, чтобы это выражение выра)
батывало результат булевского типа (bool).
В конструкциях if и else всегда должна быть использована состав)
ная (а не одиночная) инструкция.
Следует избегать употребления в составных инструкциях других,
вложенных инструкций if и циклов. Например:
if ( условие )
{
инструкции;
if ( условие )
{
инструкции;
}
else
{
инструкции;
}
// AVOID
// such nesting
// of ifstatements
//
//
//
//
//
инструкции;
}
else
{
инструкции;
while ( условие )
{
инструкции;
}
// AVOID
// such nesting
// of loopstatements
//
инструкции;
}
Исключение из этого правила составляет инструкция if ... else if ...
else (подразд. 5.5). В ней, фактически, используются инструкции if,
которые вложены в конструкцию else.
Если составная инструкция в конструкции if заканчивается инст)
рукцией передачи управления (break, continue, return), то конструк)
ция else, очевидно, не нужна. Например:
// NEVER
//
if ( условие )
{
инструкции1;
return;
57
}
else
{
инструкции2;
}
// RIGHT
//
if ( условие )
{
инструкции1;
return;
}
инструкции2;
Если в конструкции if употреблена единственная инструкция пе)
редачи управления (break, continue, return), то наглядность текста
улучшается при размещении в одной строчке. Например:
if
if
if
if
// PREFER
//
( условие )
( условие )
( условие )
( условие )
break;
continue;
return;
return ( выражение );
5.4. Инструкция выбора варианта switch
Инструкция switch должна иметь следующее форматирование:
switch ( селекторварианта )
{
case значениеселектора:
инструкции;
break;
case значениеселектора:
case значениеселектора:
инструкции;
break;
default:
инструкции;
break;
} // end switch
58
Вариант default должен присутствовать обязательно и, кроме того,
должен быть последним в инструкции switch.
Каждый вариант (включая последний) должен заканчиваться
инструкцией break. Это требуется ради соблюдения правила: «инст)
рукция break в конце каждого варианта».
В результате, допускается только полное совпадение вариантов
для различных значений «селектора варианта» (как во втором вари)
анте приведенного выше примера). Частичное совмещение вариан)
тов (за счет так называемого «проваливания», fall through) не допус)
кается:
switch ( селекторварианта )
{
case значениеселектора:
инструкции;
/* falls through */
// NEVER
case значениеселектора:
case значениеселектора:
инструкции;
break;
. . .
Не допускается использовать вложение в инструкцию switch дру)
гих выбирающих инструкций (if, switch) и циклов:
switch ( селекторварианта )
{
case значениеселектора:
if ( условие )
{
инструкции;
}
else
{
инструкции;
}
break;
// NEVER
// such nesting
// of ifstatements
//
//
//
//
//
case значениеселектора:
switch ( селекторварианта ) // NEVER
{
// such nesting
case значениеселектора:
// of switchstatements
59
инструкции;
break;
default:
инструкции;
break;
}
break;
default:
while ( условие )
{
инструкции;
}
break;
}
//
//
//
//
//
//
// NEVER
// such nesting
// of loopstatements
//
// end switch
Причина этого запрета очень простая: при его нарушении програм)
ма быстро превращается в так называемый «клубок спагетти». Из)
бежать этого позволяет использование вспомогательных функций
(helper functions). Например:
switch ( селекторварианта )
{
case значениеселектора:
HelperFunction1( . . . );
break;
case значениеселектора:
case значениеселектора:
HelperFunction2( . . . );
break;
default:
HelperFunction3( . . . );
break;
} // end switch
Вспомогательные функции не только распутывают «клубок спа)
гетти». При относительно недолгом размышлении им всегда можно
дать содержательные имена. Если же такие имена не находятся, то
это однозначно показывает: системный анализ задачи проведен не)
достаточно тщательно (и следует вернуться к его доработке).
60
5.5. Условная инструкция if ... else if ... else
Инструкция if ... else if ... else должна иметь следующее форматиро)
вание:
if ( условие )
{
инструкции;
}
else if ( условие )
{
инструкции;
}
...
else
{
инструкции;
}
Каждое выражение, которое использовано в качестве условия, не
должно иметь побочных эффектов. Желательно, чтобы это выраже)
ние вырабатывало результат булевского типа (bool).
В конструкциях if и else всегда должна быть использована состав)
ная (а не одиночная) инструкция.
Не допускается использовать вложение в инструкцию if … else if …
else других выбирающих инструкций (if, switch) и циклов. Напри)
мер:
if ( условие )
{
инструкции;
if ( условие )
{
инструкции;
}
else
{
инструкции;
}
// NEVER
// such nesting
// of ifstatements
//
//
//
//
//
инструкции;
}
else if ( условие )
{
61
инструкции;
while ( условие )
{
инструкции;
}
// NEVER
// such nesting
// of loopstatements
//
инструкции;
}
. . .
Инструкцию if ... else if ... else используют для выбора одной из
нескольких (более, чем двух) альтернатив. Завершающая конструк)
ция else специфицирует «вариант по умолчанию».
Фактически, эта инструкция заменяет инструкцию switch в тех
случаях, когда критерий выбора одной из альтернатив сложнее, чем
просто значение «селектора варианта» целого типа. Поэтому разме)
щение инструкции if ... else if ... else следует тому же самому образцу,
что и размещение инструкции switch.
5.6. Инструкция цикла с предусловием while
Инструкция while должна иметь следующее форматирование:
while ( условие )
{
инструкции;
}
Выражение, которое использовано в качестве условия, не должно
иметь побочных эффектов. Желательно, чтобы это выражение выра)
батывало результат булевского типа (bool).
В качестве тела цикла всегда должна быть использована состав)
ная (а не одиночная) инструкция. Поскольку требуется, чтобы вы)
числение «условия» не давало побочных эффектов, цикл с пустым
телом цикла оказывается бесполезным.
Вложения циклов следует избегать (например, используя вместо
вложенного цикла вызов функции).
5.7. Инструкция цикла с постусловием do ... while
Инструкция do … while должна иметь следующее форматирова)
ние:
do
{
62
инструкции;
}
while ( условие );
Выражение, которое использовано в качестве условия, не должно
иметь побочных эффектов. Желательно, чтобы это выражение выра)
батывало результат булевского типа (bool).
В качестве тела цикла всегда должна быть использована состав)
ная (а не одиночная) инструкция. Поскольку требуется, чтобы вы)
числение «условия» не давало побочных эффектов, цикл с пустым
телом цикла оказывается бесполезным.
Вложения циклов следует избегать (например, используя вместо
вложенного цикла вызов функции).
5.8. Инструкция цикла for
Инструкция for должна иметь следующее форматирование:
for ( инициализация; условие; обновление )
{
инструкции;
}
«Заголовок цикла» должен давать ясное представление о количе)
стве повторений тела цикла. Поэтому допускается только одна «уп)
равляющая переменная». Должны присутствовать все три элемента
«заголовка»: инициализация, условие и обновление (исключение из
этого правила изложено в подразд. 5.9).
Выражение, которое использовано в качестве условия, не должно
иметь побочных эффектов. Желательно, чтобы это выражение выра)
батывало результат булевского типа (bool).
В качестве тела цикла всегда должна быть использована состав)
ная (а не одиночная) инструкция.
Вложения циклов следует избегать (например, используя вместо
вложенного цикла вызов функции).
5.9. Инструкция цикла нестандартной структуры
Для записи циклов нестандартной структуры (так называемых
«бесконечных циклов») следует использовать инструкцию for со сле)
дующим форматированием:
for ( ; ; )
{
63
инструкции;
if ( условие ) break;
инструкции;
}
Выражение, которое использовано в качестве условия, не должно
иметь побочных эффектов. Желательно, чтобы это выражение выра)
батывало результат булевского типа (bool).
5.10. Инструкция возврата из функции return
Инструкция return, как правило, должна быть единственной в
строчке (исключения из этого правила изложены в подразд. 5.3).
Инструкция return должна иметь один из следующих вариантов
форматирования:
return;
return ( выражение );
По правилам синтаксиса языка С/С++ заключать в круглые скоб)
ки «выражение» в инструкции return не требуется. Однако во всех
других синтаксических конструкциях языка, где «выражение» сле)
дует за ключевым словом, круглые скобки необходимы. Вот список
этих конструкций:
if ( выражение )
switch ( выражение )
while ( выражение )
Поэтому, руководствуясь принципом последовательности в упо)
треблении языка, необходимо всегда заключать в круглые скобки
также и «выражение» в инструкции return.
Выражение в инструкции return не должно иметь побочных эф)
фектов.
Употребление инструкции return в конце текста функций с пус)
тым возвращаемым значением (void) заслуживает отдельного обсуж)
дения.
Компилятор помещает машинный код, который осуществляет
возврат из функции, в конце машинного кода функции. Это – так
называемый «эпилог» функции. Компилятор «генерирует» эпилог,
обрабатывая закрывающую скобку в конце текста определения фун)
кции. При последовательной передаче управления эпилог достигает)
ся автоматически.
Поэтому говорят, что «закрывающая фигурная скобка в конце
определения функции работает как инструкция return». Значит,
инструкция return в конце не нужна.
64
На это можно возразить следующее.
Инструкция return прочитывается читателем как «закончить
выполнение функции». Закрывающая фигурная скобка в конце оп)
ределения функции прочитывается читателем как «конец текста
определения функции».
Поэтому, следуя принципу явного формулирования, необходимо
записывать инструкцию return в конце текста определения функ)
ции. Это подкрепляется также тем обстоятельством, что правила
языка не запрещают употреблять инструкцию return в середине тек)
ста определения.
65
6. ФУНКЦИОНАЛЬНАЯ ДЕКОМПОЗИЦИЯ
ПРОГРАММНОГО ПРОЕКТА
6.1. Сложность программного обеспечения и ее источники
Ведущие специалисты в области разработки ПО считают, что его
наиболее существенной особенностью является сложность [4].
Примером сложных созданий человека могут служить архитек)
турные сооружения. Уровни их сложности приблизительно представ)
ляет рис. 6.1.
Но сложность, присущая компонентам программного обеспече)
ния, по)видимому, превышает сложность любых других созданий
человека. Причина этого – в том, что ПО конструируют из неповто)
ряющихся элементов (а если это не так, то несколько аналогичных
частей следует превратить в одну, оформляя ее как подпрограмму).
В этом состоит глубокое отличие ПО от других сложных созданий
832
2
38
84
237
6
523
43
21
132223222
92 885 7
1223222
92 35 7
123222
2 885 7
13222
2 52 7
122
2 8 7
12
12324 567 8936
1
122
122
Рис. 6.1. Относительная сложность архитектурных сооружений
66
человека: компьютеров, архитектурных сооружений и т. п., – где
повторяющиеся элементы конструкции преобладают.
Аналогия с архитектурными сооружениями показывает, что при
переходе на один уровень иерархии вверх необходимо использовать
другие методы строительства. Переход на три (или больше) уровня
иерархии вверх приводит к качественным изменениям: строитель)
ство небоскребов радикально отличается от постройки шалашей,
хотя и то и другое – виды строительства.
Цифровые вычислительные машины уже сами по себе превосхо)
дят своей сложностью большинство других объектов, созданных че)
ловеком, – количество их возможных состояний очень велико. Тем
не менее, количество возможных состояний программных систем еще
больше – как минимум, на порядок.
По мере увеличения размеров программной системы возникают
специфические трудности, о которых крупнейший специалист в об)
ласти программирования Э. Дейкстра писал:
«Разработав программу размером 30 строк, мы говорим себе, что
можем спроектировать и вдвое большую программу, и по индукции
приходим к убеждению, что можем спроектировать программу любо)
го требуемого размера; как раз это)то и неверно! Умножение на тыся)
чу уже выходит далеко за пределы нашего воображения.
Например, годовалый младенец может передвигаться со скорос)
тью один километр в час. Однако скорость 1000 километров в час
достигается только с помощью реактивного двигателя.
Между тем сегодня необходимо создание проектов ПО размером в
миллионы строк исходного текста.
Здесь наиболее существенное ограничение заложено в природе че)
ловеческого интеллекта: точное и связное мышление возможно толь)
ко в терминах небольшого числа элементов в каждый отдельный от)
резок времени» [9].
По мере увеличения размеров программной системы не просто уве)
личивается количество одинаковых, повторяемых элементов. Напро)
тив, это увеличение всегда связано с увеличением количества раз)
личных, неповторяемых элементов. Как правило, эти элементы вза)
имодействуют друг с другом некоторым нелинейным образом, так что
сложность целого возрастает значительно быстрее, чем при линей)
ной зависимости сложности от количества элементов.
Сущность программного компонента состоит из переплетающих)
ся концепций: взаимосвязей структур данных, алгоритмов и вызо)
вов функций. Эта концептуальная сущность абстрактна в том смыс)
ле, что она не зависит от конкретной формы представления, одной из
67
многих возможных. Несмотря на это сущности ПО свойственна вы)
сокая точность и обилие детальных подробностей.
Сложность разработки ПО принято разделять на:
– существенные виды сложности, присущие внутренней природе
ПО;
– дополнительные виды сложности, связанные с сегодняшними
трудностями разработки ПО, но не свойственные ПО как таковому.
Основным источником существенной сложности проектирования
ПО считается именно сложность спецификации, разработки и тести)
рования концептуальной конструкции, а не ее конкретного представ)
ления.
Существенная сложность ПО обусловлена присущей ему логичес)
кой природой, связанной с проверкой условий. Добавление в про)
грамму каждого очередного условия увеличивает количество возмож)
ных вариантов ее выполнения. В зависимости от точки включения
новой проверки условия количество вариантов увеличивается по)раз)
ному и в пределе может возрасти в 2 раза. Таким образом, количество
вариантов работы программы, содержащей проверку 1000 условий,
может составить до 21000. В результате оказывается сложно не толь)
ко понять, но даже просто перечислить все возможные состояния
программы, а это ведет к ненадежности.
Сложные функции сложно не только разрабатывать, но и вызы)
вать. Сложная структура приводит к появлению непредусмотренных
разработчиком возможных состояний ПО и непредусмотренных вза)
имозависимостей его частей.
При этом отсутствует возможность использовать какие)либо уни)
фицирующие принципы, поскольку каждое проверяемое условие свя)
зано с частным аспектом решаемой задачи и потому уникально.
Сложность ПО, которая обусловлена многовариантностью его ра)
боты, называют логической сложностью. Помимо логической слож)
ности существует также математическая или функциональная слож1
ность, которая возникает при необходимости использования для
решения задачи специального математического аппарата.
Сложность ПО обусловлена не только многовариантностью, но
также его невидимостью. Реальному ПО не свойственно быть лока)
лизованным в пространстве. Следовательно, ему не соответствует
никакой геометрический образ в том смысле, в каком поверхности
Земли соответствует карта, компьютеру соответствует электронная
схема и т. д.
Логическая сложность ПО в сочетании с его невидимостью пред)
ставляет собой труднопреодолимое препятствие. Поэтому практика
68
разработки ПО по)прежнему находится в состоянии кризиса. Про)
блемы, которые приходится решать сегодня, выросли в 50 раз по срав)
нению со вчерашними проблемами. А по отношению к росту проблем
сложность ПО растет экспоненциально.
Специалисты)практики констатируют наличие разительного не)
соответствия между состоянием теории и запросами повседневной
практики программирования – так называемого методологического
разрыва.
6.2. Методы преодоления
сложности программного обеспечения
Концептуальной основой преодоления сложности ПО была и ос)
тается иерархическая декомпозиция. Но форма ее использования,
как правило, далеко не очевидна.
В настоящее время не существуют критерии, которые позволяют
определить наилучший способ разбиения программы на модули и их
интеграции. Более того, техника структурирования, которая подхо)
дит в одной ситуации, может оказаться непригодной в другой.
Одним из наиболее перспективных методов считаются объектно)
ориентированные технологии, которые включают:
– объектно)ориентированный анализ;
– объектно)ориентированное проектирование;
– объектно)ориентированное программирование (ООП).
В целом объектно)ориентированные методы представляют собой
заметный прогресс в искусстве создания ПО. Их использование по)
зволяет преодолеть многие виды дополнительной сложности.
Тем не менее, этот прогресс относится именно к дополнительной
сложности ПО и не уменьшает существенную сложность. Часто, при
внимательном рассмотрении, оказывается, что новые решения каса)
ются только формы представления решения, не затрагивая сущнос)
ти вопроса.
Хотя практическое применение ООП продемонстрировало просто)
ту повторного использования объектно)ориентированного кода, так)
же оказалось, что объектно)ориентированные технологии не дают
ни значительного увеличения производительности, ни значительно)
го улучшения качества ПО. Более того, использование объектно)ори)
ентированных технологий не свободно от ряда существенных недо)
статков.
Многие из этих проблем возникли потому, что широко распрост)
ранено заблуждение, будто бы функциональная декомпозиция сверху
69
вниз, предлагаемая структурными методами, противоречит объект)
но)ориентированному процессу проектирования. В результате осоз)
нания этого специалисты приходят к выводу: традиционные и объек)
тно)ориентированные методы должны быть не взаимоисключающи)
ми, а взаимодополняющими.
6.3. «Монолитный» стиль программирования
Напомним одно из общих замечаний: прежде всего, необходимо
иметь в виду, что лабораторные и курсовые работы – это учебные
работы. Цель каждой из них состоит в том, чтобы студенты приобре)
ли знания и умения, которые окажутся полезными при решении дру)
гих, гораздо более сложных задач.
Поэтому само по себе решение поставленной задачи еще не явля)
ется достаточным условием для получения зачета. Отчет по каждой
лабораторной работе должен демонстрировать знание и практичес)
кое умение использования надлежащих методов разработки и доку)
ментирования ПО.
Применительно к знанию методов иерархической декомпозиции и
умению их использования это означает, что приобретать такие зна)
ния и умения нужно с самого начала деятельности, уже в небольших
программных проектах.
Для практической иллюстрации использования различных видов
декомпозиции воспользуемся простым демонстрационным примером.
Заданы два целых значения. Требуется определить наибольшее из
них.
Тривиальная задача, которую решает рассматриваемая програм)
ма, выбрана сознательно – в учебно)методических целях. Во)первых,
это позволяет не отвлекать внимание читателя на содержательную
сторону программы (она очевидна), а сосредоточить его на вопросах
дизайна. Во)вторых, размер программы – небольшой, так что ее пол)
ный текст занимает в книге немного места.
Пример использования профессионального дизайна программ при
разработке более сложного программного проекта приведен в работе
[6].
Для начала, рассмотрим решение задачи демонстрационного при)
мера без использования какой)либо декомпозиции, в так называе)
мом «монолитном» стиле.
Приведем текст программы, оформленный в соответствии с
предъявляемыми требованиями.
70
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
FILE
: Main.cpp
PROJECT
: Max - illustrating project for beginners
(Win32 Console Application)
TASK
: input two integer values and determine
the maximal value of them
PURPOSE
: main function of the project
SOURCE FILES
: Main.cpp
HEADER FILES
: none
NOTES
: none
AUTHOR
DATE
: Michael F.LEKAREV
: 19.10.2005
(C) Copyright
Michael F. LEKAREV 2005. All rights reserved
CHANGES
RefNo.
Who:
Date:
dd.mm.yyyy
#include <stdio.h>
#include <stdlib.h>
Detail:
// FILE, stderr,
//
fopen(), fclose(),
//
fscanf(), fprintf()
// exit()
// MAIN:
//
main function of the project
//
int main( void )
{
FILE *
Input;
//
FILE *
Output;
//
//
int
RetCode;
//
//
int
First;
//
int
Second;
//
int
Max;
//
Contains source data
Will contain results
of processing
RETurn CODE of input/output
standard functions
First value to be compared
Second value to be compared
Maximum of "First", "Second"
// Open "Input"
//
Input = fopen( "Max.in", "rt" );
if ( Input == 0 )
{
fprintf( stderr,
"File \"Max.in\" not opened for input\n"
);
exit( 1 );
}
//
// At this point:
//
"Input" is successfully opened
71
// Main.cpp
Page 02
// Open "Output"
//
Output = fopen( "Max.out", "wt" );
if ( Output == 0 )
{
fprintf( stderr,
"File \"Max.out\" not opened for output\n"
);
exit( 1 );
}
//
// At this point:
//
"Output" is successfully opened
// Input source data
//
RetCode = fscanf( Input, "%d %d", &First, &Second );
if ( RetCode != 2 )
{
fprintf( stderr,
"Error when input source data from \"Max.in\"\n"
);
exit( 1 );
}
//
// At this point:
//
source data are successfully read
// Output source data
//
for visual checking
//
fprintf( Output,
"Values to be compared: %d %d \n",
First, Second
);
// Determine "Max"
//
if ( First >= Second )
{
Max = First;
}
else
{
Max = Second;
}
// Output results of processing
//
fprintf( Output, "Maximum of them: %d \n", Max );
// Finish processing
//
fclose( Input );
fclose( Output );
return ( 0 );
} // end function "main"
// EOF
72
6.4. Функциональная декомпозиция
Недостатки «монолитных» программ подробно обсуждаются на
занятиях в аудитории. Поэтому при выполнении практических ра)
бот необходимо использовать, как минимум, функциональную деком1
позицию.
Ниже показан текст, который спроектирован с использованием
функциональной декомпозиции.
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
FILE
: Main.cpp
PROJECT
: Max - illustrating project for beginners
(Win32 Console Application)
TASK
: input two integer values and determine
the maximal value of them
PURPOSE
: main function of the project
SOURCE FILES
: Main.cpp
HEADER FILES
: none
NOTES
: none
AUTHOR
DATE
: Michael F.LEKAREV
: 19.10.2005
(C) Copyright
Michael F. LEKAREV 2005. All rights reserved
CHANGES
RefNo.
Who:
Date:
dd.mm.yyyy
Detail:
#include <stdio.h>
// FILE, stderr,
//
fopen(), fclose(),
//
fscanf(), fprintf()
// exit()
#include <stdlib.h>
// Global data
//
FILE *
FILE *
//
//
static
static
static
Input;
Output;
// Contains source data
// Will contain results
//
of processing
Local functions of the file
void Start( void );
void Finish( void );
void Work( void );
73
// Main.cpp
Page 02
// MAIN:
//
main function of the project
//
int main( void )
{
Start( );
// prepare application's doing
Work( );
// perform application's work
Finish( );
// complete application's doing
return ( 0 );
// success
} // end function
// START:
//
prepare application's doing
//
static void Start( void )
{
// Open "Input"
//
Input = fopen( "Max.in", "rt" );
if ( Input == 0 )
{
fprintf( stderr,
"File \"Max.in\" not opened for input\n"
);
exit( 1 );
}
// Open "Output"
//
Output = fopen( "Max.out", "wt" );
if ( Output == 0 )
{
fprintf( stderr,
"File \"Max.out\" not opened for output\n"
);
exit( 1 );
}
return;
} // end function
// FINISH:
//
complete application's doing
//
static void Finish( void )
{
fclose( Input );
fclose( Output );
return;
} // end function
74
// Main.cpp
// WORK:
//
perform application's work
//
static void Work( void )
{
int
RetCode;
//
//
int
First;
//
int
Second;
//
int
Max;
//
Page 03
RETurn CODE of input/output
standard functions
First value to be compared
Second value to be compared
Maximum of "First", "Second"
// Input source data
//
RetCode = fscanf( Input, "%d %d", &First, &Second );
if ( RetCode != 2 )
{
fprintf( stderr,
"Error when input source data from \"Max.in\"\n"
);
exit( 1 );
}
// Output source data
//
for visual checking
//
fprintf( Output,
"Values to be compared: %d %d \n",
First, Second
);
// Determine "Max"
//
if ( First >= Second )
{
Max = First;
}
else
{
Max = Second;
}
// Output results of processing
//
fprintf( Output, "Maximum of them: %d \n", Max );
return;
} // end function
// EOF
75
7. ФАЙЛОВАЯ ДЕКОМПОЗИЦИЯ ПРОГРАММНОГО ПРОЕКТА
По мере роста размеров программного проекта становится неудоб)
но работать с длинными текстовым файлами. Приемлемая предель)
ная длина одного такого файла составляет 1000–1500 строчек.
Для устранения этого неудобства используют файловую деком1
позицию – распределение полного текста программы по несколь)
ким файлам.
В учебных целях необходимо использовать файловую декомпо)
зицию уже в сравнительно небольших проектах (начиная, как ми)
нимум, с лабораторных работ, которые выполняются в третьем се)
местре).
Рассмотрим на простом примере методы файловой декомпозиции
(в сочетании с функциональной).
7.1. Расширения имен файлов
Общепринятые расширения имен текстовых файлов, которые со)
держат текст на языке С/С++:
.c
.h
.cpp
.hpp
файл на языке ANSI C;
заголовочный файл, в котором использованы только
средства языка ANSI C;
файл на языке C++;
заголовочный файл, в котором использованы средства
не только языка ANSI C, но также языка С++.
Файлы с расширениями .c, .cpp называются исходными файла)
ми (source files). Файлы с расширениями .h, .hpp называются заго)
ловочными файлами (header files).
Однако компания Microsoft частично игнорирует эти соглаше)
ния, принятые во всем остальном мире, – снабжает все заголовоч)
ные файлы расширением .h.
Несмотря на «особую позицию» компании Microsoft заголовоч)
ные файлы, которые разрабатываются при выполнении практичес)
ких работ, необходимо снабжать расширением .hpp (поскольку ис)
пользуется язык С/С++). Включить такие файлы в состав проекта
IDE MS Visual C++ 6.0 можно следующим образом:
выйти в диалог Project . Add To Project . Files;
выбрать тип файлов C++ Include Files;
выделить необходимые файлы и включить их в состав проекта.
76
7.2. Исходные файлы
7.2.1. Общие замечания
Назначение исходных файлов и их обработка в ходе автоматичес)
кого формирования машинной формы программы рассматриваются
на занятиях в аудитории.
При выполнении практических работ используется язык програм)
мирования С/С++. Поэтому исходные файлы должны иметь расши)
рение .cpp (а не: .с).
7.2.2. Пример оформления исходного файла: файл Main.cpp
Исходный файл, который содержит определение главной функ)
ции main, целесообразно назвать Main.cpp.
В комментарии)заголовке этого файла, кроме всего прочего, необ)
ходимо привести сведения о формулировке решаемой задачи и о фай)
ловом составе программного проекта. Конечно же, не нужно повто)
рять эти сведения в комментариях)заголовках остальных файлов
проекта. Таким образом, при необходимости внесения изменений
потребуется исправлять только один файл (а не несколько).
Пример оформления файла Main.cpp показан на следующих стра)
ницах.
77
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
FILE
: Main.cpp
PROJECT
: Task01 - illustrating project for beginners
(Win32 Console Application)
TASK
: input two integer values and determine
the maximal value of them
PURPOSE
: definition of main function of the project
SOURCE FILES
: Main.cpp
Work.cpp
HEADER FILES
: Task01.hpp
NOTES
: none
AUTHOR
DATE
: Michael F.LEKAREV
: 19.10.2005
(C) Copyright
Michael F. LEKAREV 2005. All rights reserved
CHANGES
RefNo.
Who:
Date:
dd.mm.yyyy
Detail:
#include <stdio.h>
// FILE, stderr,
//
fopen(), fclose(),
//
fprintf()
// exit()
#include <stdlib.h>
#include "Task01.hpp"
// Global data
//
FILE *
FILE *
Input;
Output;
// Contains source data
// Will contain results
//
of processing
// Local functions of the file
//
static void Start( void );
static void Finish( void );
// MAIN:
//
main function of the project
//
int main( void )
{
Start( );
Work( );
Finish( );
return ( 0 );
} // end function
78
// Main.cpp
Page 02
// START:
//
prepare application's doing
//
static void Start( void )
{
// Open "Input"
//
Input = fopen( "Max.in", "rt" );
if ( Input == 0 )
{
fprintf( stderr,
"File \"Max.in\" not opened for input\n"
);
exit( 1 );
}
// Open "Output"
//
Output = fopen( "Max.out", "wt" );
if ( Output == 0 )
{
fprintf( stderr,
"File \"Max.out\" not opened for output\n"
);
exit( 1 );
}
return;
} // end function
// FINISH:
//
complete application's doing
//
static void Finish( void )
{
fclose( Input );
fclose( Output );
return;
} // end function
// EOF
79
7.2.3. Пример оформления исходного файла: файл Work.cpp
Именование остальных исходных файлов программного проекта
следует тщательно продумывать.
В нашем примере, очень простом, принять решение несложно: вто)
рой (и последний) исходный файл проекта содержит только опреде)
ление функции Work и потому называется Work.cpp.
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
FILE
: Work.cpp
PROJECT
: Task01
PURPOSE
: definition of function "Work"
NOTES
: none
AUTHOR
DATE
: Michael F.LEKAREV
: 19.10.2005
(C) Copyright
Michael F. LEKAREV 2005. All rights reserved
CHANGES
RefNo.
Who:
Date:
dd.mm.yyyy
#include <stdio.h>
#include <stdlib.h>
Detail:
// FILE, stderr,
//
fscanf(), fprintf()
// exit()
#include "Task01.hpp"
// References to global data
//
extern FILE *
Input;
extern FILE *
Output;
80
// Contains source data
// Will contain results
//
of processing
// Work.cpp
// WORK:
//
perform application's work
//
void Work( void )
{
int
RetCode;
//
//
int
First;
//
int
Second;
//
int
Max;
//
Page 02
RETurn CODE of input/output
standard functions
First value to be compared
Second value to be compared
Maximum of "First", "Second"
// Input source data
//
RetCode = fscanf( Input, "%d %d", &First, &Second );
if ( RetCode != 2 )
{
fprintf( stderr,
"Error when input source data from \"Max.in\"\n"
);
exit( 1 );
}
// Output source data
//
for visual checking
//
fprintf( Output,
"Values to be compared: %d %d \n",
First, Second
);
// Determine "Max"
//
if ( First >= Second )
{
Max = First;
}
else
{
Max = Second;
}
// Output results of processing
//
fprintf( Output, "Maximum of them: %d \n", Max );
return;
} // end function
// EOF
81
7.2.4. Типовая структура текста исходного файла
Типовая структура текста исходного файла следующая:
// FILE
//
//
: имяфайла.cpp
остальная часть комментария – заголовка файла
#include < ... >
для стандартных заголовочных файлов,
в алфавитном порядке
#include “ ... “
для нестандартных заголовочных файлов,
в алфавитном порядке
определения глобальных данных
ссылки на глобальные данные
определения локальных данных файла
прототипы локальных функций файла
определения всех функций файла (и внешних, и внутренних)
в порядке “сверхувниз”,
в соответствии с иерархией функциональной декомпозиции
// EOF
7.3. Заголовочные файлы
7.3.1. Общие замечания
Назначение заголовочных файлов и их обработка в ходе компиля)
ции подробно рассматриваются на занятиях в аудитории.
При выполнении практических работ используется язык програм)
мирования С/С++. Поэтому заголовочные файлы должны иметь рас)
ширение .hpp (а не: .h).
Все программные проекты, которые разрабатываются в ходе выпол)
нения практических работ, сравнительно небольшие. Поэтому можно
ограничиться одним заголовочным файлом (например, Task01.hpp).
Практика показывает, что в хорошо разработанных программ)
ных проектах заголовочные файлы содержат только такие инструк)
ции языка С/С++, которые не приводят к выделению памяти для
команд и данных на этапе выполнения программы. Иначе говоря, в
заголовочном файле не должны встречаться определения
(definitions) переменных и функций.
82
В заголовочном файле необходимо использовать директивы пре)
процессора, которые обеспечивают так называемую «защиту от по)
вторного включения» (хотя в небольших программных проектах та)
кая защита и не требуется).
7.3.2. Защита от повторного включения
На зачете необходимо уметь объяснить, зачем может потребовать)
ся защита от повторного включения (в более сложных проектах) и
как она действует.
В сложных проектах ПО количество заголовочных файлов может
составить несколько сотен. При этом в некоторых заголовочных фай)
лах используются сведения из других заголовочных файлов.
Для упрощения использования каждый заголовочный файл стре)
мятся сделать «самодостаточным»: в его начало помещают директи)
вы препроцессора #include, с помощью которых в состав файла вклю)
чаются все необходимые сведения. Вот здесь)то и возникает опас)
ность повторного включения.
Рассмотрим повторное включение на простом примере. Пусть тре)
буется работать со структурами данных, которые представляют тре)
угольники и окружности. Они, в свою очередь, используют структу)
ру данных, которая представляет точку на плоскости.
В целях файловой декомпозиции все три структурных типа опре)
делены в трех различных заголовочных файлах:
// FILE
struct TPoint
{
double
double
};
: Point.hpp
// Type POINT:
// point on plane
x;
y;
// EOF
// FILE
: Triangle.hpp
#include «Point.hpp»
// TPoint
struct TTriangle
{
TPoint
};
// Type TRIANGLE
Vertex[ 3 ];
83
// EOF
// FILE
: Circle.hpp
#include «Point.hpp»
// TPoint
struct TCircle
{
TPoint
double
};
// Type CIRCLE
Centre;
Radius;
// EOF
Очевидно, каждый из файлов Triangle.hpp и Circle.hpp сделан «са)
модостаточным»: при использовании одного из этих файлов не нуж)
но помнить о том, что требуются также сведения из файла Point.hpp.
Проблема повторного включения возникнет в том случае, когда в
каком)то исходном файле (.cpp) потребуется включить оба файла: и
Triangle.hpp, и Circle.hpp:
#include «Triangle.hpp»
#include «Circle.hpp»
// TTriangle
// TCircle
Результат «развертывания» препроцессором этих двух директив
#include в исходный текст, который поступает на вход компилято)
ра, образно показан на рис. 7.1.
На рисунке хорошо видно, что результат деятельности препроцес)
сора содержит два определения структурного типа TPoint. Встретив
второе определение TPoint, компилятор С/С++ выдаст сообщение об
ошибке (повторное определение типа TPoint) и прекратит трансля)
цию.
Нетрудно убедиться в том, что аналогичный (неправильный) ре)
зультат получится и тогда, когда в исходном файле директивы
#include поменяются местами:
#include «Circle.hpp»
#include «Triangle.hpp»
// TCircle
// TTriangle
Как же преодолеть возникшее затруднение?
Для этого используют защиту от повторного включения, которая
состоит в следующем. В каждый заголовочный файл помещают ди)
рективы препроцессора, которые проверяют, включается ли данный
файл в первый раз или не в первый раз (т. е. во второй раз, третий,
четвертый, etc.). Если включение – не первое, то текст заголовочного
файла игнорируется (т. е. не передается на вход компилятора).
84
// FILE
: Triangle.hpp
#include “Point.hpp”
// FILE
: Point.hpp
struct TPoint
{
double
double
};
x;
y;
// EOF
struct TTriangle
{
TPoint
};
Vertex[ 3 ];
// EOF
// FILE
: Circle.hpp
#include “Point.hpp”
// FILE
: Point.hpp
struct TPoint
{
double
double
};
x;
y;
// EOF
struct TCircle
{
TPoint
double
};
Centre;
Radius;
// EOF
Рис. 7.1. Результат «развертывания» препроцессором директив
#include
85
Вот как выглядит файл Point.hpp с защитой от повторного вклю)
чения:
// FILE
: Point.hpp
#ifndef _POINT_HPP
#define _POINT_HPP
struct TPoint
{
double
double
};
// Type POINT:
// point on plane
x;
y;
#endif
// EOF
Как же действует эта защита от повторного включения?
Название макроса _POINT_HPP «сконструировано» из названия
и расширения заголовочного файла по очевидным правилам (в раз)
ных компаниях)производителях ПО эти правила слегка варьируют)
ся). Идея состоит в том, что в одном и том же проекте ПО не должно
быть заголовочных файлов с совпадающими названиями (за этим
помогает следить файловая подсистема ОС).
Когда файл Point.hpp в первый раз включается в исходный текст
директивой
#include «Point.hpp»
// TPoint
макрос _POINT_HPP не определен (т. е. не включен в таблицу имен
препроцессора). Поэтому директива препроцессора
#ifndef _POINT_HPP
вырабатывает результат «истина» (да, макрос _POINT_HPP не оп)
ределен).
В результате препроцессор продолжает анализ текста файла
Point.hpp. Далее, встречая директиву
#define _POINT_HPP
препроцессор заносит в свою таблицу имен определение макроса
_POINT_HPP (рис. 7.2).
Каждое следующее появление директивы
#include «Point.hpp»
(т. е. попытка повторного включения файла Point.hpp) приведет к
повторной проверке:
#ifndef _POINT_HPP
86
// FILE
: Triangle.hpp
#include “Point.hpp”
// FILE
: Point.hpp
#ifndef _POINT_HPP
#define _POINT_HPP
struct TPoint
{
double
double
};
4658
8598
696 9285
88
6
123
45678598
1234561722
8
x;
y;
#endif
// EOF
struct TTriangle
{
TPoint
};
Vertex[ 3 ];
// EOF
Рис. 7.2. Результат первого включения заголовочного файла Point.hpp
с защитой от повторного включения
Но теперь окажется, что таблица имен препроцессора уже содер)
жит макрос _POINT_HPP, так что директива #ifndef вырабатывает
результат «ложь» (нет, макрос _POINT_HPP определен). В резуль)
тате препроцессор игнорирует весь текст вплоть до директивы #endif
(рис. 7.3).
В итоге, препроцессор передает на вход компилятора определение
структурного типа TPoint только там, где оно потребовалось в пер)
вый раз (т. е. в первый раз встретилась директива #include
«Point.hpp»). Все последующие директивы #include «Point.hpp» не
приводят к включению в исходный текст определения структурного
типа TPoint – ведь оно уже включено!
Нетрудно убедиться в том, что аналогичный (правильный) резуль)
тат получится и тогда, когда в исходном файле директивы #include
поменяются местами:
87
// FILE
: Circle.hpp
#include “Point.hpp”
// FILE
696 9285
886
: Point.hpp
#ifndef _POINT_HPP
86
123
45678598
1234561722
8
#define _POINT_HPP
struct TPoint
{
double
double
};
x;
y;
#endif
// EOF
struct TCircle
{
TPoint
double
};
Centre;
Radius;
// EOF
Рис. 7.3. Результат попытки повторного включения заголовочного
файла Point.hpp с защитой от повторного включения
#include «Circle.hpp»
#include «Triangle.hpp»
// TCircle
// TTriangle
Таким образом, для защиты от повторного включения необходи)
мо выполнить следующие действия.
Все заголовочные файлы необходимо снабдить директивами пре)
процессора, которые обеспечивают защиту от повторного включения
(в нашем примере это пока что не сделано в файлах Triangle.hpp и
Circle.hpp).
Тщательно проверить соответствие имен макросов в этих дирек)
тивах принятым правилам, например:
_ИМЯ)ФАЙЛА_РАСШИРЕНИЕ.
Все заголовочные файлы в составе проекта ПО поместить в один
и тот же каталог (это гарантирует уникальность имен файлов).
88
7.3.3. Пример оформления заголовочного файла
Ниже показан пример оформления заголовочного файла.
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
FILE : Task01.hpp
PROJECT
: Task01
PURPOSE
: prototypes of all external functions
of the project
NOTES
: none
AUTHOR
DATE : 19.10.2005
: Michael F.LEKAREV
(C) Copyright
CHANGES
RefNo.
Michael F. LEKAREV 2005. All rights reserved
Date:
Who:
dd.mm.yyyy
Detail:
#ifndef _TASK01_HPP
#define _TASK01_HPP
/* Prototypes
** of all external functions
** of the project
*/
// Functions of file “Work.cpp”
//
void Work( void );
#endif
// EOF
Обратите внимание на размещение в этом примере директив пре)
процессора и остального текста.
Директивы препроцессора занимают среди других средств языка
С/С++ положение «бедных родственников». На них редко смотрят
как на «настоящие» инструкции языка. Кроме того, в ранних вер)
сиях языка Си на их размещение накладывались различные огра)
ничения.
89
В результате многие программисты как бы не замечают директив
препроцессора, а их размещение считают не заслуживающим внима)
ния.
Стандарт языка С/С++ снимает любые ограничения на позицию
размещения признака директивы ‘#‘ и имени директивы. Един)
ственное ограничение состоит в том, что символ ‘#‘ должен быть
первым непробельным символом в строчке текста.
Имя директивы может следовать за символом ‘#‘ без разделяю)
щего пробела, но также может быть отделено от него одной или не)
сколькими пробельными литерами. Правда, использование этой воз)
можности не способствует сколько)нибудь заметному улучшению
наглядности текста.
Директивы условной компиляции (#if, #ifdef, #ifndef, #elif,
#else, #endif) подобны инструкциям if и if ... else if ... else. Соответ)
ственно, их размещение должно следовать тем же правилам: «вло)
женный» текст должен быть размещен со сдвигом вправо на один
шаг отступа (4 позиции).
7.3.4. Типовая структура текста заголовочного файла
В общем случае заголовочный файл должен включать, главным
образом, определения нестандартных типов данных и прототипы всех
внешних функций проекта. Конечно же, как и всегда, правил без
исключений нет.
Таким образом, структура текста заголовочного файла должна
быть примерно следующей (имена файлов, типов данных и функций
приведены для конкретности).
90
// FILE
//
: Task09.hpp
11222222222222222222222234567896
2645823956
2–2637362676
#ifndef _TASK09_HPP
#define _TASK09_HPP
#include <stdio.h>
// FILE
/* Definitions
**
of all user-defined data types
*/
struct TStream
{
char *
Title;
FILE *
File;
};
struct TPoint
{
double
double
};
struct TCircle
{
TPoint
double
};
// Type STREAM:
//
stream either for input
//
or for output
// TITLE:
//
operating system’s
//
data set name
// FILE:
//
pointer to
//
C RunTime library’s
//
File Control Block
// Type POINT
//
point on plane
x;
y;
// Type CIRCLE
Centre;
Radius;
...
91
// Task09.hpp
Page 02
/* Prototypes
**
of all external functions
**
of the project
*/
// Functions of file "Error.cpp
//
void Error
(
int
MsgId
);
void Error
(
int
MsgId,
char *
VarField1
);
void Error
(
int
MsgId,
char *
VarField1,
char *
VarField2
);
void Error
(
int
MsgId,
char *
VarField1,
char *
VarField2,
char *
VarField3
);
11112134565673819
71135417153471191
// Functions of file "Input.cpp
//
void SetInputTitle( void );
11112134565673819
71135417153471191
...
void GetChar
(
TChar &
);
...
#endif
// EOF
92
ViewChar
8. ТЕСТИРОВАНИЕ И ОТЛАДКА
8.1. Общие замечания
Надлежащий уровень качества ПО достигается, прежде всего,
путем надлежащего проектирования. Никакие другие мероприя)
тия не могут компенсировать низкое качество структуры про)
граммного проекта и его оформления.
Тем не менее, тестирование (т. е. проверка работоспособности
программы путем обработки тщательно разработанного комплек)
та контрольных примеров) играет важную роль [1].
Обычно говорят, что тестирование может показать только нера1
ботоспособность программы (когда тот или иной контрольный
пример обрабатывается неправильно), но не может служить дока)
зательством корректности программы.
И это действительно так: успешная обработка всех разработан)
ных контрольных примеров не дает стопроцентной гарантии кор)
ректности. Не исключено, что найдется такой комплект исходных
данных, который будет обработан неправильно (и, в худшем слу)
чае, даже приведет к «аварии» программы).
Но все же продуманное тестирование (в дополнение к надлежа)
щему проектированию!) позволяет обеспечить приемлемый уровень
корректности ПО за приемлемую цену.
8.2. Разновидности исходных данных для тестирования
Исходные данные, которые содержатся в одном из контрольных
примеров, можно отнести к одной из следующих разновидностей
(рис. 8.1).
1. Типовые исходные данные: значения, которые находятся в
пределах области корректных исходных данных, вдали от ее гра)
ниц.
2. Предельно допустимые исходные данные: значения, которые
находятся еще в пределах области корректных исходных данных,
но вплотную примыкают к ее границам.
3. Почти корректные исходные данные: значения, которые на)
ходятся уже за пределами области корректных исходных данных,
но вплотную примыкают к ее границам.
4. Некорректные исходные данные: значения, которые нахо)
дятся за пределами области корректных исходных данных, дале)
ко от ее границ.
93
1234567 89
86
59 4
9482
9
4782 7782
52274 4
6982
9
4782 7782
469 3455236782
9
4782 7782
1234567 89
86
59 4
123455236782
9
4782 7782
Рис. 8.1. Разновидности исходных данных
Очевидно, желательно, чтобы тест (комплект контрольных при)
меров) содержал исходные данные всех перечисленных разновид)
ностей.
8.3. Выполнение теста: обработка пакета
контрольных примеров
Для достижения приемлемой уверенности в правильности програм)
мы необходимо разработать тест – комплект контрольных приме)
ров.
Программа должна обеспечивать ввод теста из файла (а не с кла)
виатуры). Это нужно для того, чтобы гарантировать «повторяемость
экспериментов», а также для документирования отладки.
Таким образом, псевдокод фрагмента программного проекта, ко)
торый обеспечивает пропуск теста, имеет следующий вид:
94
// “Пропустить” тест
// (комплект контрольных примеров)
//
for ( ; ; )
{
попытаться ввести “порцию” исходных данных;
if ( конецфайла вместо “порции” ) break;
обработать “порцию” исходных данных;
вывести результаты обработки;
}
Иногда особенности программы не позволяют поместить в один
тест более одного контрольного примера. В этом случае необходимо
разработать систему тестов (группу файлов).
Систему тестов используют и тогда, когда контрольных примеров
настолько много, что помещать их в единственный файл нерацио)
нально.
8.4. Переработанный текст демонстрационного примера
Если посмотреть на демонстрационный пример сквозь призму тес)
тирования, то его цель состоит в проверке «очень небольшой» подси)
стемы проекта ПО – функции определения наибольшего из двух зна)
чений.
Поэтому необходимо обособить именно эту часть демонстрацион)
ного примера: оформить ее как функцию и поместить в отдельный
файл. Все остальные части программы образуют «отладочное окру)
жение» проверяемой подсистемы.
Благодаря функциональной и файловой декомпозиции проверен)
ную подсистему несложно включить в создаваемый проект ПО. Столь
же просто при необходимости вернуться к возобновлению тестирова)
ния. Ниже показан текст переработанного демонстрационного при)
мера.
95
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
FILE
: Main.cpp
PROJECT
: Task01 - illustrating project for beginners
(Win32 Console Application)
TASK
: input two integer values and determine
the maximal value of them
PURPOSE
: main function of the project
SOURCE FILES
: Main.cpp
Max.cpp
HEADER FILES
: Task01.hpp
NOTES
: none
AUTHOR
DATE
: Michael F.LEKAREV
: 19.10.2005
(C) Copyright
Michael F. LEKAREV 2005. All rights reserved
CHANGES
RefNo.
Who:
Date:
dd.mm.yyyy
Detail:
#include <stdio.h>
#include <stdlib.h>
// FILE, EOF, stderr,
//
fopen(), fclose(),
//
fscanf(), fprintf()
// exit()
#include "Task01.hpp"
// Local data of the file
//
static FILE *
Input;
static FILE *
Output;
//
//
static
static
static
// Contains source data
// Will contain results
//
of processing
Local functions of the file
void Start( void );
void Finish( void );
void Work( void );
// MAIN:
//
main function of the project
//
int main( void )
{
Start( );
Work( );
Finish( );
return ( 0 );
} // end function
96
// success
// Main.cpp
Page 02
// START:
//
prepare application's doing
//
static void Start( void )
{
// Open "Input"
//
Input = fopen( "Max.in", "rt" );
if (Input == 0)
{
fprintf( stderr,
"File \"Max.in\" not opened for input\n"
);
exit( 1 );
}
// Open "Output"
//
Output = fopen( "Max.out", "wt" );
if (Output == 0)
{
fprintf( stderr,
"File \"Max.out\" not opened for output\n"
);
exit( 1 );
}
return;
} // end function
// Finish:
//
complete application's doing
//
static void Finish( void )
{
fclose( Input );
fclose( Output );
return;
} // end function
97
// Main.cpp
// WORK:
//
perform application's work
//
static void Work( void )
{
int
RetCode;
//
//
int
First;
//
int
Second;
//
int
Result;
//
//
Page 03
RETurn CODE of input/output
standard functions
First value to be compared
Second value to be compared
result of comparation:
maximum of "First", "Second"
// Process test, i.e. the given package
//
of testing examples
//
for ( ; ; )
{
// Input the next piece of source data
//
RetCode = fscanf( Input, "%d %d", &First, &Second );
if ( RetCode == EOF ) break;
if ( RetCode != 2 )
{
fprintf( stderr,
"Error when input source data from \"Max.in\"\n"
);
exit( 1 );
}
// Output source data
//
for visual checking
//
fprintf( Output,
"Values to be compared: %d %d \n",
First, Second
);
// Determine "Result"
//
Result = Max( First, Second );
// Output results of processing
//
fprintf( Output,
"Maximum of them: %d \n\n",
Result );
} // end for
return;
} // end function
// EOF
98
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
FILE
: Task01.hpp
PROJECT
: Task01
PURPOSE
: prototypes of all external functions
of the project
NOTES
: none
AUTHOR
DATE
: Michael F.LEKAREV
: 19.10.2005
(C) Copyright
Michael F. LEKAREV 2005. All rights reserved
CHANGES
RefNo.
Who:
Date:
dd.mm.yyyy
Detail:
#ifndef _TASK01_HPP
#define _TASK01_HPP
/* Prototypes
**
of all external functions
**
of the project
*/
// Functions of file "Max.cpp"
//
int Max( int First, int Second );
#endif
// EOF
99
Библиографический список
1. Безбородов Ю. М. Индивидуальная отладка программ. М.: На)
ука, 1982. 192 с.
2. Бочков С. О., Субботин Д. М. Язык программирования Си для
персонального компьютера. М.: Радио и связь, 1990. 384 с.
3. Керниган Б., Ритчи Д. Язык программирования Си: Пер. с англ.
2)е изд., перераб. и доп. М.: Финансы и статистика, 1992. 272 с.
4. Лекарев М. Ф. L)сеть в сверхбольшом программном проекте /
СПбГТУ. СПб., 2000. 96 с.
5. Лекарев М. Ф. Программная реализация конечных автоматов с
использованием L)сети / СПбГТУ. СПб., 2000. 32 с.
6. Лекарев М. Ф. Данные с плавающей точкой / ГУАП. СПб., 2006.
80 с.
7. Брукс Ф. П. Как проектируются и создаются программные ком)
плексы (Мифический человеко)месяц): Пер. с англ. М.: Наука, 1979.
152 с.
8. Brooks F. P., Jr. No silver bullet: essence and accidents of software
engineering // Computer. 1987. Vol. 20. N 4. Р. 10–19.
9. Дал У., Дейкстра Э., Хоор К. Структурное программирование:
Пер. с англ. М.: Мир, 1975. 247 с.
10. Фокс Дж. Программное обеспечение и его разработка: Пер. с
англ. М.: Мир, 1985. 368 с.
11. Straker D. C)style: standards and guidelines. Prentice Hall,
International (UK) Ltd., 1992. 231 p.
12. Stroustrup B. The C++ Programming Language. 2nd Edition.
Addison)Wesley, 1991. 669 p.
100
Документ
Категория
Без категории
Просмотров
6
Размер файла
388 Кб
Теги
lekarev2
1/--страниц
Пожаловаться на содержимое документа