На головную страницу Института
Написано в 1998 г. English

Конфигурационные ориентиры
на пути к многократному использованию

М.М.Горбунов-Посадов

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

1. Введение

2. Везде ли пригоден класс?

3. Примеры

4. Многослоойность и односвязность

5. Реализация рассылки

6. Однородный набор

7. Заключение

Литература

1. Введение

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

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

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

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

Нередко многократно используется ствол этого дерева, или, иначе, каркас (framework), в гнездах которого размещаются вновь создаваемые элементы программы, определяющие конкретику приложения. Например, современная инструментальная среда обычно предполагает, что разработчик пишет свой код в рамках определенных интерфейсных соглашений. Непосредственное кодирование таких соглашений потребовало бы написать несколько рутинных строк на языке высокого уровня. Инструментальная среда выносит эти строки в каркас программы. Строки каркаса автоматически появляются на экране, предоставляя разработчику возможность сразу приступить к делу — начать заполнять гнездо, предназначенное для вариантной составляющей приложения.

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

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

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

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

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

2. Везде ли пригоден класс?

Если внедряемый класс достаточно богат, содержателен, то и интерфейс его обычно весьма нетривиален. Такой интерфейс заставляет "плясать под свою дудку" всю остальную программу, она вынуждена под него подлаживаться. Из-за нетривиальности интерфейсов иллюзорной представляется возможность компоновки крупной программы из независимо разработанных (т. е. ничего не знающих об интерфейсах друг друга) многократно используемых классов: эти классы, вероятнее всего, не смогут ужиться между собой.

Строго говоря, несогласованность интерфейсов можно преодолеть с помощью переходников ("смазывающих" подпрограмм, брокеров). Однако изготовление переходников — дело весьма трудоемкое и деликатное, да и структура программы тут превращается в труднопроходимые дикие заросли, и поэтому переходники нельзя всерьез воспринимать как регулярный инструмент поддержки многократного использования.

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

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

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

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

Определенные результаты в направлении приспособления класса и объекта для нужд многократного использования получила фирма Microsoft в своих разработках OLE, COM, ActiveX [2‑3]. Однако, к сожалению, речь там идет именно о приспособлении класса и объекта, иначе говоря, априорно ограничивается область поиска перспективных многократно используемых программных конструкций.

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

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

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

3. Примеры

Начнем с примеров, иллюстрирующих специфику многократного использования крупных компонентов.

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

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

Пример из области "железа" IBM PC. Здесь системный блок и клавиатура по сути связаны отношением "каркас-подпрограмма". Достаточно соединить клавиатурный кабель с предназначенным для него разъемом на тыльной стороне системного блока — и сборка завершена. Благодаря четкой функциональной специализации клавиатуры и унификации разъема (точнее, интерфейса) любой системный блок, вообще говоря, успешно соединяется с любой клавиатурой. Впрочем, при более пристальном рассмотрении клавиатура воспринимается скорее как объект класса "клавиатуры", где методам соответствуют многочисленные программно-аппаратные соглашения, обеспечивающие реальное, довольно-таки сложное взаимодействие с системным блоком.

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

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

Иначе говоря, устройство чтения компакт-дисков при подключении делегирует своих представителей в целый ряд слоев системного блока: слой "пятидюймовых дисководов", слой интерфейсного кабеля ("шлейфа"), слой прерываний и т. д. Отношения в каждом из слоев несколько проще, чем полный свод отношений в упомянутой выше паре "системный блок — клавиатура", но в совокупности слои позволяют организовать ни в чем не уступающее плотное взаимодействие устройства и системного блока.

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

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

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

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

Рис. 1. Подключение к компилятору нового компонента

Сведем наши наблюдения в таблицу.

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

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

4. Многослойность и односвязность

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

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

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

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

5. Реализация рассылки

Итак, вернемся к компилятору с расширяемого языка, точнее, к добавлению реализации новой языковой конструкции. Рассмотрим только одну небольшую подзадачу: как организовать рассылку по своим местам вновь появившихся зарезервированных слов? Будем предполагать, что компилятор написан на Си [4], и попытаемся дополнить этот язык средствами, поддерживающими расслоение компонента. Воспользуемся тем, что в Си уже имеется понятие препроцессирования, и включим в число директив препроцессора Си еще три: #Install_in, #Horizon и #End_of_horizon.

Пусть для определенности новая конструкция расширяемого языка содержит одно зарезервированное слово — newword. Тогда в тексте ее реализации предлагается записать строку

#Install_in  keyword  newword

где #Install_in — новая директива препроцессора Си, обеспечивающая включение второго операнда (newword) в набор зарезервированных слов, имя которого (keyword) задано первым операндом.

Элементы формируемого посредством директив #Install_in набора keyword будут востребованы по крайней мере в двух точках программы. Во-первых, в h-файле, разделяемом лексическим и синтаксическим анализатором, где всем зарезервированным словам присваиваются номера. Во-вторых, в таблице зарезервированных слов лексического анализатора.

Пусть константе перечисления, задающей номер зарезервированного слова, присваивается имя, получаемое конкатенацией префикса "w_" и нумеруемого слова. Тогда спецификатор перечисления, записываемый в h-файле и определяющий эти константы, будет иметь вид:

enum {

#Horizon  keyword

w_  ##  keyword

#End_of_horizon ,

};

