Недоря А.Е. - Диссертация. Глава 1

Введение    Глава 1    Глава 2    Глава 3    Глава 4    Заключение    Литература    Приложения


1. Определение РПС и выбор языка реализации

Будем называть расширяемой переносимой системой (РПС) систему, построенную по следующим принципам:

Расширяемость:

  • ядро системы определяет набор базовых понятий;
  • этот набор понятий может быть расширен без изменения ядра (и без перекомпиляции);
  • новые понятия могут быть определены на базе уже определенных понятий;
  • отсутствуют различия между базовыми и вновь определенными понятиями;

Переносимость:

  • все машинно-зависимые и системно-зависимые части РПС текстуально выделены;
  • объем таких частей существенно меньше объема системы;
  • система может быть настроена таким образом, чтобы (почти) полностью использовать ресурсы конкретной аппаратуры и ОС.

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

1.1. Основные требования к языку

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

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

  • несмотря на существование формальных моделей ОО программирования (ООП) (см. Booch [1], Meyer [2]), терминология ООП часто используется за рамками этих формальных моделей, что может привести к расхождению в понимании терминологии;
  • термин ООП часто ассоциируется с программированием на языке Smalltalk [3], а в последнее время с программированием на языке C++ [4];
  • вполне возможно, что существуют (или будут существовать) другие модели, более пригодные для реализации РПС.

Тем не менее, в данный момент мы не видим способа реализации РПС вне рамок ООП. В работе [5] проводится сравнение двух моделей ООП с моделью CLOS (Common Lisp Object System). В этой работе все свойства языков программирования делятся с точки зрения соответствия модели ООП на необходимые, желательные, допустимые и недопустимые. Рассматриваются те свойства языков, которые являются существенными с точки зрения ООП, а именно: способы описания подпрограмм, отношения между объектами, свойства классов, механизм задания наследования, типизация, полиморфизм и так далее. Такой подход позволяет оценить уровень реализации одной из двух моделей ООП в некотором языке программирования, а также сравнить модели между собой. Таблица сравнения двух моделей ООП, и оценка уровня реализации этих моделей в языках CLOS и Оберон-2 приводится в приложении.

Рассмотрим некоторые свойства языков программирования в рамках моделей ООП. Будем использовать следующую нотацию:


<свойство> (B:<отношение>, M:<отношение>, <отношение>),


где B обозначает модель Буша, M - модель Мейера, а последнее отношение отражает точку зрения автора.


<отношение>:

  E - Essential (необходимое)

  D - Desirable (желательное)

  P - Permissible (допустимое)

  I - Inadmissable (недопустимое)

n/a – не определено в модели


Свойства класса:

абстракция (интерфейс отделен от реализации) (B:E,M:E,E)

инкапсуляция (B:E,M:E,E)

класс есть модуль (единица компиляции) (B:n/a,M:E,P)

класс может содержать общие переменные (т.е. доступные всем объектам класса) (B:P,M:D,D)


Отделение спецификации от реализации общепризнано является необходимым свойством языков программирования (не только ОО). Не столь общепризнано, на каком уровне происходит это отделение. Так в модели Мейера считается существенным отождествление класса и модуля. С нашей точки зрения такое отождествление приводит к слиянию двух важных структурирующих механизмов: модулей – позволяющих объединить семантически связанные компоненты, и классов, выражающих связи по наследованию. Заметим, что разделение концепций класса и модуля автоматически реализует переменные класса и методы класса, позволяет описывать в одном модуле набор тесно связанных классов, причем эта связь может быть полностью скрыта в реализации и не отражена в интерфейсах модуля и классов, тем самым заменяя механизм "друзей" в языке C++.


Подпрограммы:

могут определяться вне класса (B:D,M:P,E)

только как методы (B:P,M:D,I)


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


Наследование:

одиночное (B:E,M:E,E)

множественное (B:P,M:E,P)

переопределение (класс может переопределить реализацию структуры или поведения) (B:D,M:E,E)


Наследование является ключевым понятием расширяемых систем. Если необходимость одиночного наследования очевидна, то множественное наследование (МН) вызывает множественные сомнения. Часто необходимость в МН вызвана неряшливым проектированием структуры системы. Более того, легко привести пример (см. например [6]), который не реализуем при МН, но легко реализуем в терминах одиночного наследования введением дополнительного интерфейсного объекта. Вполне возможно, что существуют примеры (неизвестные автору), которые нельзя (или очень трудно) выразить в терминах одиночного наследования, поэтому мы считаем МН допустимым свойством языка. В главе 4 подробно описывается проектирование системы с использованием только одиночного наследования.


