Правила программирования на С и С++Название этой статьи отражает то, что я считаю основной трудностью при работе как с С++, так и с С: эти языки дают вам столько гибкости, что если у вас нет желания и способности призвать себя к порядку, то в итоге вы можете получить гигантский модуль не поддающейся сопровождению тарабарщины, притворяющейся к тому же компьютерной программой. Вы можете поистине делать все при помощи этих языков, даже если вы этого не хотите. В этой книге делается попытка дать средство для преодоления этой трудности в виде собрания практических правил программирования на С++ и С - правил, которые, надеюсь, уберегут вас от неприятностей, если вы будете их использовать с самого начала. Хотя большинство из приводимых здесь правил применимы равно при программировании как на С, так и на С++, я включил много материала, относящегося лишь к миру С++ и сконцентрированного по мере возможности в заключительном разделе. Если вы программируете лишь на С, то просто игнорируйте материал по С++, встречающийся вам в более ранних разделах. Я профессионально занимаюсь программированием примерно с 1979 года и ежедневно пользуюсь правилами из этой книги. Я не утверждаю, что эти правила безусловны, или даже "верны". Однако я могу сказать, что они отлично мне служили все это время. Хотя эта книга не относится к категории путеводителей по "ловушкам и рытвинам", многие из этих правил предохранят вас от неприятностей того сорта, который обсуждается в путеводителях по "ловушкам и рытвинах". Практические правила по своей сути гибки. Они постепенно меняются с ростом опыта, и ни одно правило не действует постоянно. Тем не менее я предупреждаю вас с самого начала, что мое мнение относительно этого материла самое наилучшее и что я не очень симпатизирую неряшливым мыслям или небрежному программированию. Я не извиняюсь за усиленное подчеркивание тех вещей, в которые я сильно верю. Мои мнения всегда могут измениться, если, конечно, вы сможете убедить меня в том, что я не прав, но имейте в виду, что эта книга основана на опыте, а не на теории. Я сознаю, что большая часть этой книги подходит опасно близко к чьему-то культу и многие вещи, произносимые мной, дискуссионны, но думаю, что всегда имеется возможность разумного разговора двух людей, объединенных целью совершенствования своего мастерства. Я часто читаю курсы по С++ и объектно-ориентированному проектированию как по приглашению частных фирм, так и в Калифорнийском университете в Беркли. Эта книга появилась в ответ на просьбы моих студентов, большинство из которых увлеченные профессионалы с настоящим желанием изучить этот материал. Я вижу множество программ в процессе проверки домашних заданий, и эти программы достаточно репрезентативны в качестве произведений сообщества профессиональных программистов из района залива Сан-Франциско. К несчастью, каждый семестр я также вижу, что одни и те же проблемы повторяются снова и снова. Поэтому эта книга является некоторым образом и списком распространенных проблем, найденных мной в созданных настоящими программистами реальных программах, сопровождаемым моими советами по их решению. Обсуждаемые здесь проблемы программирования и проектирования не ограничиваются, к несчастью, лишь ученическими программами. Многие из примеров того, что не следует делать, взяты из коммерческого продукта: библиотеки классов Micrisoft Foundation Class(MFC) корпорации Micrisoft. Я могу сказать, что эта библиотека была спроектирована без заботы о хорошем сопровождении людьми, не подозревающими о существовании даже элементарных принципов объектно-ориентированного проектирования. Я не выделял явно большинство примеров этого в тексте, так как это не книга с названием "Что неправильно в 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). Предыдущий оратор, который убеждал нас пользоваться библиотекой Microsoft Foundation Class (MFC), сказал нам, что поддержка OLE в MFC "включает 20000 строк кода, необходимых для каждого базового приложения OLE 2.0". Аудитория была ошеломлена не полезностью MFC, а тем фактом, что для написания базового приложения OLE 2.0 требуется 20000 строк кода. Любой интерфейс такой сложности таит в себе изъян. Следующие несколько правил используют OLE для показа характерных проблем, но не думайте, что проблема запутанности характерна лишь для Microsoft - она свойственна всей отрасли. 2.1. Не решайте проблем, которых не существует. 2.2. Решайте конкретную проблему, а не общий случай. Возвращаясь к истории из предыдущего раздела, нужно заметить, что библиотека MFC в действительности решает проблему сложности, связанную с OLE, при помощи простого и легко понятного интерфейса, реализующего все возможности, нужные для большинства приложений OLE 2.0. Тот факт, что никто не хотел программировать с использованием OLE, пока для этого не появилась оболочка на основе MFC, впечатляет. Разработка хорошей оболочки вокруг плохого интерфейса не может быть решением лежащей в основе проблемы. Если оболочка с использованием MFC столь проста, то почему лежащий в основе пласт так сложен? Ответ на этот вопрос является основным предметом проектирования. Создатели интерфейса OLE никогда не задавали себе два основных вопроса:
Процесс объектно-ориентированного проектирования является в какой-то мере попыткой решения этой проблемы. Относительно просто добавить новую возможность в объектно-ориентированную систему или посредством наследования, или добавив новых обработчиков сообщений к существующим классам. Скрывая определения данных от пользователя класса, вы оставляете за собой право полностью менять внутреннюю организацию класса, включая определения данных, не беспокоя пользователей этого класса, при условии, что вы сохраняете его существующий интерфейс. В структурном проектировании вы не стараетесь иметь такую роскошь. Вы обычно проектируете сперва структуры данных, и модификация структуры данных является главным делом, потому что нужно проверить каждую подпрограмму, использующую эту структуру данных, чтобы убедиться в том, что она еще работает. Как следствие, "структурные" программы склонны иметь много ничего не делающего кода. Это потому, что кто-нибудь может захотеть воспользоваться некой возможностью в будущем. На деле многие проектировщики структурных программ горды своей способностью предсказывать направление, в котором может развиваться программа. Все это приводит к большому объему ненужной работы и программам, имеющим больший размер, чем необходимо. Вместо того, чтобы учитывать в проекте все возможные случаи, проектируйте свой код так, чтобы он мог быть легко расширен при необходимости добавления новых возможностей. Объектно-ориентированные проекты, как правило, тут работают лучше. |