Terra incognita — программная архитектура


Прошедшая неделя ознаменовалась двумя незаметными событиями. Факт незамеченности их объясняется, скорее, узостью круга заинтересованных лиц, чем незначительностью происшедшего. Во-первых, NASA "освободила" обширный перечень своих программных разработок, сделав их доступными на коммерческой основе. Во-вторых, после пятилетнего (!) периода разработки вышла новая версия текстового редактора GNU Emacs.


Простое любопытство, "погнавшее" автора на сайт www.openchannelsoftware.com,
являющийся этакой "онлайновой распродажей" классических наработок знаменитой
аэрокосмической ассоциации, привело к неожиданным выводам. Главный из них — программы,
написанные более десяти (а то и двадцати) лет назад, сопровождавшиеся и дорабатывающиеся
все эти годы специалистами NASA, изначально предназначенные сугубо для "внутреннего"
использования, основанные на технологиях, о которых сегодня в "приличном
обществе" упоминать не принято (древний язык программирования FORTRAN и структурное
проектирование), оказывается, представляют собой ничуть не устаревшие и очень
хорошие инструменты. А значит, и товар, стоимость которого весьма высока. Последнее,
впрочем, подтверждается "ценниками" программ на openchannelsoftware.com.

Выход новой версии GNU Emacs (21.1) также "подогрел" любопытство — и вот уже больше 80 MB исходных текстов "оседают" на винчестере, недолгая процедура компиляции и сборки закончена, и самый "монстроидальный" текстовый редактор, на деле являющийся целой Вселенной, запущен и работает. Пять лет доводок, устранения ошибок, дополнений функциональности, гигантские объемы кода, "базарная" организация проекта — все это, казалось бы, должно однозначно свидетельствовать, что "монстр" неповоротлив, прожорлив, ненадежен… А на деле все обстоит совершенно иначе — Emacs работает, и работает прекрасно.

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

Мрачные цифры и факты

Программы — самые убого сконструированные, ненадежные и несопровождаемые технологические объекты, созданные человеком.
Поль Страссман, бывший CIO компании Xerox и Министерства обороны США

Жизненный цикл программного обеспечения исследовать очень трудно. ПО коммерческого
характера, ориентированное на массово-потребительский рынок, может внезапно "умереть"
по совершенно не зависящим от качества реализации причинам — это было неоднократно.
Поэтому пытаться сопоставлять факты из истории даже сложных и больших разработок
с их качеством — занятие бесполезное (стоит только вспомнить печальную судьбу
OS/2 и BeOS, которые фактически "умерли" по сугубо маркетинговым причинам,
или судьбу Plan9, которая так и "не родилась" в массовом признании из-за
более глубинных идеологических факторов). Кроме того, ПО этого класса вообще невозможно
оценивать по стоимости разработки (в смысле — а сколько денег пущено на ветер),
ведь последняя чаще всего является коммерческой тайной за семью печатями.

Но есть одна область применения программ, в которой ПО разрабатывается надолго,
в рамках ограниченного и известного бюджета и с исключительно высокими требованиями
к надежности и качеству. Это, естественно, военные приложения. Здесь нет маркетинга
(по крайней мере, сразу после выбора поставщика услуг), четко обозначены сроки
и бюджетные рамки. И, естественно, ведется строгий учет "достижений".
Последние, по данным Министерства обороны США, более чем невеселы:

  • 47% заказов, оплаченных МО США на разработку ПО, были выполнены, но разработанное ПО не используется по причине несоответствия требованиям;
  • 29% заказов оплатило МО США, но программы так и не были разработаны (не просто в оговоренные сроки, а вообще никогда);
  • выполнение 19% оплаченных контрактов на разработку ПО расторгнуто по причине срыва сроков;
  • 3% заказанного ПО разработано в срок, эти программы используются МО США, но после серьезных доработок силами собственных специалистов;
  • всего 2% заказанного ПО было одновременно и поставлено в срок, и не потребовало доработок.

Если военные вынуждены "открывать" информацию по своим затратам (хотя бы из-за зависимости от бюджета государства), то информация о "маленьких катастрофах" — привилегия сугубо коммерческого сектора. В 1999 г. онлайновый аукцион eBay "обвалился" на целых 22 часа. Причины происшедшего — ошибка в ОС Solaris (для устранения которой задолго до наступления критической ситуации существовала "заплатка"-patch) и неустойчивое поведение при сбоях операционной системы СУБД Oracle, приведшее к разрушению базы данных. Еще раньше, в 1998 г., получила "боевое крещение" ОС Windows NT, ставшая первой "высокоэффективной противокорабельной операционной системой" и доказавшая этот статус успешным затоплением ракетного крейсера "Йорктаун". Список можно продолжать долго, вплоть до недавно "отвеселившихся" в Сети CodeRed и Nimda…

