Голуб Ален И. - Веревка достаточной длины, чтобы... выстрелить с. Руководство по программированию. Автору удается сделать изложение столь серьезной темы живым и интересным за счет рассыпанного по тексту юмора и глубокого знания предмета
Скачать 1.36 Mb.
|
Часть 8б. ПРОБЛЕМЫ СЦЕПЛЕНИЯ ..................................................................... 171 117. Избегайте дружественных классов.................................................................... 171 118. Наследование — это форма сцепления ............................................................. 172 119. Не портьте область глобальных имен: проблемы Си++.................................. 173 Часть 8в. ССЫЛКИ ...................................................................................................... 177 120. Ссылочные аргументы всегда должны быть константами.............................. 177 121. Никогда не используйте ссылки в качестве результатов, пользуйтесь указателями .......................................................................................................... 177 122. Не возвращайте ссылки (или указатели) на локальные переменные ............. 181 123. Не возвращайте ссылки на память, выделенную оператором new ................ 181 Часть 8г. КОНСТРУКТОРЫ, ДЕСТРУКТОРЫ И OPERATOR=( ) ................... 183 124. Операция operator=( ) должна возвращать ссылку на константу .......... 184 125. Присваивание самому себе должно работать................................................... 184 126. Классы, имеющие члены-указатели, должны всегда определять конструктор копии и функцию operator=()................................................ 185 127. Если у вас есть доступ к объекту, то он должен быть инициализирован ...... 186 128. Используйте списки инициализации членов .................................................... 186 129. Исходите из того, что члены и базовые классы инициализируются в случайном порядке............................................................................................ 186 130. Конструкторы копий должны использовать списки инициализации членов 188 131. Производные классы должны обычно определять конструктор копии и функцию operator=( ) ........................................................................................... 189 132. Конструкторы, не предназначенные для преобразования типов, должны иметь два или более аргумента........................................................................... 192 133. Используйте счетчики экземпляров объектов для инициализации на уровне класса................................................................................................... 193 134. Избегайте инициализации в два приема ........................................................... 194 135. Суперобложки на Си++ для существующих интерфейсов редко хорошо работают ............................................................................................................... 194 Содержание 9 Часть 8д. ВИРТУАЛЬНЫЕ ФУНКЦИИ................................................................... 198 136. Виртуальные функции — это те функции, которые вы не можете написать на уровне базового класса ................................................................... 198 137. Виртуальная функция не является виртуальной, если вызывается из конструктора или деструктора............................................................................ 199 138. Не вызывайте чисто виртуальные функции из конструкторов ....................... 203 139. Деструкторы всегда должны быть виртуальными ........................................... 203 140. Функции базового класса, имеющие то же имя, что и функции производного класса, обычно должны быть виртуальными ............................ 204 141. Не делайте функцию виртуальной, если вы не желаете, чтобы производный класс получил контроль над ней ................................................. 205 142. Защищенные функции обычно должны быть виртуальными ......................... 205 143. Опасайтесь приведения типов (спорные вопросы Си++) ................................ 206 144. Не вызывайте конструкторов из операции operator=( ) .......................... 208 Часть 8е. ПЕРЕГРУЗКА ОПЕРАЦИЙ....................................................................... 211 145. Операция — это сокращение (без сюрпризов) ................................................. 211 146. Используйте перегрузку операций только для определения операций, имеющих аналог в Си (без сюрпризов).............................................................. 212 147. Перегрузив одну операцию, вы должны перегрузить все сходные с ней операции................................................................................................................ 213 148. Перегруженные операции должны работать точно так же, как они работают в Си ....................................................................................................... 214 149. Перегруженной бинарной операции лучше всего быть встроенным (inline) псевдонимом операции приведения типа ........................ 215 150. Не теряйте разум с операторами преобразования типов ................................. 217 151. Если можно, то делайте все преобразования типов с помощью конструкторов....................................................................................................... 217 Часть 8ж. УПРАВЛЕНИЕ ПАМЯТЬЮ..................................................................... 219 152. Используйте new/delete вместо malloc()/free() ............................... 219 153. Вся память, выделенная в конструкторе, должна быть освобождена в деструкторе ........................................................................................................ 219 154. Локальные перегрузки операторов new и delete опасны ............................ 219 Часть 8з. ШАБЛОНЫ................................................................................................... 220 155. Используйте встроенные шаблоны функций вместо параметризированных макросов......................................................................... 220 156. Всегда знайте размер шаблона после его расширения .................................... 221 157. Шаблоны классов должны обычно определять производные классы............ 224 158. Шаблоны не заменяют наследование; они его автоматизируют..................... 224 Часть 8и. ИСКЛЮЧЕНИЯ .......................................................................................... 228 159. Назначение исключений — не быть пойманными ........................................... 228 160. По возможности возбуждайте объекты типа error ....................................... 231 161. Возбуждение исключений из конструктора ненадежно .................................. 233 ЗАКЛЮЧЕНИЕ ............................................................................................................. 240 ОБ АВТОРЕ.................................................................................................................... 241 Благодарности Работа над этой книгой затянулась, и я весьма обязан трем редакторам издательства McGraw-Hill, которые по очереди мирились с постоянными задержками с моей стороны: Нэйлу Ливайну, Дэну Гонно и Дженифер Холт-Диджованна. Я особенно признателен Бобу Дюшарму, который защитил меня от самого себя, сделав очень тщательный просмотр первоначального наброска. Его советы значительно улучшили книгу в ее нынешнем виде. Введение Название этой книги отражает то, что я считаю основной трудностью при работе как с Си++, так и с Си: эти языки дают вам столько гибкости, что если у вас нет желания и способности призвать себя к порядку, то в итоге вы можете получить гигантский модуль не поддающейся сопровождению тарабарщины, притворяющейся к тому же компьютерной программой. Вы можете поистине делать все при помощи этих языков, даже если вы этого не хотите. В этой книге делается попытка дать средство для преодоления этой трудности в виде собрания практических правил программирования на Си++ и Си — правил, которые, надеюсь, уберегут вас от неприятностей, если вы будете их использовать с самого начала. Хотя большинство из приводимых здесь правил применимы равно при программировании как на Си, так и на Си++, я включил много материала, относящегося лишь к миру Си++ и сконцентрированного по мере возможности в заключительном разделе. Если вы программируете лишь на Си, то просто игнорируйте материал по Си++, встречающийся вам в более ранних разделах. Я профессионально занимаюсь программированием примерно с 1979 года и ежедневно пользуюсь правилами из этой книги. Я не утверждаю, что эти правила безусловны, или даже "верны". Однако я могу сказать, что они отлично мне служили все это время. Хотя эта книга не относится к категории путеводителей по "ловушкам и рытвинам", многие из этих правил предохранят вас от неприятностей того сорта, который обсуждается в путеводителях по "ловушкам и рытвинаì". Практические правила по своей сути гибки. Они постепенно меняются с ростом опыта, и ни одно правило не действует постоянно. Тем не менее я предупреждаю вас с самого начала, что мое мнение относительно этого ìàòåðèàëà самое наилучшее и что я не очень симпатизирую неряшливым мыслям или небрежному программированию. Я не извиняюсь за усиленное подчеркивание тех вещей, в которые я сильно верю. Мои мнения всегда могут измениться, если, конечно, вы сможете убедить меня Правила программирования на Си и Си++ 12 в том, что я не прав, но имейте в виду, что эта книга основана на опыте, а не на теории. Я сознаю, что большая часть этой книги подходит опасно близко к чьему-то культу и многие вещи, произносимые мной, дискуссионны, но думаю, что всегда имеется возможность разумного разговора двух людей, объединенных целью совершенствования своего мастерства. Я часто читаю курсы по Си++ и объектно-ориентированному проектированию как по приглашению частных фирм, так и в Калифорнийском университете в Беркли. Эта книга появилась в ответ на просьбы моих студентов, большинство из которых увлеченные профессионалы с настоящим желанием изучить этот материал. Я вижу множество программ в процессе проверки домашних заданий, и эти программы достаточно репрезентативны в качестве произведений сообщества профессиональных программистов из района залива Сан- Франциско. К несчастью, каждый семестр я также вижу, что одни и те же проблемы повторяются снова и снова. Поэтому эта книга является некоторым образом и списком распространенных проблем, найденных мной в созданных настоящими программистами реальных программах, сопровождаемым моими советами по их решению. Обсуждаемые здесь проблемы программирования и проектирования не ограничиваются, к несчастью, лишь ученическими программами. Многие из примеров того, что не следует делать, взяты из коммерческого продукта: библиотеки классов 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 Введение 13 Cummings,1994) ♣ О нумерации правил: иногда я группировал некоторые правила вместе, потому что удобно описывать их все одновременно. В этом случае все эти правила (имеющие различные номера) располагаются в начале раздела. Я использовал запись номера правила вида "1.2" в случаях, когда оно является особым случаем другого правила. ♣ Буч Г. Объектно–ориентированный анализ и проектирование с примерами приложений на С++, 2–е изд./Пер. с англ.—М.; СПб.: "Издательство БИНОМ" — "Невский диалект", 1998.—560 с.—Прим. перев. Часть 1 Процесс проектирования Эта часть вместе с последующей, посвященной разработке, являются наиболее туманными в этой книге. Правила здесь довольно общего характера по своей природе, они совсем не затрагивают техники программирования на Си или Си++, а скорее рассматривают более общий процесс проектирования и разработки программы. Правила из данной части относятся к процессу общего проектирования. После прочтения этой части в законченном виде я стал беспокоиться, что многие из этих правил будут казаться банальными. Несмотря на это, некоторые из приводимых здесь правил являются самыми важными в этой книге, потому что нарушение их может вызвать много бед в процессе разработки. В известном смысле, большинство правил этой части предназначены для управленцев; программисты их часто знают, но у них нет свободы, необходимой, чтобы воспользоваться своими знаниями. Процесс проектирования 15 1. Сущность программирования: без сюрпризов, минимум сцепления и максимум согласованности Многие (если не все) правила в этой книге могут быть объединены в три метаправила (при желании), выраженные в заголовке этого раздела. Правило "без сюрпризов" не требует пояснений само по себе. Пользовательский интерфейс должен действовать так, как кажется он должен действовать. Функция или переменная должны делать то, что означают их имена. Сцепление — это связь между двумя программами или объектами пользовательского интерфейса. Когда один объект меняется, то все, с чем он соединен, может также измениться. Сцепление вызывает сюрпризы. (Я меняю эту штучку здесь, и внезапно та штуковина вон там перестает работать). Пример из Си++: если объект одного класса посылает сообщение объекту второго класса, то посылающий класс сцеплен с принимающим классом. Если вы меняете интерфейс для принимающего класса, то вы также должны исследовать код в посылающем классе, чтобы убедиться в том, что он еще работает. Этот вид слабого сцепления безвреден. Вам нужно знать об отношениях сцепления для сопровождения программы, но без некоторого количества сцеплений программа не могла бы работать. Несмотря на это, для вас желательно по мере возможности минимизировать число отношений сцепления. Эта минимизация обычно выполняется в Си посредством модулей, а в Си++ посредством классов. Функции в модуле (функции-члены в классе) сцеплены друг с другом, но за исключением нескольких интерфейсных функций (или объектов) они вовсе не сообщаются с внешним миром. В Си вы должны использовать статический класс памяти, чтобы ограничить использование функции одним модулем. В Си++ вы используете закрытые функции-члены. Согласованность является противоположностью сцепления; сущности, которые группируются вместе (пункты диалогового и простого меню, функции в модуле, или члены класса), должны быть связаны по назначению. Отсутствие связности также является "сюрпризом". У текстового редактора, которым я пользуюсь, имеется в меню пункт "Настройка" и, кроме того, дополнительные опции настройки рассыпаны по четырем другим всплывающим меню. Я ожидал согласованной конфигурации и, когда не смог найти нужную мне опцию в пункте "Настройка", то решил, что этой опции просто нет. Эта плохо спроектированная система до сих пор доставляет беспокойство; после года пользования я по-прежнему не помню, где расположена каждая опция, и часто вынужден тратить раздражающие пять минут на поиск в пяти разных Правила программирования на Си и Си++ 16 местах того, что хотел изменить. По отношению к исходному коду отсутствие согласованности заставляет вас делать то же самое — тратить свою жизнь на поиск объявлений функций в 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 даже пользуется нотацией классов Си++ для документирования того, как Процесс проектирования 17 должны работать различные интерфейсы OLE. Для реализации OLE на другом языке программирования (не Си++) вы должны имитировать на этом языке таблицу виртуальных функций Си++, что фактически ограничивает ваш выбор Си++, Си или языком ассемблера (если вы не разработчик компиляторов, который может добавить к выбранному вами языку нужные свойства). Если честно, то вы должны быть сумасшедшим, чтобы программировать OLE не на Си++; потребуется гораздо меньше времени на изучение Си++, чем на написание имитатора Си++. То есть, эта идея независимости от языка программирования является неудачной. Интерфейс мог бы быть существенно упрощен за счет отказа от нее. Возвращаясь к истории из предыдущего раздела, нужно заметить, что библиотека MFC в действительности решает проблему сложности, связанную с OLE, при помощи простого и легко понятного интерфейса, реализующего все возможности, нужные для большинства приложений OLE 2.0. Тот факт, что никто не хотел программировать с использованием OLE, пока для этого не появилась оболочка на основе MFC, впечатляет. Разработка хорошей оболочки вокруг плохого интерфейса не может быть решением лежащей в основе проблемы. Если оболочка с использованием MFC столь проста, то почему лежащий в основе пласт так сложен? Ответ на этот вопрос является основным предметом проектирования. Создатели интерфейса OLE никогда не задавали себе два основных вопроса: • Какие основные возможности должно поддерживать настоящее приложение? • Как реализовать эти возможности простейшим способом? Другими словами, они имели в виду не реальное приложение, когда они проектировали этот интерфейс, а какой-то теоретический худший случай. Они реализовали самый общий интерфейс из возможных, не думая о том, что на самом деле предполагается делать при помощи этого интерфейса, и получив в результате систему, которая может делать все, но при этом слишком сложная, чтобы быть пригодной для использования. (Вероятно, они совсем не пробовали реализовать этот интерфейс в каком-либо приложении, иначе они бы обнаружили эти проблемы). Процесс объектно-ориентированного проектирования является в какой- то мере попыткой решения этой проблемы. Относительно просто добавить новую возможность в объектно-ориентированную систему или посредством наследования, или добавив новых обработчиков сообщений к существующим классам. Скрывая определения данных от пользователя класса, вы оставляете за собой право полностью менять внутреннюю организацию класса, включая определения данных, не беспокоя Правила программирования на Си и Си++ 18 пользователей этого класса, при условии, что вы сохраняете его существующий интерфейс. В структурном проектировании вам не нужна такая роскошь. Вы обычно проектируете сперва структуры данных, и модификация структуры данных является серьезным делом, потому что нужно проверить каждую подпрограмму, использующую эту структуру данных, чтобы убедиться в том, что она еще работает. Как следствие, "структурные" программы склонны иметь много ничего не делающего кода. Это потому, что кто- нибудь может захотеть воспользоваться некой возможностью в будущем. На деле многие проектировщики структурных программ горды своей способностью предсказывать направление, в котором может развиваться программа. Все это приводит к большому объему ненужной работы и программам, имеющим больший размер, чем необходимо. Вместо того, чтобы учитывать в проекте все возможные случаи, проектируйте свой код так, чтобы он мог быть легко расширен при необходимости добавления новых возможностей. Объектно- ориентированные проекты, как правило, тут работают лучше. 3. Интерфейс пользователя не должен быть похожим на компьютерную программу (принцип прозрачности) Я однажды слышал, как кто-то сказал, что лучшим пользовательским интерфейсом из когда-либо разработанных является карандаш. Его назначение тотчас же понятно, для него не нужно руководство пользователя, он готовится к работе без особой суеты. Однако наиболее важным свойством является прозрачность. Когда вы пользуетесь карандашом, то думаете о том, что вы пишите, а не о самом карандаше. Подобно карандашу, лучшими компьютерными интерфейсами являются те, которые скрывают сам факт того, что вы обращаетесь к компьютеру: замечательный пример — интерфейс с системой зажигания вашего автомобиля. Вы поворачиваете зажигание, включаете скорость и жмете на газ, как если бы все эти объекты интерфейса (ключ, рычаг скоростей, педаль) были прицеплены прямо на двигатель. Тем не менее, это не так: они теперь обычно просто устройства ввода в компьютер, который управляет двигателем. К сожалению, подобный уровень ясности часто отсутствует в пользовательских интерфейсах. Представьте графический интерфейс пользователя Windows на автомобиле. Вы трогаетесь, выбрав в главном меню пункт "Движение автомобиля". Щелчок по нему откроет меню "Переключение скорости", которое предложит вам выбор из опций "Вперед", "Назад" и "Нейтральная". Щелкните по одной из них, чтобы Процесс проектирования 19 передвинуть флажок на нужное вам направление. Затем вернитесь в меню "Движение автомобиля" и выберите команду "Поехали". Это вызовет появление диалогового окна "Скорость", где вы должны использовать ползунок для ввода желаемой скорости. Однако установить скорость правильно трудно вследствие высокого разрешения ползунка (пол- миллиметра движения мыши соответствует примерно 1 км/ч), поэтому вы скорее установите 59,7 км/ч вместо 60. Затем вы нажимаете кнопку "Поехали" в диалоговом окне, вслед за чем появляется сообщение "Стояночный тормоз не убран — нажмите F1 для справки" (динамик издает громкий звук). Вы покорно щелкаете по кнопке "ОК", чтобы убрать окно сообщений, затем снова пытаетесь открыть главное меню, но машина просто посылает вам звуковой сигнал. Наконец, поняв, что дело в том, что диалоговое окно "Скорость" еще отображается, вы щелкаете по кнопке "Отмена", чтобы убрать его. Вы открываете меню "Стояночный тормоз" и убираете флажок "Включен". Затем вы снова открываете окно "Поехали". И вновь получаете сообщение (и громкий звук) о том, что вы должны сначала выбрать направление в меню "Переключение скорости". В этот момент вы решаете, что вам, может быть, лучше пройтись на работу пешком. Вот другой пример: çанимаясь недавно подготовкой обзора, я просмотрел несколько программ авиационных бортовых журналов. ("Бортовой журнал" — это очень простой табличный документ. Каждая строка соответствует отдельному вылету, а столбцы разбивают общую продолжительность вылета на различные категории: итоговая продолжительность, продолжительность полета в облаках и т.п.. В других столбцах полет помечается как деловой и так далее). Самый лучший интерфейс из всех был тот, который выглядел совершенно одинаково с привычным бумажным журналом, но автоматизировал нудную работу. Вы вводили время в "итоговый" столбец — и то же самое время появлялось в других подходящих по смыслу столбцах. Значения по столбцам складывались автоматически для получения итогов по категориям. Вы могли легко генерировать необходимые отчеты и экспортировать данные в формат ASCII с разделителями из символов табуляции, который читается любой электронной таблицей или текстовым редактором. Для непривычного взгляда весь интерфейс казался, мягко говоря, разочаровывающим, но он был функциональным и интуитивно понятным, а программа — маленькой и быстрой. Однако самым важным было то, что этот интерфейс выглядел как бортовой журнал, а не как программа для Windows. Другой крайностью был ошеломляющий графический интерфейс пользователя Windows: у него были диалоговые окна; у него была трехмерная графика; вы могли генерировать круговые диаграммы, Правила программирования на Си и Си++ 20 показывающие процент продолжительности полета в облаках по отношению к вашему общему налету на "Цесне–172" за последние 17 лет; вы могли помещать внутрь отсканированную фотографию самолета…— вы представили эту картину? Программа выглядела превосходно, но ее было почти невозможно использовать. Не было практической причины для создания большинства диаграмм и отчетов, которые она могла генерировать. Ввод данных был неудобный и медленный — вы должны были вызвать диалоговое окно с полями, разбросанными по всей его поверхности. Фактически вы должны были прочитать все, чтобы обнаружить ту категорию, которая вас интересовала, а некоторые из категорий были скрыты за кнопками, неизбежно влеча за собой сложный поиск. Чтобы добавить обиду к оскорблению, эта программа была надстроена над сервером реляционной базы данных (помните, что это для поддержки простой таблицы без реляционных связей). Она заняла 30 Мбайт на моем диске. Мне требовалось почти 5 минут, чтобы сделать запись, которая занимала примерно 10 секунд в бумажном бортовом журнале или упомянутом ранее простом графическом интерфейсе пользователя. Программа была бесполезна, но, конечно, потрясающа. Одна из главных проблем заключалась в том, что инструменты, использованные для создания второй программы, перегружают проектирование интерфейса. Все эти программы были разработаны на языке очень высокого уровня Visual Basic (который мне на самом деле не очень нравится, между прочим). Приложения, созданные при помощи таких построителей приложений, как Visual Basic (или Power Builder, или Delphi, или …) обычно имеют специфический внешний вид, который немедленно говорит вам, что за инструмент был использован для построения этого приложения. Проектировщику интерфейса некуда обратиться за помощью, если этот специфический вид не подходит для конкретного проекта. Пользователи генераторов приложений должны иметь их несколько на выбор, чтобы затем использовать тот, который лучше всего соответствует потребностям данного интерфейса. Несмотря на это, мой опыт показывает, что наиболее практические программы со временем (в конце концов) должны перенести, по меньшей мере, часть кода интерфейса на язык низкого уровня типа Си или Си++, поэтому важно, чтобы ваш генератор приложений был способен также использовать низкоуровневый код. Процесс проектирования 21 4. Не путайте легкость в изучении с легкостью в использовании Эта проблема когда-то касалась почти исключительно машин Macintosh, но Windows и здесь в последнее время выходит вперед. Компьютер Mac был спроектирован так, чтобы прежде всего быть простым в освоении. Положим, что тетушка Матильда Мак-Гиликатти часто заходила в компьютерный магазин, чтобы пользоваться их услугой по моментальной печати кулинарных рецептов. В итоге Матильда забирает компьютер домой и успешно вводит рецепты в течение нескольких месяцев. Теперь она хочет взять эти рецепты, проанализировать их химический состав и написать статью в научный журнал о коллоидных свойствах продуктов питания на основе альбумина. Доктор Мак-Гиликатти — хорошая машинистка, печатающая обычно около 100 слов в минуту, но эта ужасная мышь ее постоянно тормозит. Каждый раз, когда ее руки отрываются от клавиатуры, она теряет несколько секунд. Она пытается найти слово в своем документе и обнаруживает, что для этого должна открыть меню, ввести текст в диалоговое окно и щелкнуть по нескольким экранным кнопкам. В конце файла она должна явно указать утилите поиска возвратиться к его началу. (Ее версия редактора vi 15-летней давности позволяет выполнить все это при помощи двух нажатий клавиш — без необходимости отрываться от клавиатуры). Наконец, она обнаруживает, что на выполнение обычной работы — подготовки статьи в журнал — уходит в два раза больше времени, чем раньше, в основном из-за проблем с пользовательским интерфейсом. Ей не понадобилось руководство, чтобы пользоваться этой программой, — ну и что? Вернемся к примеру с карандашом из предыдущего параграфа. Очень трудно научиться пользоваться карандашом. У большинства детей это занимает несколько лет. (Вы могли бы возразить, что, судя по каракулям на рецептах, многие врачи этому так и не научились). С другой стороны, после того, как вы научились, карандашом пользоваться очень легко. Главная проблема здесь состоит в том, что для опытного пользователя часто требуется совершенно другой интерфейс, чем для начинающего. Дополнительная помощь типа "горячих" клавиш не решает эту проблему; старый неуклюжий интерфейс пользователя все еще мешает продуктивности, и нет особой разницы: откроете ли вы меню при помощи "горячей" клавиши, или мышью. Здесь проблема в самом меню. Правила программирования на Си и Си++ 22 5. Производительность может измеряться числом нажатий клавиш Интерфейс, требующий меньше нажатий клавиш (или других действий пользователя типа щелчков мышью), лучше того, который требует много нажатий для выполнения одной и той же операции, даже если такие виды интерфейсов обычно сложнее в освоении. Подобным образом пользовательскими интерфейсами, скрывающими информацию в меню или за экранными кнопками, обыкновенно труднее пользоваться, потому что для выполнения одной задачи необходимо выполнить несколько операций (вызвав подряд несколько ñïóñêàþùèõñÿ меню). Хороший пример — настройка программы. Во многих из используемых мной ежедневно программ опции настройки рассыпаны по нескольким меню. То есть для вызова диалогового окна, настраивающего один из аспектов того, что делает программа (например, выбор шрифта), я должен выбрать одно меню. Затем я должен вызвать другое меню, чтобы сделать что-то в том же духе (например, выбрать цвет). Лучше поместить все опции настройки на одном экране и использовать форматирование экрана для объединения опций по назначению. 6. Если вы не можете сказать это по-английски, то вы не сможете выполнить это и на Си/Си++ Это правило с последующим также относятся к правилам пользовательского интерфейса, но здесь под "пользователем" уже понимается программист, использующий написанный вами код — часто это вы сами. Акт записи на английском языке описания того, что делает программа, и что делает каждая функция в программе, является критическим шагом в мыслительном процессе. Хорошо построенное, грамматически правильное предложение — признак ясного мышления. Если вы не можете это записать, то велика вероятность того, что вы не полностью продумали проблему или решение. Плохая грамматика и построение предложения являются также показателем небрежного мышления. Поэтому первый шаг в написании любой программы — записать то, что делает программа, и как она это делает. Есть разные мнения о возможности мышления вне языка, но я убежден, что аналитическое мышление того типа, который нужен в компьютерном программировании, тесно связано с языковыми навыками. Я не думаю, что является случайностью то, что многие из знакомых мне лучших программистов имеют дипломы по истории, филологии и схожим наукам. Процесс проектирования 23 Также не является случайностью то, что некоторые из виденных мной худших программ были написаны инженерами, физиками и математиками, затратившими в университете массу энергии на то, чтобы держаться как можно дальше от занятий по языку и литературе. Сущность заключается в том, что математическая подготовка почти не нужна в компьютерном программировании. Тот тип организационного мастерства и аналитических способностей, который нужен для программирования, связан полностью с гуманитарными науками. Логика, например, преподавалась на философском факультете, когда я был в университете. Процесс, используемый при проектировании и написании компьютерных программ, почти полностью идентичен тому, который используется, чтобы сочинять и писать книги. Процесс программирования совсем не связан с теми процессами, которые используются для решения математических уравнений. Здесь я делаю различие между информатикой (computer science) — математическим анализом компьютерных программ — и программированием или разработкой программного обеспечения — дисциплиной, интересующейся написанием компьютерных программ. Программирование требует организационных способностей и языковой подготовки, а не абстрактного мышления, необходимого для занятий математическим анализом. (В университете меня заставили проходить год на лекции по математическому анализу, но я никогда из него ничего не использовал ни на занятиях по информатике, хотя для них матанализ был необходимым условием, ни в реальной жизни). Я как-то получил открытую рецензию на книгу, посвященную мной предмету проектирования компиляторов, в которой рецензент (который преподавал в одном из ведущих университетов) заявил, что он "считает абсолютно неуместным включение исходного кода компилятора в книгу о проектировании компиляторов". По его мнению, необходимо учить "фундаментальным принципам" — лежащей в основе математике и теории языка, а детали реализации — "тривиальны". Первое замечание имеет смысл, если у вас создалось впечатление, что книга написана ученым- специалистом по информатике, а не программистом. Рецензент интересовался лишь анализом компилятора, а не тем как его написать. Второе замечание просто показывает вам, насколько изолировала себя научная элита от реального труда программирования. Интересно, что основополагающая работа по теории языка, сделавшая возможным написание компиляторов, была выполнена в Массачусетском технологическом институте лингвистом Наумом Хомским, а не математиком. Правила программирования на Си и Си++ 24 Обратной стороной этой медали является то, что если вы зашли в тупик при решении проблемы, один из лучших способов выйти из него — это объяснить проблему приятелю. Почти всегда решение возникает в вашей голове посредине объяснения. 6.1. Начинайте с комментариев Если вы последовали совету в предыдущем правиле, то комментарии для вашей программы уже готовы. Для того, чтобы получить документированное описание реализации, вы просто писали и добавляли вслед за каждым абзацем блоки кода, реализующие качества, описанные в этом абзаце. Оправдание "у меня не было времени, чтобы добавить комментарии" на самом деле означает "я писал этот код без проекта системы и у меня нет времени воспроизвести его". Если создатель программы не может воспроизвести проект, то кто же сможет? 7. Читайте код Все писатели — это читатели. Вы учитесь, когда смотрите, что делают другие писатели. Удивительно, но программисты — писатели на Си++ и Си — часто не читают код. Тем хуже. Я настоятельно рекомендую, чтобы, как минимум, члены группы программирования читали код друг друга. Читатель может найти ошибки, которые вы не увидели, и подать мысль, как улучшить код. Идея здесь — не формальная "критика кода", имеющая довольно сомнительный характер: никто не хочет наступать на ногу коллеге, поэтому шансы получить полезную обратную связь в формальной ситуации малы. Для вас лучше присесть с коллегой и просто разобрать код строка за строкой, объясняя что как делается и получая какую-то обратную связь и совет. Для того, чтобы подобное упражнение принесло пользу, автор кода не должен делать никаких предварительных пояснений. Читатель должен быть способен понимать код, читая его. (Нам всем приходилось иметь дело с учебниками, столь трудными для понимания, что ничего нельзя было понять без объяснения преподавателя. Хотя это и гарантирует, что преподаватель не останется без работы, но никак не отражается на авторе учебника). Если вам пришлось объяснять что-то вашему читателю, то это значит, что ваше объяснение должно было быть в коде в виде комментария. Добавьте этот комментарий, как только вы его произнесли; не откладывайте этого до окончания просмотра. Процесс проектирования 25 7.1. В цехе современных программистов нет места примадоннам Это следствие из правила чтения. Программисты, которые думают, что их код совершенен, которые отвергают критику, вместо того, чтобы считать ее полезной, и которые настаивают на том, что они должны работать втихомолку, вероятно, пишут тарабарщину, не поддающуюся сопровождению — даже если кажется, что она работает. (Смысловое ударение здесь на слове кажется). 8. Разбивайте сложные проблемы на задачи меньшего размера На самом деле это также правило и литературного стиля. Если концепцию слишком сложно объяснить за один раз, то разбейте ее на меньшие части и объясняйте каждую по очереди. То же назначение у глав в книге и параграфов в главе. Как пример, связанный с программированием, возьмем прошитое бинарное дерево, отличающееся от нормального дерева тем, что указатель на узел-потомок в концевом узле указывает на само дерево. Действительным преимуществом прошитого дерева является то, что его легко пересечь нерекурсивно при помощи этих дополнительных указателей. Проблема заключается в том, что сложно выйти из алгоритмов пересечения (в особенности обратного пересечения). С другой стороны, имея указатель на узел, легко написать алгоритм поиска последующего элемента в обратном порядке. Путем изменения формулировки с "выполнить пересечение в обратном порядке" на "начав с самого отдаленного узла, искать последующие элементы в обратном порядке, пока они не закончатся" получаем разрешимую задачу: tree t; // дерево node = postorder_first( t ); // исходный узел while ( node ) // есть еще узлы? node = postorder_successor( t ); // следующий узел-родитель 9. Используйте весь язык 9.1. Используйте для работы соответствующий инструмент Данное правило является спутником правила "Не путайте привычность с читаемостью", представленного ниже, но скорее больше касается проблем руководства. Мне часто говорят, что студентам не разрешается использовать некоторые части Си или Си++ (обычно это указатели), Правила программирования на Си и Си++ 26 потому что они "нечитаемы". Обычно это правило навязывается руководителями, знающими ФОРТРАН, БЕЙСИК или какой-то другой язык, не поддерживающий указатели, ибо их не очень-то заставишь изучать Си. Вместо того, чтобы допустить, что их знания недостаточны, такие руководители будут лучше калечить своих программистов. Указатели отлично читаемы для программистов на Си. И наоборот, я видел ситуации, где руководство требовало, чтобы программисты перешли с языка программирования типа КОБОЛ на Си, но не желало оплачивать переподготовку, необходимую для перехода. Или хуже, руководство платило за переподготовку, но не предоставляло времени, необходимого для действительного изучения материала. Переподготовка является занятием, требующим всего рабочего дня. Вы не можете одновременно выполнять "полезную" работу, а если попытаетесь, то ваши деньги будут выброшены на ветер. Так или иначе, после того, как руководители видят, что их ðàáîòíèêè не былè превращенû в гуру программирования на Си++ после 3-дневного краткого курса, они реагируют, накладывая ограничения на использование некоторых частей языка. Фактически говоря "вы не можете использовать ту часть Си++, которая не похожа на язык, который мы использовали до перехода на Си++". Естественно, что будет нельзя эксплуатировать ни одну из прогрессивных особенностей языка — которые прежде всего и являются главной причиной его использования — если вы ограничите себя "простейшим" подмножеством особенностей. Глядя на эти ограничения, мне в первую очередь интересно знать, зачем было менять КОБОЛ на Си. Принуждение программистов на языке КОБОЛ использовать Си всегда поражало меня своей большой глупостью. КОБОЛ — великолепный язык для работы с базами данных. У него есть встроенные примитивы, упрощающие выполнение задач, которые довольно трудны для Си. Си, в конце концов, был разработан для создания операционных систем, а не систем управления базами данных. Довольно просто дополнить КОБОЛ, чтобы он поддерживал модный графический интерфейс пользователя, если это единственная причина перехода на Си. 10. Проблема должна быть хорошо продумана перед тем, как она сможет быть решена Это правило с двумя последующими первоначально располагалось в начале этой главы. Подумав, я переместил их сюда, так как побоялся, что, прочитав их, вы пропустите оставшуюся часть главы. Однако в мои намерения не входит чтение проповедей. Эти правила посвящены весьма реальным проблемам и во многих отношениях являются самыми важными Процесс проектирования 27 правилами в этой книге. Настоящее правило является настолько очевидным утверждением в повседневной жизни, что кажется странным его восприятие как едва ли не ереси применительно к программированию. Мне часто говорят, что "невозможно потратить пять месяцев на проектирование, не написав ни одной строки кода — ведь наша производительность измеряется числом строк кода, написанных за день". Люди, говорящее это, обычно знают, как делается хороший проект; просто у них нет этой "роскоши". Мой опыт говорит, что хорошо спроектированная программа не только работает лучше (или просто работает), но и может быть написана быстрее и быть проще в сопровождении, чем плохо спроектированная. Лишние четыре месяца при проектировании могут сэкономить вам более четырех месяцев на этапе реализации и буквально годы в период сопровождения. Вам не добиться высокой производительности, если приходится выбрасывать прошлогоднюю работу из-за существенных изъянов проекта. Кроме того, скверно спроектированные программы труднее реализовать. Тот аргумент, что у вас нет времени на проектирование, потому что вы "должны захватить рынок программ как можно скорее", просто не выдерживает никакой критики, потому что реализация плохого (или никакого) проекта требует гораздо больше времени. 11. Компьютерное программирование является индустрией обслуживания Меня иногда шокирует неуважение, проявляемое некоторыми программистами по отношению к пользователям своих программ, как если бы "пользователь" (произносится с презрительной усмешкой) был низшей формой жизни, неспособной к познавательной деятельности. Но факт состоит в том, что весь компьютер существует лишь с одной целью: служить конечному пользователю наших продуктов. Если никто бы не пользовался компьютерными программами, то не было бы программистов. Печальным фактом является то, что существенно больше половины разрабатываемого ежегодно кода выбрасывается за ненадобностью. Такие программы или никогда не поступают в эксплуатацию, или используются лишь очень короткое время, после чего выбрасываются. Это означает невероятную потерю производительности, сокращая для большинства управляющих реальные среднесуточные цифры выработки. Подумайте о всех начинающих фирмах, выпускающих программы, которые никогда не будут проданы, о всех внутрифирменных группах разработчиков, пишущих бухгалтерские пакеты, которыми нельзя пользоваться. Правила программирования на Си и Си++ 28 Легко увидеть, как возникает эта печальная ситуация: программисты создают программы, которые никому не нужны. Исправить ее тоже легко, хотя это и сталкивается с неожиданными трудностями в некоторых условиях: спросите людей, что им нужно, и затем сделайте то, что они вам сказали. К сожалению, многие программисты производят впечатление полагаю- щих, что конечные пользователи не знают, чего хотят. Вздор! Почти всегда пользователи оказываются так запуганы сыплющим специальными терми- нами "экспертом", что замолкают. Мне часто говорили: "Я знаю, что мне нужно, но не могу это выразить". Лучший ответ на это: "Отлично, скажите это на нормальном языке — я сделаю перевод на компьютерный". 12. Вовлекайте пользователей в процесс проектирования 13. Заказчик всегда прав Ни одной программе не добиться успеха, если ее проектировщики не общаются непосредственно с ее конечными пользователями. Несмотря на это, часто ситуация больше напоминает игру ("испорченный телефон"), в которую многие из нас играли в детском саду и при которой 20 ребятишек садятся в кружок. Кто-нибудь шепчет фразу своему соседу (соседке), который передает ее своему, и так далее по кругу. Забава заключается в том, чтобы послушать, как сообщение звучит после того, как пройдет весь круг — обычно ничего похожего на исходную фразу. Тот же самый процесс часто встречается при разработке программ. Пользователь говорит с управляющим, докладывающим другому управляющему, который нанимает консультационную фирму. Президент консультационной фирмы разговаривает с руководителем разработчиков, который в свою очередь говорит со старшим группы, обращающимся, наконец, к программистам. Шансы на то, что даже простой документ с требованиями останется после этого процесса невредимым, равны нулю. Единственным решением этой проблемы является тесное вовлечение пользователей в процесс разработки, лучше всего путем включения, по крайней мере, одного конечного пользователя в команду разработчиков. Родственная ситуация складывается в случае простой самонадеянности части программистов, которые говорят: "Я знаю, что пользователи сказали, что им нужно сделать это таким способом, но у них нет достаточных знаний о компьютерах, чтобы принять сознательное решение; мой способ лучше". Такое отношение фактически гарантирует, что программой никогда не будут пользоваться. Исправить ситуацию здесь можно, официально назначив конечного пользователя лицом, оценивающим Процесс проектирования 29 качество проекта. Никто не может начать писать код до тех пор, пока пользователь-член команды не даст на это добро. Сотрудники, игнорирующие проект в пользу своих идей, должны быть уволены. В реальной жизни для подобного типа детского упрямства на самом деле нет места. При этом нужно сказать, что опытный проектировщик зачастую предлагает лучшее решение проблемы, чем то, что придумано конечным пользователем, в особенности, если учесть, что конечные пользователи часто предлагают интерфейсы, созданные по образцу программ, которыми они постоянно пользуются. Несмотря на это, вы должны убедить пользователя, что ваш способ лучше, перед тем, как его реализовать. "Лучший" интерфейс не является лучшим, если никто, кроме вас, не сможет (или не захочет) им пользоваться. 14. Малое — это прекрасно (большое == медленное) Распухание программ является огромной проблемой. Жесткий диск вместимостью 350 Мбайт на моем лэптопе может вместить операционную систему, усеченные версии моих компилятора и редактора и больше ничего. В стародавние времена я мог разместить версии для CP/M тех же программ на единственной дискете вместимостью 1,2 Мбайта. UNIX в то время спокойно работал на 16-разрядном PDP-11 c 64 Кбайтами ядра (внутренней памяти). В наше время большинство операционных систем требуют 32-разрядных машин с минимум 16 Мбайтами оперативной памяти, чтобы работать с приемлемой скоростью ♣ . Я убежден, что б î льшая часть этого распухания памяти является результатом небрежного программирования. В добавок к проблеме размера у вас также есть проблема со временем выполнения. Виртуальная память не является настоящей памятью. Если ваша программа слишком велика, чтобы поместиться в оперативной памяти, или если она выполняется одновременно с другими программами, то она должна периодически подкачиваться с диска. На эти подкачки, мягко выражаясь, расходуется время. Чем меньше программа, тем менее вероятно, что произойдет подкачка, и тем быстрее она будет выполняться. Третьей проблемой является модульность. Одна из основ философии UNIX гласит "меньше — лучше". Большие задачи лучше выполняются взаимодействующей системой маленьких модульных программ, каждая из которых делает хорошо лишь одно задание, но каждая из них может сообщаться с другими компонентами. (Стандарт связи и внедрения ♣ Óæå íå ðåäêîñòü åìêîñòü äèñêîâîé ïàìÿòè, ïðåâûøàþùàÿ ñïóñòÿ 5 ëåò óêàçàííûå àâòîðîì çíà÷åíèÿ íà äâà ïîðÿäêà, à îïåðàòèâíîé — íà ïîðÿäîê. — Ïðèì.ïåðåâ. Правила программирования на Си и Си++ 30 объектов Microsoft (OLE) добавляет это свойство в Windows, а OpenDoc — в Macintosh). Если ваше приложение представляет собой модульную конструкцию из маленьких программ, работающих вместе, то становится очень просто настраивать вашу программу по заказу путем смены модулей. Если вам не нравится этот редактор, то поменяйте его на новый. Наконец, программы обычно уменьшаются в процессе усовершенствования. Большие программы, вероятно, никогда не подвергались усовершенствованиям. Разыскивая решение этой проблемы, я заметил, что коллективы программистов с плохим руководством часто создают излишне большие программы. То есть группа ковбоев от программирования, каждый из которых работает в одиночку в своем офисе и не разговаривает с другèмè, напишет массу лишнего кода. Вместо одной версии простой служебной функции, используемой по всей системе, каждый программист создаст свою версию одной и той же функции. |