Типизация:

сильная (B:D,M:E,E), слабая (B:P,M:I,I),

статическая (B:D,M:D,E), динамическая (B:D,M:E,E).


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


Управление памятью:

сборка мусора (B:P,M:E,E).


Использование в системе сборки мусора (garbage collection, далее GC) вызывает наибольшее количество споров. Противники сборки мусора обычно выдвигают два основных аргумента:

  • большие накладные расходы;
  • GC не может быть использована в системах реального времени или системах с гарантированным временем отклика.

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

Для РПС только сборка мусора позволяет достичь приемлемого уровня надежности, так как существенно затрудняется получение информации о наличии указателей в объекте (модуль, реализующий некоторый сервис, не может знать о расширениях). Использование явного переопределения методов возвращения памяти (деструкторы C++), вместе с необходимостью отслеживать наличие ссылок на объект, приводит к необходимости реализации различных вариантов сборки мусора для различных объектов системы, и следовательно к существенному снижению надежности по сравнению с наличием одного механизма сборки мусора, встроенного в систему.

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

  • доступность и качество готовых реализаций;
  • потенциальная эффективность языка и эффективность реализаций;
  • объем требуемых ресурсов;
  • трудоемкость реализации.

Необходимость статической типизации позволила нам существенно уменьшить число рассматриваемых языков. Кроме того, мы не стали рассматривать такие языки, как Eiffel и Sather, которые видимо имеют большое будущее, но в настоящее время мало доступны.

1.2. Сравнение языков Модула-3, Оберон, Оберон-2, C++

В данном разделе мы сравним языки семейства Модулы-2 [7], а именно языки Модула-3 [8], Оберон [9], Оберон-2 [10] и язык C++.

1.2.1. Народ vs. C++

Язык C++ не подходит в качестве языка разработки РПС, так как в нем не реализованы многие существенные свойства таких языков:

  • разделение концепции модуля и класса;
  • сильная статическая типизация;
  • динамическая типизация;
  • сборка мусора.

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

1.2.2. Модула-3 vs. Оберон

Разработчики языка Модула-3 пытались объединить лучшие черты языков Mesa, Модула-2, Cedar и Модула-2+ [11]. Полученный язык действительно весьма привлекателен, но, к сожалению, страдает некоторым гигантизмом. Помимо поддержки ООП, Модула-3 поддерживает исключения, финализацию, параллелизм и весьма сложный механизм раздельной компиляции, требующий этапа связывания всей программы, что является абсолютно недопустимым для РПС. Для реализации языка требуется большая динамическая поддержка, что затрудняет реализацию для машин с ограниченными ресурсами. Механизм ассоциирования методов с объектами позволяет реализовать как методы, ассоциированные с классом, так и методы, ассоциированные с экземпляром. В то же время в языке отсутствуют операции проверки динамического типа объекта, что является необходимым требованием для нашей модели ООП. Язык содержит набор низкоуровневых возможностей, но, в отличие от языка Модула-2, использование этих возможностей разрешено только в специальных низкоуровневых (unsafe) модулях.

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

Помимо очевидных преимуществ, связанных с размером языка (и динамической поддержкой), на наше решение повлияло то, что несмотря на название, язык Оберон гораздо ближе по духу к языку Модула-2, чем Модула-3.

1.2.3. Оберон vs. Оберон-2

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

  • методы (type-bound procedure) позволяют аккуратно разделить операции над каждым объектом и переопределять одни операции, не изменяя других; повышают эффективность, ликвидируя лишние динамические проверки типов; повышают надежность ввиду неявной инициализации методов;
  • экспорт только для чтения (read only export) позволяет повысить эффективность доступа к атрибутам объектов (без ущерба для надежности);
  • динамические массивы позволяют повысить универсальность операций и избавиться от ограничений в системе.

В то же время новые возможности не усложняют реализацию языка. Любой текст на языке Оберон является также текстом на языке Оберон-2.

1.2.4. Выводы

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

Язык Модула-2 обладает многими важными особенностями, упрощающими реализацию на нем динамической поддержки:

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

И еще одно свойство языка Модула-2 является очень важным для нас: близость его к языку Оберон-2. Это позволяет легко использовать Модула-2 библиотеки при программировании на Обероне и тем самым не потерять весь объем программного обеспечения, накопленного нами на языке Модула-2.

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


Введение    Глава 1    Глава 2    Глава 3    Глава 4    Заключение    Литература    Приложения