Нет болезни — нет лекарств

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

Что это? Болезнь? Кризис? Порок индустрии? Или же проявление чего-то более глубинного, что незабвенный Васисуалий Лоханкин непременно назвал бы "сермяжным"? К сожалению, ни у кого готовых ответов на этот вопрос нет. Хотя бы потому, что несмотря на наличие тяжелого терминологического вооружения, современная программистская армия, увы, остается на уровне, извините за сравнение, варварских племен древности, для которых столкновение с легионом сложности в 98% случаев чревато поражением. И слава Богу, что в последнее время набирает даже не силу, а просто популярность один, допускающий пусть множество, но все же более или менее разумных толкований, термин — "программная архитектура" (ПА). Это не лекарство — лекарств нет и быть не может хотя бы потому, что никто не знает, в чем заключается болезнь, это не симптоматическое обезболивающее (врачи хорошо знают неэффективность симптоматического лечения), это, скорее, пока робкая попытка упорядочить знания о программировании "по большому счету". Естественно, последнее понятие нуждается в пояснении, и оно не заставит себя ждать.

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

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

Программная архитектура

Начнем с того, что никакого точного определения для этого термина не существует. Впрочем, мнения маститых специалистов хоть и разделяются, но в них присутствует нечто общее. Давайте к ним прислушаемся. К счастью, история существования термина не столь продолжительна, и мы не вынуждены блуждать в закоулках времени — в 1990 г. в журнальной публикации одного из первопроходцев ПА (Лейн) было дано такое определение: "ПА — это изучение высокоуровневой структуры и показателей программной системы. Важными аспектами ПА являются разделение функций между отдельными модулями системы, назначение соединений между ними и формы представления разделяемой модулями информации". Привкус "программирования в малом" в этом раннем определении еще ощущается очень остро хотя бы из-за сугубо программистского термина "модуль". В 1995 г. на первом международном симпозиуме по ПА известный сегодня специалист Мэри Шоу (Mary Shaw) привела собственное определение: "Структурная модель, представляющая программную систему множеством компонентов, соединением этих компонентов и (обычно) различными аспектами реализации". Всего пять лет отделяют одно определение от другого, и вот уже "программирование в малом" исчезло — формулировка хоть и размытая, но более или менее похожа на нечто, "укладывающееся" в рамки математической теории множеств. И вот, наконец, считающееся классическим определение, датированное 1999 г. (Буч, Booch): "ПА — набор стратегических решений по организации программной системы, выбору ее структурных элементов (СЭ) и их интерфейсов, совместно с описанием поведения на уровне спецификаций взаимодействия СЭ, а также описание композиции СЭ в подсистемы, включающее архитектурный стиль и руководства по всем перечисленным пунктам". "Классическое" определение, кроме присущей его автору любви к рекурсивному оперированию неопределенными терминами, обладает главным свойством — оно уводит от "программирования в малом" настолько далеко, насколько это возможно.

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

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

Словарь ПА

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

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

Первая значительная категория — ПА потоков данных. Из названия очевидно, что главное понятие в ней — данные, которые в рамках принятой идиоматики "прокачиваются" через обрабатывающие их компоненты. Разновидностей последних всего две — каналы (трубы, pipes) и фильтры. Собственно каналы с данными ничего не делают, они лишь обеспечивают доставку данных от одного компонента-фильтра к другому (и потому по аналогии именуются "трубами"). Главное "действующее лицо" в процессе обработки — фильтр, обладающий входом и выходом, осуществляющий некоторые операции с данными, поступившими на его вход, и выдающий результат на выход. Каналы-трубы соединяют входы и выходы разных фильтров, за счет чего достигается "сборка" в нечто большое и цельное множества маленьких компонентов. При этом к одному выходу компонента можно "подсоединить" сразу несколько труб, направив поток выводных данных на несколько компонентов. ПА потоков данных — квинтэссенция архитектурной модели ОС Unix, в которой даже создано специальное обозначение для доступных на пользовательском уровне компонентов-каналов (символ "|"). У этой архитектурной модели есть свои достоинства и недостатки. Ее хвалят за простоту декомпозиции большой системы до уровня маленьких компонентов, за возможность организации иерархических систем на ее основе, за простоту расширений и модификаций, за реализацию полноценных исполняемых компонентов в виде "черных ящиков" и даже за простоту распараллеливания выполнения отдельных компонентов системы на разных вычислителях. К недостаткам ПА потоков данных на компонентной базе каналов и фильтров часто относят трудность реализации в рамках такой архитектурной модели интерактивных систем, возможные большие неприятности при неряшливой реализации компонентов (знаменитые проблемы переполнения буферов и проч.), влияние выбранного порядка соединения фильтров на их функциональность, необходимость введения промежуточных языков описания данных, интерпретаторы которых должны выполняться при активации каждого компонента-фильтра. Реалии подтверждают и положительные, и отрицательные свойства программных систем, основанных на ПА потоков данных, — они действительно гибкие, живучие (ОС Unix исполнилось 30 лет), они действительно "не любят" сложных языков описания данных (в той же ОС Unix используется самый простой вариант — ASCII-текст).

