Реферат: Правила программирования на Си и Си++ Ален И. Голуб



ВЕРЕВКА ДОСТАТОЧНОЙ ДЛИНЫ, ЧТОБЫ… ВЫСТРЕЛИТЬ СЕБЕ В НОГУ

Правила программирования на Си и Си++

Ален И. Голуб

Москва 2001

Программисты, инженеры, научные работники, студенты и все, кто работает с Си или Си++! Если вы хотите писать лучший код без блужданий по лабиринтам технической документации, то это краткое, но содержательное руководство является именно тем, что вам нужно. "Веревка достаточной длины, чтобы… выстрелить себе в ногу" предлагает более 100 практических правил, которые вы сможете использовать для создания элегантного, простого в сопровождении кода. А так как книга написана признанным знатоком в этой области, то и вы в ней не заблудитесь.

Ален Голуб предлагает необходимый набор пояснений, советов и технических приемов с целью помочь вам полностью использовать возможности этих чрезвычайно мощных языков. Но не бойтесь встретить очередное скучное руководство по программированию. Автору удается сделать изложение столь серьезной темы живым и интересным за счет рассыпанного по тексту юмора и глубокого знания предмета.

Голуб рассматривает универсальные правила, форматирование и правильную организацию программ перед тем, как углубиться в такие основополагающие вопросы, как:

Практические способы организации и написания сопровождаемого кода.

Объектно-ориентированное программирование и методы абстракции данных.

Как избежать проблем, специфических для Си и Си++.

Для закрепления правил, намеченных в этой книге в общих чертах, предлагается множество примеров. Вы также найдете здесь полезные проектные стратегии, освещение вопросов административного управления и многое другое.

© Original copyright. McGraw–Hill, 1995

© Перевод с английского языка. В.Зацепин, 1996

© Редакция и текст примечаний. В.Базаров, 1998

© Оформление Интернет–версии. В.Зацепин, 2001


^ Посвящается Аманде

Содержание

Содержание 4

Благодарности 11

Введение 12

Часть 15

Процесс проектирования 15

1. Сущность программирования: без сюрпризов, минимум сцепления и максимум согласованности 16

2. Подавляйте демонов сложности (часть 1) 17

2.1. Не решайте проблем, которых не существует 17

2.2. Решайте конкретную проблему, а не общий случай 17

3. Интерфейс пользователя не должен быть похожим на компьютерную программу (принцип прозрачности) 19

4. Не путайте легкость в изучении с легкостью в использовании 22

5. Производительность может измеряться числом нажатий клавиш 23

6. Если вы не можете сказать это по-английски, то вы не сможете выполнить это и на Си/Си++ 23

6.1. Начинайте с комментариев 25

7. Читайте код 25

7.1. В цехе современных программистов нет места примадоннам 26

8. Разбивайте сложные проблемы на задачи меньшего размера 26

9. Используйте весь язык 26

9.1. Используйте для работы соответствующий инструмент 26

10. Проблема должна быть хорошо продумана перед тем, как она сможет быть решена 27

11. Компьютерное программирование является индустрией обслуживания 28

12. Вовлекайте пользователей в процесс проектирования 29

13. Заказчик всегда прав 29

14. Малое — это прекрасно (большое == медленное) 30

Часть 32

Общие проблемы разработки программ 32

15. Прежде всего, не навреди 33

16. Редактируйте свою программу 33

17. Программа должна быть переписана не менее двух раз 33

18. Нельзя измерять свою производительность числом строк 33

19. Вы не можете программировать в изоляции 34

20. Пустые потери времени 35

21. Пишите программу с учетом сопровождения — вы специалист по сопровождению 36

21.1. Эффективность — часто просто пугало 36

Часть 37

Форматирование и документация 37

22. Программа без комментариев ничего не стоит 38

23. Располагайте программу и документацию вместе 38

24. Комментарии должны быть предложениями 39

25. Пропустите свой исходный тест через систему проверки орфографии 39

26. Комментарий не должен подтверждать очевидное 39

27. Комментарий должен предоставлять только нужную для сопровождения информацию 40

29. Комментарии должны быть выровнены вертикально 44

30. Используйте аккуратные столбцы везде, где можно 46

31. Не располагайте комментариев между именем функции и открывающей скобкой 47

32. Помечайте конец длинного составного оператора чем-нибудь, имеющим смысл 47

