Размышления о путях развития ООП

1. Кризис программирования

"Программа выполнила допустимую операцию, но будет закрыта по умолчанию".
Из программистского юмора

Когда Ваш компьютер в третий раз за день зависает по необъяснимым причинам, или любимый текстовый редактор перечеркивает полдня Вашей работы, аварийно завершаясь с лаконичным сообщением "Программа выполнила недопустимую операцию и будет закрыта", совершенно закономерно приходят в голову мысли о кризисе отрасли программирования. Эмоциональные советы "специалистов" о том, что "настоящие программисты не используют Windows, Basic, Pascal, С++ и проч. (нужное подчеркнуть)" являются на самом деле не столько советами, сколько признанием собственного бессилия перед конъюнктурой компьютерного рынка, который заставляет производителей в спешке выпускать все новые и новые версии сырых программных продуктов. Это внешняя причина кризиса.

Внутренние причины кризиса не менее серьезны, они приводят к относительному замедлению темпов прогресса программного обеспечения в сравнении, например, с аппаратным. В книге [1] 1982 года издания известный советский специалист в области кибернетики Д.А.Поспелов предсказывал к 2000 году появления интеллектуальных роботов, способных наравне с человеком-специалистом решать различные задачи в соответствующей проблемной области. Прогноз оказался чересчур оптимистичным. Несмотря на то, что основные теоретические разработки в области искусственного интеллекта были сделаны еще в 50-60-е годы, для практической работы их, по-видимому, оказалось недостаточно. Каковы же внутренние причины, замедляющие развитие программного обеспечения?

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

Во-вторых, за последние 20-30 лет (если не за несколько столетий, прошедших с момента распространения книгопечатания и широчайшего внедрения математики во все естественные науки) не произошло ничего нового в технологии хранения и приобретения знаний. Компьютер, аналогично экскаватору, просто заменил лопату в руках человека и позволил ему делать больше и быстрее. О проблемах сохранения и использования знаний убедительно и эмоционально писал в 1997 году А.Седов [2]. Если ограничиться только областью разработки программного обеспечения (софтверной инженерией), проблемы особенно заметны. Накопленное при разработке некоторой программы знание, как правило, умирает по окончании жизненного цикла данной программы (сохраняясь разве что в опыте и квалификации разработчика, да и то лишь частично).

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

В то же время значительная часть программистов занимается сегодня изобретением армии велосипедов, и происходит это по следующим причинам:

  • недостаток информации;
  • ценовая недоступность коммерческих программных средств и библиотек;
  • сложность освоения, недостаточная функциональность и/или надежность сторонних программных средств и библиотек.

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

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

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

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

Тем не менее, ни одна из методик не претендует на абсолютную универсальность. Каждая имеет свои достоинства и недостатки, а также область применения. Поскольку наибольшее распространение и, полагаю, наибольшие перспективы имеет подход ООП (см., например, классическую работу Г.Буча [3]), было бы заманчиво проанализировать его недостатки и наметить пути его дальнейшего развития, что я попытаюсь сделать в настоящей статье.

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

2. Три источника, или поиски "серебряных пуль"

"Атомы являются не более чем удобной концепцией для описания функционирующего реального мира.
На самом деле ;) атомов не существует, поверь квантовому химику ;)".
А.Хаврюченко

2.1. Конструируем из кубиков, или универсальность "черного ящика"

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

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

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

Итак, модель удобно конструировать из кубиков - элементов. Что говорит по этому поводу современная практика программной инженерии?

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

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

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

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

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

2.2. Агрегация и полиморфизм

А.Усов [4] исчерпывающе объяснил недостатки существующих реализаций метода ООП и показал практическую полезность таких механизмов, как агрегация (с использованием специальных объектов-контейнеров), реальный полиморфизм, построение сложных систем с использованием объектных сред (по сути тех же укрупненных функциональных блоков) и механизма сообщений между элементами. Именно его статьи, первоначально опубликованные в FIDO7.SU.OOP, произвели некий поворот в моем сознании и убедили в полезности дальнейших работ в
области ООП.

Коротко можно отметить, что классический метод ООП [3] использует иерархическую модель проблемной области в виде дерева наследования объектов. Заметим, что такая структура: а) ограниченна, б) статична. Однако объекты реального мира, как правило, не укладываются в столь простую классификацию, структура их взаимозависимости скорее представляет собой многосвязный граф, который можно спроецировать на дерево только в очень частном случае. Попытка исправить ситуацию введением в ООП множественного наследования не является удовлетворительным решением.

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

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

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

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

Пусть некий молодой человек в рассматриваемый момент исполняет роль студента и находится в контексте контейнера Лекция. Вдруг у него в кармане звонит сотовый телефон (приходит сообщение от контейнера Семья), и он временно переключается из одного контейнера в другой для проведения беседы в контексте Семьи. Ситуация аналогична аппаратному прерыванию в компьютере. Отличие в том, что аппаратных прерываний не так много, и их обычно обрабатывают особенным специфическим образом. В то время как в реальной жизни (и ее модели) подобные ситуации являются скорее правилом, чем исключением. Как обеспечить соответствующее поведение модели? Исходя из логики полной инкапсуляции, следует предусмотреть в контейнере свойство (схему обработки сообщения) "звонок сотового" (и еще огромное количество подобных для всех аналогичных случаев), что весьма громоздко, нецелесообразно и вообще совершенно нелепо. Более того, реальная жизнь (на примере популярной телерекламы ;) показывает, что регламентировать подобную связь не во власти контейнера Лекция. Напрашивается следующий шаг развития нашего миропонимания.

2.3. Роли и их динамика

Хотя работы по использованию ролей велись давно (см., например, [5]), не могу не выразить благодарность В.Павликову [6], выступления которого привлекли мое внимание к этой замечательной концепции.

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

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

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

3. Особенности реализации

"Ну, если базовый кубик унаследовать от волшебной палочки..."
А. Наумов

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

Продолжение следует... Критика и предложения принимаются с благодарностью!

Литература

  1. Поспелов Д. А. Фантазия или наука: на пути к искусственному интеллекту - М.: Наука. 1982.
  2. Седов А. "Машины теорий. Структура линейного текста и ее альтернативы"
  3. Буч. Г. Объектно-ориентированное проектирование с примерами применения: Пер. с англ. - М.: Конкорд, 1992.
  4. Усов А. Письма про ООП.
  5. ???
  6. Павликов В. "ELIT. ООП и роли"

Донской А. Н., 2000, http://simulators.narod.ru