Вторая категория — ПА "вызов-возврат". Она отражает тяжкое, но необходимое наследие "программирования в малом". В ней существуют соответствующие классической парадигме программирования компоненты — главная программа и подпрограммы, образующие иерархическую структуру. На взаимодействие (вызов) не налагается никаких ограничений — неважно, какими низкоуровневыми механизмами оно поддерживается. Будь то механизмы удаленного вызова подпрограмм, обеспечиваемые сервисами типа RPC — Remote Procedure Call, будь то вызовы сервера клиентом с помощью страшных и сложных механизмов модного middleware — все это попадает в предметную область ПА "вызов-возврат". Естественно, и эта категория архитектур имеет свои особенности. К ее достоинствам можно отнести "привычность" для разработчиков (сильные аналогии с "программированием в малом", которому в основном и учат) и относительную простоту построения интерактивных приложений. Недостатки же весьма серьезны — требование свободы вызова порождает необходимость использовать либо очень сложные (а значит, не подпадающие под наше определение ПА) системы (RPC, middleware — все это далеко не игрушечные разработки), либо ограничивать само понятие свободы до границ "одного большого приложения". В любом случае, это далеко не лучшая архитектурная модель, что признается подавляющим большинством исследователей и не признается в подавляющем большинстве реальных проектов (вероятнее всего, именно из-за "привычности").

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

Четвертая категория — "слоистая" ПА (layered). Ее компоненты — слои, реализующие дополнительную функциональность на основе функций лежащих под ними слоев. Как в данном случае реализован слой — роли не играет, он может быть, например, программной библиотекой, "надстроенной" над другой библиотекой, или же транслятором из одного языка в другой, для которого уже есть интерпретатор. Достоинства "слоистых" архитектур из-за их древнего, по компьютерным меркам, происхождения (ими еще в конце 60-х годов занимался великий Дийкстра) хорошо известны: четкая методика проектирования, заключающаяся в повышении уровня абстракции от слоя к слою, хорошая мобильность полученных программ (если слои представляются моделями абстрактных машин), при стандартизации интерфейсов каждого слоя можно добиться очень высоких показателей повторного использования кода. Впрочем, и недостатки у "слоистых" архитектур довольно серьезные: нагромождение абстрактных машин ведет к серьезной деградации производительности, нахождение "правильного" уровня абстракции для каждого "слоя" — искусство, не поддающееся формализации, неправильно выбранный уровень абстракции чреват серьезным усложнением "слоя" или необходимостью в создании промежуточного "слоя" (что часто приводит к перетяжелению и нежизнеспособности ПО, основанного на "слоистой" архитектуре).

ПА "независимых компонентов" — очень интересный класс архитектур. В них компоненты — исполняемые сущности, обменивающиеся сообщениями и реагирующие на сообщения на основе так называемой "модели событий". Последняя фактически представляет собой аналог уникальной для каждого компонента таблицы, ставящей в соответствие сообщению инициацию выполнения компонентом некоторого действия. Этот малораспространенный класс архитектур обладает весомыми достоинствами — при повторном использовании исполняемых (реальных) компонентов достигается 100%-ное повторное использование кода, принципиальное отделение компонентов от механизмов связи между ними существенно упрощает разработку, проблем с уникальными именами при росте проекта не возникает, допускается распараллеливание уже существующих программных систем. Недостатки ее также серьезны: построенная на основе такой архитектурной модели система принципиально асинхронна, что усложняет ее тестирование, в процессе разработки проекта требуется проектировать и систему событий, и политики диспетчеризации событий, и механизмы регистрации компонентов.

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

Вот и вся архитектура…

Действительно, за пределами описанных выше пяти категорий архитектур ПО отыскать что-либо очень трудно. Естественно, существуют различные инструментальные средства, автоматизирующие сборку приложения из компонентов, но все они работают в рамках одной архитектурной модели. Естественно, ведутся попытки создать инструментальное средство, позволяющее "собирать" приложения гибридных архитектур. И все-таки, успехи программных проектов NASA и GNU Emacs убеждают — главными инструментами в программировании остаются здравый смысл, хороший текстовый редактор и знания. Знаменитый Emacs, например, является классическим примером очень удачной разработки на основе вариации "слоистой" ПА — "виртуальной машины".