Между строками #Horizon и  #End_of_horizon заключено наборное гнездо, представляющее собой цикл периода компиляции. Цикл повторяется столько раз, сколько содержится элементов в наборе, имя которого указано в директиве #Horizon. Имя набора служит кроме того в качестве переменной цикла, которая последовательно пробегает все элементы набора. Каждое повторение цикла приводит к добавлению в формируемый текст строк, заключенных между строками #Horizon и #End_of_horizon, где на место переменной цикла подставляется очередной элемент набора. В директиве #End_of_horizon указывается разделитель, записываемый между повторениями цикла. Напомним, что оператор ## в Си означает конкатенацию двух лексем (периода компиляции). Таким образом, наш спецификатор перечисления после работы препроцессора должен превратиться в

enum { ... , w_newword, ... };

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

char *words[] = {

#Horizon  keyword

#keyword ,

#End_of_horizon

};

Записанный в третьей строчке оператор # работает так же, как и в макроопределении Си, задавая заключение подставляемого на место переменной цикла текста в двойные кавычки. После обработки препроцессором таблица примет вид

char *words[] = { ... , "newword" , ... , };

где зарезервированные слова появляются строго в той же последовательности, что и константы в только что рассмотренном перечислении. Заметим, что в директиве #End_of_horizon отсутствует операнд: запятая перебралась в тело цикла, здесь она использована в роли завершителя, а не разделителя, что допускается синтаксисом Си.

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

Реализация директив #Install_in, #Horizon и #End_of_horizon несколько отличается от традиционной схемы работы препроцессора. Перед тем как начать расширение наборного гнезда #Horizon ... #End_of_horizon, нужно выявить элементы указанного в нем набора. А для этого придется предварительно проанализировать все включаемые в строящуюся программу исходные тексты и выполнить расположенные там директивы #Install_in. Такой же анализ надо провести и по интерактивному запросу разработчика, пожелавшего ознакомиться с полным списком элементов определенного набора. Кроме того, поскольку расширение наборного гнезда может оказаться достаточно нетривиальным, полезно было бы дооснастить препроцессор средствами интерактивного просмотра результатов его работы.

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

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

6. Однородный набор

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

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

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

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

Оформление однородного набора оказывается полезным и в случае, если никаких подключений многократно используемых компонентов не ожидается. С его помощью получают наглядное компактное представление все ортогональные слои программы, которые иначе неизбежно распались бы на далеко отстоящие друг от друга текстовые фрагменты [5‑7]. Кроме того, к программе могут подключаться не только многократно используемые компоненты, но и одноразовые дополнения, и расширяемость однородного набора влияет на технологию таких подключений не менее благотворно.

7. Заключение

Итак, что же дает применение однородного набора? С одной стороны, его двери всегда гостеприимно распахнуты для безболезненного приема новых многократно используемых компонентов. С другой стороны, однородный набор позволяет конструктивно и четко провести расчленение программы на модули, отвечающие за отдельные направления ее развития, и благодаря этой четкости и конструктивности у элементов набора существенно возрастают шансы на превращение в "независимые единицы программистского знания" [8], в многократно используемый программный материал.

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

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

Литература

[1]  

Fayad M.E., Schmidt D.C. Object-oriented application frameworks // Comm. ACM. — 1997. — V. 40, N. 10. — P. 32‑38.

[2]  

Чеппел Д. Технологии ActiveX и OLE. — М.: Русская редакция, 1997. — 320 с.

[3]  

Роджерсон Д. Основы COM. — М.: Русская редакция, 1997. — 376 с.

[4]  

Керниган Б., Ритчи Д. Язык программирования Си. — М.: Финансы и статистика, 1992. — 272 с.

[5]  

Горбунов-Посадов М.М. Конфигурации программ. — М.: Малип, 1994. — 272 с.

[6]  

Горбунов-Посадов М.М. Безболезненное развитие программы // Открытые системы. — 1996. — № 4. — С.65‑70. — http://www.keldysh.ru/gorbunov/evolution.htm

[7]  

Горбунов-Посадов М.М. Система открыта, но что-то мешает // Открытые системы. — 1996. — № 6. — С.36‑39. — http://www.keldysh.ru/gorbunov/open.htm

[8]  

Цейтин Г.С. На пути к сборочному программированию // Программирование. — 1990. — № 1. — С.78‑92.


Рекомендуемая форма библиографической ссылки на данную статью

Горбунов-Посадов М.М. Конфигурационные ориентиры на пути к многократному использованию. — Препринты ИПМ им.М.В.Келдыша. — 1997. — № 37. — 16 с. — URL: http://www.keldysh.ru/gorbunov/reuse.htm

или

Горбунов-Посадов М.М. Облик многократно используемого компонента // Открытые системы. — 1998. — № 3. — С. 45‑49. — URL: http://www.osp.ru/os/1998/03/179494/

Другие публикации на данную тему

Горбунов-Посадов М.М. Как растет программа. — Препринты ИПМ им.М.В.Келдыша. — 2000. — № 50. — 16 с. — http://www.keldysh.ru/gorbunov/grow.htm

Горбунов-Посадов М.М. Расширяемые программы. — М.: Полиптих, 1999. — 336 с. — http://www.keldysh.ru/gorbunov/


Рейтинг@Mail.ru