33. Располагайте в строке только один оператор 48

34. Указывайте имена аргументов в прототипах функций 49

35. Используйте "предикатную" форму при разбиении длинных выражений 49

36. Подпрограмма должна помещаться на экране 50

37. Нужно обеспечивать возможность распечатки всего текста программы 50

38. Используйте штриховую линию для зрительного разделения подпрограмм 51

39. Пробел — один из наиболее эффективных комментариев 52

40. Используйте отступы в четыре пробела 53

41. Условные операторы выделяются абзацными отступами 54

41.1. Комментарии должны иметь тот же отступ, что и окружающий текст программы 55

42. Выравнивайте скобки вертикально по левой границе 56

43. Используйте скобки, если в условном операторе имеется более, чем одна строка 56

Часть 57

Имена и идентификаторы 57

44. Имена должны быть обычными словами английского языка, описывающими то, что делает функция, аргумент или переменная 58

44.1. Не используйте в качестве имен тарабарщину 59

45. Имена макросов должны записываться ЗАГЛАВНЫМИ_БУКВАМИ 60

45.1. Не используйте заглавных букв для констант перечисления 60

45.2. Не используйте заглавных букв в именах типов, созданных при помощи typedef 60

46. Не пользуйтесь именами из стандарта ANSI Cи 61

47. Не пользуйтесь именами Microsoft 61

48. Избегайте ненужных идентификаторов 63

49. Именованные константы для булевых величин редко необходимы 63

Часть 67

Правила обычного программирования 67

50. Не путайте привычность с читаемостью 68

51. Функция должна делать только одно дело 70

52. Иметь слишком много уровней абстракции или инкапсуляции так же плохо, как и слишком мало 70

53. Функция должна вызываться более одного раза, но… 71

53.1. Код, используемый более одного раза, должен быть помещен в функцию 72

54. Функция должна иметь лишь одну точку выхода 72

54.1. Всегда предусматривайте возврат значения из блока внешнего уровня 73

55. Избегайте дублирования усилий 74

56. Не захламляйте область глобальных имен 75

56.1. Избегайте глобальных идентификаторов 75

56.2. Никогда не требуйте инициализации глобальной переменной при вызове функции 76

56.2.1. Делайте локальные переменные статическими в рекурсивных функциях, если их значения не участвуют в рекурсивном вызове 77

56.3. Используйте счетчик экземпляров объектов вместо инициализирующих функций 78

56.4. Если оператор if завершается оператором return, то не используйте else 79

57. Помещайте более короткий блок условного оператора if/else первым 81

58. Старайтесь сдвинуть ошибки с этапа выполнения на этап компиляции 81

59. Применяйте указатели на функции Си в качестве селекторов 82

60. Избегайте циклов do/while 84

60.1. Никогда не используйте do/while для бесконечного цикла 85

61. В цикле со счетчиком его значение должно по возможности уменьшаться 85

62. Не делайте одно и то же двумя способами одновременно 85

63. Используйте оператор for, если имеются любые два из инициализурующего, условного или инкрементирующего выражений 86

64. То, чего нет в условном выражении, не должно появляться и в других частях оператора for 88

65. Допускайте, что ситуация может измениться в худшую сторону 88

66. Компьютеры не знают математики 89

66.1. Рассчитывайте на невозможное 90

66.2. Всегда проверяйте коды возврата ошибки 91

67. Избегайте явно временных переменных 91

68. Не нужно магических чисел 92

69. Не делайте предположений о размерах 92

70. Опасайтесь приведения типов (спорные вопросы Си) 94

71. Немедленно обрабатывайте особые случаи 96

72. Не старайтесь порадовать lint 98

73. Помещайте код, динамически распределяющий и освобождающий память, в одном и том же месте 99

74. Динамическая память — дорогое удовольствие 100

75. Тестовые подпрограммы не должны быть интерактивными 102

76. Сообщение об ошибке должно подсказывать пользователю, как ее исправить 102

77. Не выводите сообщения об ошибке, если она исправима 103

78. Не используйте системно-зависимых функций для сообщений об ошибках 103

Часть 105

Препроцессор 105

79. Все из одного .h файла должно быть использовано в, по меньшей мере, двух .c файлах 106

80. Используйте вложенные директивы #include 106

81. Вы должны быть всегда способны заменить макрос функцией 107

