Игнорирование статического контроля типов




Рассмотрим вариант 3, относящийся к проекту, в котором игнорируетсястатический контроль типов. Распространенные доводы в пользу отказана стадии проектирования от статического контроля типов сводятсяк тому, что "типы - это продукт языков программирования", или что"более естественно рассуждать об объектах, не заботясь о типах",или "статический контроль типов вынуждает нас думать о реализациина слишком раннем этапе". Такой подход вполне допустим до тех пор,пока он работает и не приносит вреда. Вполне разумно на стадиипроектирования не заботиться о деталях проверки типов, и частовполне допустимо на стадии анализа и начальных стадиях проектированияполностью забыть о вопросах, связанных с типами. В то же время,классы и иерархии классов очень полезны на стадии проектирования,в частности, они дают нам большую определенность понятий, позволяютточно задать взаимоотношения между понятиями и помогают рассуждатьо понятиях. По мере развития проекта эта определенность и точностьпреобразуется во все более конкретные утверждения о классах и ихинтерфейсах. Важно понимать, что точно определенные и строго типизированныеинтерфейсы являются фундаментальным средством проектирования. ЯзыкС++ был создан как раз с учетом этого. Строго типизированныйинтерфейс гарантирует, что только совместимые частипрограммы могут быть скомпилированы и скомпонованы воедино, и темсамым позволяет делать относительно строгие допущения об этих частях.Эти допущения обеспечиваются системой типов языка.В результате сводятся к минимуму проверки на этапевыполнения, что повышает эффективность и приводит к значительномусокращению фазы интеграции частей проекта, реализованных разнымипрограммистами. Реальный положительный опытинтеграции системы со строго типизированными интерфейсами привелк тому, что вопросы интеграции вообще не фигурируют среди основныхтем этой главы. Рассмотрим следующую аналогию: в физическом мире мы постоянносоединяем различные устройства, и существует кажущееся бесконечнымчисло стандартов на соединения. Главная особенность этих соединений:они специально спроектированы таким образом, чтобы сделать невозможнымсоединение двух устройств, нерассчитанных на него,то есть соединение должно быть сделано единственнымправильным способом. Вы не можете подсоединить электробритву крозетке с высоким напряжением. Если бы вы смогли сделать это, тосожгли бы бритву или сгорели сами. Масса изобретательности былапроявлена, чтобы добиться невозможности соединения двухнесовместимых устройств. Альтернативой одновременного использованиянескольких несовместимых устройств может послужить такое устройство,которое само себя защищает от несовместимых с ним устройств,подключающихся к его входу. Хорошим примером может служить стабилизаторнапряжения. Поскольку идеальную совместимость устройств нельзягарантировать только на "уровне соединения", иногда требуется болеедорогая защита в электрической цепи, которая позволяет в динамикеприспособиться или (и) защититься от скачков напряжения. Здесь практически прямая аналогия: статический контрольтипов эквивалентен совместимости на уровне соединения, а динамическиепроверки соответствуют защите или адаптации в цепи. Результатомнеудачного контроля как в физическом, так и в программном мире будетсерьезный ущерб. В больших системах используются оба вида контроля.На раннем этапе проектирования вполне достаточно простого утверждения:"Эти два устройства необходимо соединить"; но скоро становитсясущественным, как именно следует их соединить: "Какие гарантиидает соединение относительно поведения устройств?", или"Возникновение каких ошибочных ситуаций возможно?", или"Какова приблизительная цена такого соединения?" Применение "статической типизации" не ограничивается программныммиром. В физике и инженерных науках повсеместно распространеныединицы измерения (метры, килограммы, секунды), чтобы избежатьсмешивания несовместимых сущностей. В нашем описании шагов проектирования в $$11.3.3 типыпоявляются на сцене уже на шаге 2 (очевидно, после несколькоискусственного их рассмотрения на шаге 1) и становятся главнойтемой шага 4. Статически контролируемые интерфейсы - этоосновное средство взаимодействия программных частей системына С++, созданных разными группами, а описание интерфейсов этихчастей (с учетом точных определений типов) становится основнымспособом сотрудничества между отдельными группами программистов.Эти интерфейсы являются основным результатом процесса проектированияи служат главным средством общения между разработчиками ипрограммистами. Отказ от этого приводит к проектам, в которых неяснаструктура программы, контроль ошибок отложен на стадиювыполнения, которые трудно хорошо реализовать на С++. Рассмотрим интерфейс, описанный с помощью "объектов",определяющих себя самостоятельно. Возможно, например, такое описание:"Функция f() имеет аргумент, который должен быть самолетом"(что проверяется самой функцией во время ее выполнения), в отличиеот описания "Функция f() имеет аргумент, тип которого есть самолет"(что проверяется транслятором). Первое описание является существеннонедостаточным описанием интерфейса, т.к. приводит к динамической проверкевместо статического контроля. Аналогичный вывод из примера ссамолетом сделан в $$1.5.2. Здесь использованы более точныеспецификации, и использован шаблон типа и виртуальные функции взаменнеограниченных динамических проверок для того, чтобы перенестивыявление ошибок с этапа выполнения на этап трансляции. Различиевремен работы программ с динамическим и статическим контролемможет быть весьма значительным, обычно оно находится в диапазонеот 3 до 10 раз. Но не следует впадать в другую крайность. Нельзя обнаружитьвсе ошибки с помощью статического контроля. Например, дажепрограммы с самым обширным статическим контролем уязвимы к сбоямаппаратуры. Но все же, в идеале нужно иметь большое разнообразиеинтерфейсов со статической типизацией с помощью типов из областиприложения, см. $$12.4. Может получиться, что проект, совершенноразумный на абстрактном уровне, столкнется с серьезнымипроблемами, если не учитывает ограничения базовых средств, вданном случае С++. Например, использование имен, а не типов дляструктурирования системы приведет к ненужным проблемам длясистемы типов С++ и, тем самым, может стать причиной ошибок инакладных расходов при выполнении. Рассмотрим три класса: class X { // pseudo code, not C++ f() g() } class Y { g() h() } class Z { h() f() } используемые некоторыми функциями бестипового проекта: k(a, b, c) // pseudo code, not C++ { a.f() b.g() c.h() } Здесь обращения X x Y y Z z k(x,y,z) // ok k(z,x,y) // ok будут успешными, поскольку k() просто требует, чтобы ее первыйпараметр имел операцию f(), второй параметр - операцию g(), атретий параметр - операцию h(). С другой стороны обращения k(y,x,z); // fail k(x,z,y); // fail завершатся неудачно. Этот пример допускает совершенно разумныереализации на языках с полным динамическим контролем (например,Smalltalk или CLOS), но в С++ он не имеет прямогопредставления, поскольку язык требует, чтобы общность типов былареализована как отношение к базовому классу. Обычно примеры,подобные этому, можно представить на С++, если записывать утвержденияоб общности с помощью явных определений классов, но это потребуетбольшого хитроумия и вспомогательных средств. Можно сделать,например, так: class F { virtual void f(); }; class G { virtual void g(); }; class H { virtual void h(); }; class X: public virtual F, public virtual G { void f(); void g(); }; class Y: public virtual G, public virtual H { void g(); void h(); }; class Z: public virtual H, public virtual F { void h(); void f(); }; k(const F& a, const G& b, const H& c) { a.f(); b.g(); c.h(); } main() { X x; Y y; Z z; k(x,y,z); // ok k(z,x,y); // ok k(y,x,z); // error F required for first argument k(x,z,y); // error G required for second argument } Обратите внимание, что сделав предположения k() о своих аргументахявными, мы переместили контроль ошибок с этапа выполнения на этаптрансляции. Сложные примеры, подобные приведенному, возникают,когда пытаются реализовать на С++ проекты, сделанные на основеопыта работы с другими системами типов. Обычно это возможно,но в результате получается неестественная и неэффективная программа.Такое несовпадение между приемами проектирования и языкомпрограммирования можно сравнить с несовпадением при пословномпереводе с одного естественного языка на другой. Ведь английскийс немецкой грамматикой выглядит столь же неуклюже, как и немецкийс английской грамматикой, но оба языка могут быть доступныпониманию того, кто бегло говорит на одном из них. Этот пример подтверждает тот вывод, что классы в программе являютсяконкретным воплощением понятий, используемых при проектировании,поэтому нечеткие отношения между классами приводят к нечеткостиосновных понятий проектирования.

Гибридный проект

Переход на новые методы работы может быть мучителен для любойорганизации. Раскол внутри нее и расхождения между сотрудниками могутбыть значительными. Но резкий решительный переход, способный в одночасьепревратить эффективных и квалифицированных сторонников "старой школы"в неэффективных новичков "новой школы" обычно неприемлем. В то жевремя, нельзя достичь больших высот без изменений, азначительные изменения обычно связаны с риском. Язык С++ создавался с целью сократить такой риск за счетпостепенного введения новых методов. Хотя очевидно, что наибольшиепреимущества при использовании С++ достигаются за счет абстракцииданных, объектно-ориентированного программирования иобъектно-ориентированного проектирования, совершенно неочевидно,что быстрее всего достичь этого можно решительнымразрывом с прошлым. Вряд ли такой явный разрыв будет возможен,обычно стремление к усовершенствованиям сдерживается или должносдерживаться, чтобы переход к ним был управляемым. Нужно учитыватьследующее: - Разработчикам и программистам требуется время для овладения новыми методами. - Новые программы должны взаимодействовать со старыми программами. - Старые программы нужно сопровождать (часто бесконечно). - Работа по текущим проектам и программам должна быть выполнена в срок. - Средства, рассчитанные на новые методы, нужно адаптировать к локальному окружению.Здесь рассматриваются как раз ситуации, связанные с перечисленнымитребованиями. Легко недооценить два первых требования. Поскольку в С++ возможны несколько схем программирования,язык допускает постепенный переход на него, используяследующие преимущества такого перехода: - Изучая С++, программисты могут продолжать работать. - В окружении, бедном на программные средства, использование С++ может принести значительные выгоды. - Программы, написанные на С++, могут хорошо взаимодействовать с программами, написанными на С или других традиционных языках. - Язык имеет большое подмножество, совместимое с С. Идея заключается в постепенном переходе программиста страдиционного языка на С++: вначале он программирует на С++в традиционном процедурном стиле, затем с помощью методов абстракцииданных, и наконец, когда овладеет языком и связанными с ним средствами,полностью переходит на объектно-ориентированное программирование.Заметим, что хорошо спроектированную библиотеку использовать намногопроще, чем проектировать и реализовывать, поэтому даже с первых своихшагов новичок может получить преимущества, используя болееразвитые средства С++. Идея постепенного, пошагового овладения С++, а также возможностьсмешивать программы на С++ с программами, написанными на языках,не имеющих средств абстракции данных и объектно-ориентированногопрограммирования, естественно приводит к проекту, имеющемугибридный стиль. Большинство интерфейсов можно пока оставитьна процедурном уровне, поскольку что-либо более сложное непринесет немедленного выигрыша. Например, обращение к стандартнойбиблиотеке math из С определяется на С++ так: extern "C" { #include <math.h> } и стандартные математические функции из библиотеки можно использоватьтак же, как и в С. Для всех основных библиотек такое включениедолжно быть сделано теми, кто поставляет библиотеки, так чтопрограммист на С++ даже не будет знать, на каком языке реализованабиблиотечная функция. Использование библиотек, написанных на такихязыках как С, является первым и вначале самым важным способомповторного использования на С++. На следующем шаге, когда станут необходимы более сложныеприемы, средства, реализованные на таких языках как С или Фортран,представляются в виде классов за счет инкапсуляции структур данныхи функций в интерфейс классов С++. Простым примеромвведения более высокого семантического уровня за счет переходаот уровня процедур плюс структур данных к уровню абстракции данныхможет служить класс строк из $$7.6. Здесь за счет инкапсуляциисимвольных строк и стандартных строковых функций Сполучается новый строковый тип, который гораздо проще использовать. Подобным образом можно включить в иерархию классов любойвстроенный или отдельно определенный тип. Например, тип intможно включить в иерархию классов так: class Int: public My_object { int i; public: // definition of operations // see exercises [8]-[11] in section 7.14 for ideas // определения операций получаются в упражнениях [8]-[11] // за идеями обратитесь к разделу 7.14 }; Так следует делать, если действительно есть потребностьвключить такие типы в иерархию. Обратно, классы С++ можно представить в программе на С илиФортране как функции и структуры данных. Например: class myclass { // representation public: void f(); T1 g(T2); //... }; extern "C" { // map myclass into C callable functions: void myclass_f(myclass* p) { p->f(); } T1 myclass_g(myclass* p, T2 a) { return p->g(a); } //... }; В С-программе следует определить эти функции в заголовочном файлеследующим образом: // in C header file extern void myclass_f(struct myclass*); extern T1 myclass_g(struct myclass*, T2); Такой подход позволяет разработчику на С++, если у него уже естьзапас программ, написанных на языках, в которых отсутствуют понятияабстракции данных и иерархии классов, постепенно приобщаться к этимпонятиям, даже при том требовании, что окончательную версии программыможно будет вызывать из традиционных процедурных языков.

Классы

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


Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-16 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: