Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 184
Текст из файла (страница 184)
Однако в больших проектах политика «нет наследованию» приводит к менее понятным и менее гибким системам, в которых наследование спмулируется при помощи других языковых и проектных конструкций. Более того, я подозреваю, что, посмотря на такую политику, наследование в конце концов все равно будет использоваться, поскольку программисты найдут убедительные аргументы в пользу применения основанных на наследовании проектных регпений в различных частях системы. Поэтому политика «нет наследованию > приведет лишь к тому, что будет утрачена связная оощая архитектура, и использование иерархий классов будет ограничено отдельными подсистемами. Иными словами, лержитс ум открытым.
Иерархии классов не являются необходимой частью всякой хорошей программы, но они могут помочь как в понимании приложения, так и в выражении решения задачн. Тот факт, что наследование может быть неправильно применено — зто лишь повод быть внимательным, а не причина для отказа. 24.2.3. Отказ от статической проверки типов Рассмотрим проект, который отказывается от статической проверки типов на этапе проектирования. Обычно это обосновывается следующим образом: «типы — это артефакт' языка программированпяь, «естественнее думать аб объектах, не беспокоясь о типахгч и «статическая проверка типов вынуждает нас слишком рано задумываться о вопросах реализациям Такая позиция хороша, пока все идет хорошо На такие мелочи, как проверка типа, можно не обращать внимания на стадии проектирования; на стадии анаяиза и ранних стадиях проектирования часто безопасно на шсто ипюрировать вопросы типов. Однако классы и иерархии классов при проектировании очень полезны.
В частности, они позволяют определиться в концепциях, уточнить их взаимосвязь и понять смысл, При продвижении проекта эта точность обретает форму все более точных высказываний относительно классов и их интерфейсов. Важно понять, что точно определенные н строго типизированные интерфейсы являются фундаментальным инструментом проектирования.
С++ проектировался с таким учетом. Строго типизированный интерфейс гарантирует (до известного предела), что компилируются и компонуются только совместимые части программ, таким образом позволяя этим частям делать довольно сильные предположения относительно друг друга. Выполнение этих предположений гарантируется системой типов. Эффект от этого проявляется в минимизации тестирования программы, что повышает эффективность н значительно упрощает фазу интеграции в проекте, над которым работало мнох«ество людей.
По сути дела, интеграция не стала главной темой данной главы лишь потому, что в интеграции систем со строго тнпизированцыл1и интерфейсами накоплен большой положительный опыт. ' То есть нечто искуственное, привнесевяое, обусловленное ие сутью вещей, а вашим подходом к пвм.— Прпввч.
ред. 24.2. Проектирование и язык программирования воз Рассмотрим аналогию. В физическом мире мы все время что-то стыкуем, и кажется, что число стандартов на разъемы бесконечно. Очевидно, разъемы специально спроектированы так, чтобы было невозможно соединить две части, если онп не предназначены для этого, и чтобы они соединялись только правильным образом. Вам не воткнуть низковольтную электробритву в высоковольтную сеть. Если бы вам это удалось, то результатом было бы либо обуглившаяся бритва, либо обуглившийся труп того, кто хотел побриться. Немало изобретательности потрачено на то, чтобы разные части аппаратуры не вставлялись одна в другую.
Альтернатива использованию несовместимых разъемов — детали, которые сами защищают себя от нежелательного поведения других деталей, воткнутых в их разьемы, Хорошим примером этого является предохранитель от выбросов напряжения. 11оскольку совершенную совместимость невозможно гарантировать на «уровне совместимости разъемов», порой нам требуется более сложная схема защиты, которая бы динамически подстраивалась для защиты от нескольких источников угрозы. Аналогия почти полная. Статическая проверка типа равносильна совместимости разъемов, а динамическая проверка относится к защите/подстройке цепей.
Есл„ обе проверки не сработали — как в физическом мире, так и в программном мире— может возникнуть серьезная авария. В больших системах используются обе формы проверок. На ранних стадиях проектирования можно просто сказать; «Эти две детали должны стыковаться». Однако как именно они должны стыковаться, становится относительно ясно лишь позже. Какие гарантии дает разъем относительно своего повеления? Какие условия приведут к возникновения ошибки? Каковы приблизительные опенки стоимости? Использование «статической типизации» не ограничивается физическим миром.
Использование единиц измерения (например, метров, килограммов и секунд), чтобы избежать смешения разнородных сущностей, широко распространено как в физике, так и в технике, Согласно схеме проектирования из у 23.4.3 вопросы о типе возникают на этапе 2 (предположительно после поверхностного рассмотрения на этапе 1) и становятся главными на этапе 4, Статически проверяемые интерфейсы — это основной способ гарантировать взаимодействие между программами на С++, разработанными разными группами. Документирование этих интерфейсов (включая точную информацию о типах) является основным способом общения между различными группзмн программистов. Такие интерфейсы являются одним из самых важных результатов процесса проектирования, и на них фокусируется общение между проектировщиками и программистами. Недостаточное внимание к вопросам типов при рассмотрении интерфейсов приводит к проектам с неясной структурой, а выявление оп~ибок откладывается до этапа выполнения программы.
Например, интерфейс может определяться в терминах самоидентифицирующихся объектов: //Приссер, предполагающий динамическую проверку типа, а не апатическую втаскв; //стек может содержать указатели па объектылюбого типа ооЫ/() Глава 24. Проектирование и программирование 804 з ризЬ (пеш ЯааЬ900); з.риз/с (пеш ЯааЬЗ 7В); зрор ()- !аяео/7 () // правильно; (1оьео~уознанаепс взлет) //ЗааЬ ЗЗ — зто самолет з.рор() — >!аЬеоЯ; //ошибка во время выполнения: // ивт ам о бил ь ЗааЬ900 не Влсеет лета т ь Здесь недостаточно строгое определение интерфейса (функции Иас/есризп ()) приводит к динамической проверке вместо статической. Стек з предназначался для хранения самолетов (р)апе), но это осталось неявным в коде, так что гарантировать выполнение этого требования становится обязанностью пользователя. Более точное определение — шаблон плюс виртуальные функции вместо ничем не ограниченной динамической проверки типов — переместит выявление ошибки на стадию компиляции: // опек может содержать только Вкизатели на самолета 3!асЬср1апе*> з; ооЫУ() ( //ошибка: ЪааЬ900 — не самолегп з.ризЬ (петю ЯааЬ900), з.ризЬ (пеш ЗааЬЗ 7В); е.рор () — !айео//(); дрор () — >!айеоЯ''в О правильно: ЗаоЬ З78 — зто самолет с1азз Р1ане ( //- оо!с! !игп г!яЬ! (); ); с!азз Саг ( //...
оо!с1!игп г!дЬ!(); Схожий пример приведен также в 9 16.2.2. Разница в затратах времени (выполнения) на динамическую и статическую проверку может быть значительной. Расходы на динамическую проверку по сравнению со статической обычно больше в 3 — 10 раз. Впрочем, пс надо впадать и в другую крайность. Статической проверкой невозможно выловить все ошибки.
Например, даже гсрозрамма с самой полной статической проверкой уязвима к сбоям аппаратной части. Посмотрите также пример из 9 25.4.1, где полная статическая проверка может оказаться недостижима. Однако нужно стремиться к тому, чтобы статически на уровне прикладной программы проверялось подавляющее большинство интерфейсов; 8 24 4.2. Другая проблема заключается в том, что проект может быть безупречен в абстракции, но вызывает серьезные проблемы из-за того, что не принимает в расчет ограничений, налагаемых основными средствами программирования, в данном случае— С++. Например, функция /(), которой нужно выполнить операцию 1игп г!Вй! () (повернуть вправо) над некоторым аргументом, может сделать это только при условии, что все ее аргументы принадлежат одному типу: 24.2.
Проектирование и язык программирования иопЦ~Х' р~// какая гпапая должен быпшХ? 1 р- Гига п~Ы~); //... ) Некоторые языки (такис как ЗшаИса1)г и С(.ОЯ) позволяют использовать два типа (если они имеют те же операции) взаимозаменяемым образом, обращаясь к каждому типу через общий подобъект базового класса и откладывая разрешение имени до выполнения программы. Однако С++ (преднамеренно) по~щерживает это понятие только через шаблоны и разрешение имен только во время компиляции. Функция (не шаблон) может принимать аргументы двух типов, только если они могут быть неявно преобразованы в общий тип.
Таким образом, в предыдущем примере Х должен быть общим базовым классом для Р!апе (самолет) и Саг(автомобиль) — например, классом Уеп1с1е (транспортное средство). Как правило, примеры, вдохновленные чуждыми С++ понятиями, гяолгно отобразить в С++, явно выразив соотвстствующис допущения. Например, имея Р!апе и Саг (без обьцего базового класса), мы можем по-прежнему создать иерархию классов, которая позволит нам передавать вЯХ*~ объекты, содержащие либо автомобили, либо самолеты Я 25 А.1). Однако для этого часто требуется много технических приемов и сообразительности.
Для отображения таких концепций в понятия С++ часто полезны шаблоны. Несоответствие между понятиями проекта и С++, как правило, ведет к енеестественному виду» кода и его неэффективности. Программисты, занимающиеся сопровождением, обычно недолюбливают неидиоматический код, возникающий из-за таких несоответствий. Несоответствие между техникой проектирования и языком реализации можно сравнить с дословны м переводом с одного человеческого языка на другой. Например, англ ийский язык с немецкой грамматикой также неуклюж, как немецкий с английской, и оба оказываются почти непонятны для того, кто хорошо владеет только одним из этих языков. Классы в программе — это конкретное представление проектных понятий.
Следовательно, затушевывая отношения между классами, мы делаем менее понятнымц фундаменталы|ые концепции проекта. 24.2.4. Отказ от программирования Программирование дорого и непредсказуемо по сравнению со многими другими видами деятельности, и полученная программа часто не на 100% надежна.
Программирование трудоемко, и — по многим причинам — многие серьезные проекты задерживаются из-за неготовности кода. Так почему бы программирование как род деятельности совсем не устранить из процесса? Многим руководителям кахсется, что избавление от нахальных, недисциплинированных, слишком высоко оплачиваемых, одержимых специальными терминами, несоответствующим образом одевающихся и т. и. программистов,' может принести значительную дополнительную прибыль. Для программистов ато может звучать абсурдно. Однако в некоторых важных областях в самом деле сугцествуют альтернативы тралиционному программированию. В некоторых прикладных областях можно сгенсриро- ' Да, я сам программист.