81.1. Операция ?: не то же самое, что и оператор if/else 112

81.2. Помещайте тело макроса и его аргументы в круглые скобки 113

82. enum и const лучше, чем макрос 114

83. Аргумент параметризированного макроса не должен появляться в правой части более одного раза 115

83.1. Никогда не используйте макросы для символьных констант 116

84. Если все альтернативы отпали, то используйте препроцессор 117

Часть 120

Правила, относящиеся к языку Си 120

85. Подавляйте демонов сложности (часть 2) 121

85.1. Устраняйте беспорядок 121

85.2. Избегайте битовых масок; используйте битовые поля 122

85.3. Не используйте флагов завершения 124

85.4. Рассчитывайте, что ваш читатель знает Си 125

85.5. Не делайте вид, что Си поддерживает булевый тип (#define TRUE) 125

86. Для битового поля размером 1 бит должен быть определен тип unsigned 127

87. Указатели должны указывать на адрес, больший, чем базовый для массива 127

88. Используйте указатели вместо индексов массива 128

89. Избегайте goto, за исключением… 129

Часть 133

Правила программирования на Си++ 133

Часть 8а. Вопросы проектирования и реализации 134

90. Не смешивайте объектно-ориентированное и "структурное" проектирование 134

90.1. Если проект не ориетирован на объекты, то используйте Си 134

91. Рассчитывайте потратить больше времени на проектирование и меньше на разработку 136

92. Библиотеки классов Си++ обычно не могут быть использованы неискушенными пользователями 136

93. Пользуйтесь контрольными таблицами 138

94. Сообщения должны выражать возможности, а не запрашивать информацию 140

95. Вам обычно не удастся переделать имеющуюся структурную программу в объектно-ориентированную 140

96. Объект производного класса является объектом базового класса 142

97. Наследование — это процесс добавления полей данных и методов-членов 142

98. Сначала проектируйте объекты 145

99. Затем проектируйте иерархию снизу вверх 145

99.1. Базовые классы должны иметь более одного производного объекта 146

100. Возможности, определенные в базовом классе, должны использоваться всеми производными классами 146

101. Си++ — это не Smalltalk: избегайте общего класса object 146

102. Смешения не должны наследоваться от чего попало 151

103. Смешения должны быть виртуальными базовыми классами 151

104. Инициализируйте виртуальные базовые классы при помощи конструктора, используемого по умолчанию 152

105. Наследование не подходит, если вы никогда не посылаете сообщения базового класса объекту производного класса 153

106. Везде, где можно, предпочитайте включение наследованию 153

107. Используйте закрытые базовые классы лишь когда вы должны обеспечить виртуальные замещения 153

108. Проектируйте структуры данных в последнюю очередь 154

109. Все данные в определении класса должны быть закрытыми 154

110. Никогда не допускайте открытого доступа к закрытым данным 154

110.1. Не пользуйтесь функциями типа get/set (чтения и присваивания значений) 159

111. Откажитесь от выражений языка Си, когда программируете на Си++ 161

112. Проектируйте с учетом наследования 162

112.1. Функция-член должна обычно использовать закрытые поля данных класса 163

113. Используйте константы 164

114. Используйте структуры только тогда, когда все данные открытые и нет функций-членов 165

115. Не размещайте тела функций в определениях классов 166

116. Избегайте перегрузки функций и аргументов, используемых по умолчанию 171

Часть 8б. Проблемы сцепления 173

117. Избегайте дружественных классов 173

118. Наследование — это форма сцепления 174

119. Не портьте область глобальных имен: проблемы Си++ 175

Часть 8в. Ссылки 179

120. Ссылочные аргументы всегда должны быть константами 179

121. Никогда не используйте ссылки в качестве результатов, пользуйтесь указателями 179

122. Не возвращайте ссылки (или указатели) на локальные переменные 183

123. Не возвращайте ссылки на память, выделенную оператором new 183

Часть 8г. Конструкторы, деструкторы и operator=( ) 185

124. Операция operator=( ) должна возвращать ссылку на константу 186

125. Присваивание самому себе должно работать 186

126. Классы, имеющие члены-указатели, должны всегда определять конструктор копии и функцию operator=() 187

127. Если у вас есть доступ к объекту, то он должен быть инициализирован 188

128. Используйте списки инициализации членов 188

129. Исходите из того, что члены и базовые классы инициализируются в случайном порядке 188

130. Конструкторы копий должны использовать списки инициализации членов 190

131. Производные классы должны обычно определять конструктор копии и функцию operator=( ) 191

132. Конструкторы, не предназначенные для преобразования типов, должны иметь два или более аргумента 194

133. Используйте счетчики экземпляров объектов для инициализации на уровне класса 195

134. Избегайте инициализации в два приема 196

135. Суперобложки на Си++ для существующих интерфейсов редко хорошо работают 196

Часть 8д. Виртуальные функции 201

136. Виртуальные функции — это те функции, которые вы не можете написать на уровне базового класса 201

137. Виртуальная функция не является виртуальной, если вызывается из конструктора или деструктора 202

138. Не вызывайте чисто виртуальные функции из конструкторов 206

139. Деструкторы всегда должны быть виртуальными 206

140. Функции базового класса, имеющие то же имя, что и функции производного класса, обычно должны быть виртуальными 207

141. Не делайте функцию виртуальной, если вы не желаете, чтобы производный класс получил контроль над ней 209

142. Защищенные функции обычно должны быть виртуальными 209

143. Опасайтесь приведения типов (спорные вопросы Си++) 210

144. Не вызывайте конструкторов из операции operator=( ) 212

Часть 8е. Перегрузка операций 215

145. Операция — это сокращение (без сюрпризов) 215

146. Используйте перегрузку операций только для определения операций, имеющих аналог в Си (без сюрпризов) 216

147. Перегрузив одну операцию, вы должны перегрузить все сходные с ней операции 217

148. Перегруженные операции должны работать точно так же, как они работают в Си 218

149. Перегруженной бинарной операции лучше всего быть встроенным (inline) псевдонимом операции приведения типа 219

150. Не теряйте разум с операторами преобразования типов 221

151. Если можно, то делайте все преобразования типов с помощью конструкторов 221

Часть 8ж. Управление памятью 223

152. Используйте new/delete вместо malloc()/free() 223

153. Вся память, выделенная в конструкторе, должна быть освобождена в деструкторе 223

154. Локальные перегрузки операторов new и delete опасны 223

Часть 8з. Шаблоны 224

155. Используйте встроенные шаблоны функций вместо параметризированных макросов 224

156. Всегда знайте размер шаблона после его расширения 225

157. Шаблоны классов должны обычно определять производные классы 228

158. Шаблоны не заменяют наследование; они его автоматизируют 228

Часть 8и. Исключения 232

159. Назначение исключений — не быть пойманными 232

160. По возможности возбуждайте объекты типа error 235

161. Возбуждение исключений из конструктора ненадежно 237

Заключение 244

Об авторе 245


Благодарности

Работа над этой книгой затянулась, и я весьма обязан трем редакторам издательства McGraw-Hill, которые по очереди мирились с постоянными задержками с моей стороны: Нэйлу Ливайну, Дэну Гонно и Дженифер Холт-Диджованна. Я особенно признателен Бобу Дюшарму, который защитил меня от самого себя, сделав очень тщательный просмотр первоначального наброска. Его советы значительно улучшили книгу в ее нынешнем виде.

Введение

Название этой книги отражает то, что я считаю основной трудностью при работе как с Си++, так и с Си: эти языки дают вам столько гибкости, что если у вас нет желания и способности призвать себя к порядку, то в итоге вы можете получить гигантский модуль не поддающейся сопровождению тарабарщины, притворяющейся к тому же компьютерной программой. Вы можете поистине делать все при помощи этих языков, даже если вы этого не хотите. В этой книге делается попытка дать средство для преодоления этой трудности в виде собрания практических правил программирования на Си++ и Си — правил, которые, надеюсь, уберегут вас от неприятностей, если вы будете их использовать с самого начала. Хотя большинство из приводимых здесь правил применимы равно при программировании как на Си, так и на Си++, я включил много материала, относящегося лишь к миру Си++ и сконцентрированного по мере возможности в заключительном разделе. Если вы программируете лишь на Си, то просто игнорируйте материал по Си++, встречающийся вам в более ранних разделах.

Я профессионально занимаюсь программированием примерно с 1979 года и ежедневно пользуюсь правилами из этой книги. Я не утверждаю, что эти правила безусловны, или даже "верны". Однако я могу сказать, что они отлично мне служили все это время. Хотя эта книга не относится к категории путеводителей по "ловушкам и рытвинам", многие из этих правил предохранят вас от неприятностей того сорта, который обсуждается в путеводителях по "ловушкам и рытвинам".

Практические правила по своей сути гибки. Они постепенно меняются с ростом опыта, и ни одно правило не действует постоянно. Тем не менее я предупреждаю вас с самого начала, что мое мнение относительно этого материала самое наилучшее и что я не очень симпатизирую неряшливым мыслям или небрежному программированию. Я не извиняюсь за усиленное подчеркивание тех вещей, в которые я сильно верю. Мои мнения всегда могут измениться, если, конечно, вы сможете убедить меня в том, что я не прав, но имейте в виду, что эта книга основана на опыте, а не на теории. Я сознаю, что большая часть этой книги подходит опасно близко к чьему-то культу и многие вещи, произносимые мной, дискуссионны, но думаю, что всегда имеется возможность разумного разговора двух людей, объединенных целью совершенствования своего мастерства.

Я часто читаю курсы по Си++ и объектно-ориентированному проектированию как по приглашению частных фирм, так и в Калифорнийском университете в Беркли. Эта книга появилась в ответ на просьбы моих студентов, большинство из которых увлеченные профессионалы с настоящим желанием изучить этот материал. Я вижу множество программ в процессе проверки домашних заданий, и эти программы достаточно репрезентативны в качестве произведений сообщества профессиональных программистов из района залива Сан-Франциско. К несчастью, каждый семестр я также вижу, что одни и те же проблемы повторяются снова и снова. Поэтому эта книга является некоторым образом и списком распространенных проблем, найденных мной в созданных настоящими программистами реальных программах, сопровождаемым моими советами по их решению.

Обсуждаемые здесь проблемы программирования и проектирования не ограничиваются, к несчастью, лишь ученическими программами. Многие из примеров того, что не следует делать, взяты из коммерческого продукта: библиотеки классов Microsoft Foundation Classes (MFC) корпорации Microsoft. Я могу сказать, что эта библиотека была спроектирована без заботы о удобстве сопровождения людьми, не подозревающими о существовании даже элементарных принципов объектно-ориентированного проектирования. Я не выделял явно большинство примеров этого в тексте, так как это не книга с названием "Что неправильно в MFC"; пользователи библиотеки MFC узнают ее код, когда натолкнутся на него. Я выбрал примеры из MFC просто потому, что мне пришлось много с ней работать и очень близко познакомиться с ее недостатками. Во многих других коммерческих библиотеках классов имеются сходные проблемы.

Наконец, эта книга не является введением в Си++. Обсуждение, сопровождающее относящиеся к Си++ правила, предполагает, что вы знаете этот язык. Я не расходую место на описание того, как работает Си++. Имеется множество хороших книг, которые учат вас языку Си++, включая мою собственную "C+C++" (New York: McGraw-Hill,1993). Вы должны также ознакомиться с принципами объектно-ориентированного проектирования. Я рекомендую второе издание книги Гради Буча "Object-Oriented Analysis and Design with Applications" (Redwood City: Benjamin Cummings,1994).

^ О нумерации правил: иногда я группировал некоторые правила вместе, потому что удобно описывать их все одновременно. В этом случае все эти правила (имеющие различные номера) располагаются в начале раздела. Я использовал запись номера правила вида "1.2" в случаях, когда оно является особым случаем другого правила.

Часть

^ 1

Процесс проектирования

Эта часть вместе с последующей, посвященной разработке, являются наиболее туманными в этой книге. Правила здесь довольно общего характера по своей природе, они совсем не затрагивают техники программирования на Си или Си++, а скорее рассматривают более общий процесс проектирования и разработки программы.

Правила из данной части относятся к процессу общего проектирования. После прочтения этой части в законченном виде я стал беспокоиться, что многие из этих правил будут казаться банальными. Несмотря на это, некоторые из приводимых здесь правил являются самыми важными в этой книге, потому что нарушение их может вызвать много бед в процессе разработки. В известном смысле, большинство правил этой части предназначены для управленцев; программисты их часто знают, но у них нет свободы, необходимой, чтобы воспользоваться своими знаниями.

^ 1. Сущность программирования: без сюрпризов, минимум сцепления и максимум согласованности

Многие (если не все) правила в этой книге могут быть объединены в три метаправила (при желании), выраженные в заголовке этого раздела.

Правило "без сюрпризов" не требует пояснений само по себе. Пользовательский интерфейс должен действовать так, как кажется он должен действовать. Функция или переменная должны делать то, что означают их имена.

Сцепление — это связь между двумя программами или объектами пользовательского интерфейса. Когда один объект меняется, то все, с чем он соединен, может также измениться. Сцепление вызывает сюрпризы. (Я меняю эту штучку здесь, и внезапно та штуковина вон там перестает работать). Пример из Си++: если объект одного класса посылает сообщение объекту второго класса, то посылающий класс сцеплен с принимающим классом. Если вы меняете интерфейс для принимающего класса, то вы также должны исследовать код в посылающем классе, чтобы убедиться в том, что он еще работает. Этот вид слабого сцепления безвреден. Вам нужно знать об отношениях сцепления для сопровождения программы, но без некоторого количества сцеплений программа не могла бы работать. Несмотря на это, для вас желательно по мере возможности минимизировать число отношений сцепления.

Эта минимизация обычно выполняется в Си посредством модулей, а в Си++ посредством классов. Функции в модуле (функции-члены в классе) сцеплены друг с другом, но за исключением нескольких интерфейсных функций (или объектов) они вовсе не сообщаются с внешним миром. В Си вы должны использовать статический класс памяти, чтобы ограничить использование функции одним модулем. В Си++ вы используете закрытые функции-члены.

Согласованность является противоположностью сцепления; сущности, которые группируются вместе (пункты диалогового и простого меню, функции в модуле, или члены класса), должны быть связаны по назначению. Отсутствие связности также является "сюрпризом". У текстового редактора, которым я пользуюсь, имеется в меню пункт "Настройка" и, кроме того, дополнительные опции настройки рассыпаны по четырем другим всплывающим меню. Я ожидал согласованной конфигурации и, когда не смог найти нужную мне опцию в пункте "Настройка", то решил, что этой опции просто нет. Эта плохо спроектированная система до сих пор доставляет беспокойство; после года пользования я по-прежнему не помню, где расположена каждая опция, и часто вынужден тратить раздражающие пять минут на поиск в пяти разных местах того, что хотел изменить. По отношению к исходному коду отсутствие согласованности заставляет вас делать то же самое — тратить свою жизнь на поиск объявлений функций в 15 различных файлах, что является очевидной проблемой при сопровождении.

^ 2. Подавляйте демонов сложности (часть 1)

Ричард Рашид (разработчик Mach — варианта ОС UNIX) выступил несколько лет назад с основным докладом на конференции разработчиков Microsoft. Его главный смысл состоял в том, что слишком большая сложность как в пользовательском интерфейсе, так и в программе является единственной большой проблемой, стоящей перед проектировщиками и пользователями программного обеспечения. По иронии, его речь была произнесена спустя два дня после провалившейся попытки показать нескольким тысячам очень толковых программистов, как программировать разработанный Microsoft интерфейс OLE 2.0 — один из самых сложных интерфейсов прикладного программирования, когда-либо мной виденных. (OLE означает "связь и внедрение объекта". Стандарт OLE 2.0 определяет интерфейс, который может использоваться двумя программами для взаимодействия между собой определенным образом. Это действительно объектная ориентация на уровне операционной системы).

Предыдущий оратор, который убеждал нас пользоваться библиотекой Microsoft Foundation Classes (MFC), сказал нам, что поддержка OLE в MFC "включает 20000 строк кода, необходимых для каждого базового приложения OLE 2.0". Аудитория была ошеломлена не полезностью MFC, а тем фактом, что для написания базового приложения OLE 2.0 требуется 20000 строк кода. Любой интерфейс такой сложности таит в себе изъян. Следующие несколько правил используют OLE для показа характерных проблем, но не думайте, что проблема запутанности характерна лишь для Microsoft — она свойственна всей отрасли.

^ 2.1. Не решайте проблем, которых не существует

2.2. Решайте конкретную проблему, а не общий случай

Поучительно использовать OLE 2.0 как пример того, что случается со многими слишком сложными проектами. Имеется две главные причины сложности интерфейса OLE. Во-первых, он безуспешно пытается быть независимым от языка программирования. Идея таблицы виртуальных функций Си++ является центральной для OLE 2.0. Спецификация OLE даже пользуется нотацией классов Си++ для документирования того, как должны работать различные интерфейсы OLE. Для реализации OLE на другом языке программирования (не Си++) вы должны имитировать на этом языке таблицу виртуальных функций Си++, что фактически ограничивает ваш выбор Си++, Си или языком ассемблера (если вы не разработчик компиляторов, который может добавить к выбранному вами языку нужные свойства). Если честно, то вы должны быть сумасшедшим, чтобы программировать OLE не на Си++; потребуется гораздо меньше времени на изучение Си++, чем на написание имитатора Си++. То есть, эта идея независимости от языка программирования является неудачной. Интерфейс мог бы быть существенно упрощен за счет отказа от нее.

Возвращаясь к истории из предыдущего раздела, нужно заметить, что библиотека MFC в действительности решает проблему сложности, связанную с OLE, при помощи простого и легко понятного интерфейса, реализующего все возможности, нужные для большинства приложений OLE 2.0. Тот факт, что никто не хотел программировать с использованием OLE, пока для этого не появилась оболочка на основе MFC, впечатляет. Разработка хорошей оболочки вокруг плохого интерфейса не может быть решением лежащей в основе проблемы.

Если оболочка с использованием MFC столь проста, то почему лежащий в основе пласт так сложен? Ответ на этот вопрос является основным предметом проектирования. Создатели интерфейса OLE никогда не задавали себе два основных вопроса:

Какие основные возможности должно поддерживать настоящее приложение?

Как реализовать эти возможности простейшим способом?

Другими словами, они имели в виду не реальное приложение, когда они проектировали этот интерфейс, а какой-то теоретический худший случай. Они реализовали самый общий интерфейс из возможных, не думая о том, что на самом деле предполагается делать при помощи этого интерфейса, и получив в результате систему, которая может делать все, но при этом слишком сложная, чтобы быть пригодной для использования. (Вероятно, они совсем не пробовали реализовать этот интерфейс в каком-либо приложении, иначе они бы обнаружили эти проблемы).

Процесс объектно-ориентированного проектирования является в какой-то мере попыткой решения этой проблемы. Относительно просто добавить новую возможность в объектно-ориентированную систему или посредством наследования, или добавив новых обработчиков сообщений к существующим классам. Скрывая определения данных от пользователя класса, вы оставляете за собой право полностью менять внутреннюю организацию класса, включая определения данных, не беспокоя пользователей этого класса, при условии, что вы сохраняете его существующий интерфейс.

В структурном проектировании вам не нужна такая роскошь. Вы обычно проектируете сперва структуры данных, и модификация структуры данных является серьезным делом, потому что нужно проверить каждую подпрограмму, использующую эту структуру данных, чтобы убедиться в том, что она еще работает. Как следствие, "структурные" программы склонны иметь много ничего не делающего кода. Это потому, что кто-нибудь может захотеть воспользоваться некой возможностью в будущем. На деле многие проектировщики структурных программ горды своей способностью предсказывать направление, в котором может развиваться программа. Все это приводит к большому объему ненужной работы и программам, имеющим больший размер, чем необходимо.

Вместо того, чтобы учитывать в проекте все возможные случаи, проектируйте свой код так, чтобы он мог быть легко расширен при необходимости добавления новых возможностей. Объектно-ориентированные проекты, как правило, тут работают лучше.

^ 3. Интерфейс пользователя не должен быть похожим на компьютерную программу (принцип прозрачности)

Я однажды слышал, как кто-то сказал, что лучшим пользовательским интерфейсом из когда-либо разработанных является карандаш. Его назначение тотчас же понятно, для него не нужно руководство пользователя, он готовится к работе без особой суеты. Однако наиболее важным свойством является прозрачность. Когда вы пользуетесь карандашом, то думаете о том, что вы пишите, а не о самом карандаше.

Подобно карандашу, лучшими компьютерными интерфейсами являются те, которые скрывают сам факт того, что вы обращаетесь к компьютеру: замечательный пример — интерфейс с системой зажигания вашего автомобиля. Вы поворачиваете зажигание, включаете скорость и жмете на газ, как если бы все эти объекты интерфейса (ключ, рычаг скоростей, педаль) были прицеплены прямо на двигатель. Тем не менее, это не так: они теперь обычно просто устройства ввода в компьютер, который управляет двигателем.

К сожалению, подобный уровень ясности часто отсутствует в пользовательских интерфейсах. Представьте графический интерфейс пользователя Windows на автомобиле. Вы трогаетесь, выбрав в главном меню пункт "Движение автомобиля". Щелчок по нему откроет меню "Переключение скорости", которое предложит вам выбор из опций "Вперед", "Назад" и "Нейтральная". Щелкните по одной из них, чтобы передвинуть флажок на нужное вам направление. Затем вернитесь в меню "Движение автомобиля" и выберите команду "Поехали". Это вызовет появление диалогового окна "Скорость", где вы должны использовать ползунок для ввода желаемой скорости. Однако установить скорость правильно трудно вследствие высокого разрешения ползунка (пол-миллиметра движения мыши соответствует примерно 1 км/ч), поэтому вы скорее установите 59,7 км/ч вместо 60. Затем вы нажимаете кнопку "Поехали" в диалоговом окне, вслед за чем появляется сообщение "Стояночный тормоз не убран — нажмите F1 для справки" (динамик издает громкий звук). Вы покорно щелкаете по кнопке "ОК", чтобы убрать окно сообщений, затем снова пытаетесь открыть главное меню, но машина просто посылает вам звуковой сигнал. Наконец, поняв, что дело в том, что диалоговое окно "Скорость" еще отображается, вы щелкаете по кнопке "Отмена", чтобы убрать его. Вы открываете меню "Стояночный тормоз" и убираете флажок "Включен". Затем вы снова открываете окно "Поехали". И вновь получаете сообщение (и громкий звук) о том, что вы должны сначала выбрать направление в меню "Переключение скорости". В этот момент вы решаете, что вам, может быть, лучше пройтись на работу пешком.

Вот другой пример: занимаясь недавно подготовкой обзора, я просмотрел несколько программ авиационных бортовых журналов. ("Бортовой журнал" — это очень простой табличный документ. Каждая строка соответствует отдельному вылету, а столбцы разбивают общую продолжительность вылета на различные категории: итоговая продолжительность, продолжительность полета в облаках и т.п.. В других столбцах полет помечается как деловой и так далее).

Самый лучший интерфейс из всех был тот, который выглядел совершенно одинаково с привычным бумажным журналом, но автоматизировал нудную работу. Вы вводили время в "итоговый" столбец — и то же самое время появлялось в других подходящих по смыслу столбцах. Значения по столбцам складывались автоматически для получения итогов по категориям. Вы могли легко генерировать необходимые отчеты и экспортировать данные в формат ASCII с разделителями из символов табуляции, который читается любой электронной таблицей или текстовым редактором. Для непривычного взгляда весь интерфейс казался, мягко говоря, разочаровывающим, но он был функциональным и интуитивно понятным, а программа — маленькой и быстрой. Однако самым важным было то, что этот интерфейс выглядел как бортовой журнал, а не как программа для Windows.

Другой крайностью был ошеломляющий графический интерфейс пользователя Windows: у него были диалоговые окна; у него была трехмерная графика; вы могли генерировать круговые диаграммы, показывающие процент продолжительности полета в облаках по отношению к вашему общему налету на "Цесне–172" за последние 17 лет; вы могли помещать внутрь отсканированную фотографию самолета…— вы представили эту картину? Программа выглядела превосходно, но ее было почти невозможно использовать. Не было практической причины для создания большинства диаграмм и отчетов, которые она могла генерировать. Ввод данных был неудобный и медленный — вы должны были вызвать диалоговое окно с полями, разбросанными по всей его поверхности. Фактически вы должны были прочитать все, чтобы обнаружить ту категорию, которая вас интересовала, а некоторые из категорий были скрыты за кнопками, неизбежно влеча за собой сложный поиск. Чтобы добавить обиду к оскорблению, эта программа была надстроена над сервером реляционной базы данных (помните, что это для поддержки простой таблицы без реляционных связей). Она заняла 30 Мбайт на моем диске. Мне требовалось почти 5 минут, чтобы сделать запись, которая занимала примерно 10 секунд в бумажном бортовом журнале или упомянутом ранее простом графическом интерфейсе пользователя. Программа была бесполезна, но, конечно, потрясающа.

Одна из главных проблем заключалась в том, что инструменты, использованные для создания второй программы, перегружают проектирование интерфейса. Все эти программы были ра
еще рефераты
Еще работы по разное