Сетевой электронный научный журнал "СИСТЕМОТЕХНИКА", № 2, 2004 г.

МЕТОД МОДИФИКАЦИИ ПРОГРАММНОГО КОДА НА ОСНОВЕ КОМПОНЕНТНОГО ПОДХОДА

 

Литвиненко А.Н., Кручинин А.Н.

(Ростовский государственный университет, axalix@mail.ru)

 

Введение

В настоящее время, подавляющее большинство программных продуктов создаются на основе процедурного или объектно-ориентированного подхода, которые широко описаны и реализованы в различных мощных средствах разработки. Повышенное внимание сейчас уделяется технологиям, позволяющим облегчить процесс написания кода в рамках уже существующих сред. Сложность написания кода связана со многими причинами: как с объективными так и с субъективными. Примером субъективных причин могут выступать индивидуальные требования к функциональности программного продукта (к его оформлению, масштабируемости и прочее). Простейший пример индивидуального требования — поддержка программой или системой нового шрифта. Нередко требования накапливаются и реализуются в последующих версиях продукта, но все чаще их реализация выходит на качественно новый уровень, с возможностью использования технологи Plug and Play, встроенного языка (например, VBA в MS Office, Max Script в 3ds Max, скрипта в 1С), каркасного подхода, а также и другие средства.

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

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

 

1. Подход к построению модифицированного приложения

Структура будущего программного продукта закладывается на этапе проектирования. Среди алгоритмов проектирования наиболее распространены следующие:

· структурное проектирование программных продуктов;

· информационное моделирование предметной области;

· объектно-ориентированное проектирование программных продуктов.

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

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

 

2. Этапы проектирования модифицируемого приложения

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

· получение ядра кода;

· локализация непостоянного кода и потенциальных мест его появление;

· параметризация кода и определение структуры спецификации для подключения компонент;

· реализация компонент;

· написание плагин-модулей и их подключение;

 

2.1. Получение ядра кода

Рассмотрим подробнее этапы построения кода: первым этапом при проектировании модифицируемого приложения является выделение в существующем коде т.н. ядра: той части кода, которая не подвергается изменениям и носит постоянный характер. Например в HTML коде, к ядру можно отнести теги, открывающие и закрывающие элементы <html>, <head>, <body>, т.к. они требуются синтаксисом и должны всегда присутствовать. Ядро здесь является полным аналогом горизонтальных слоев в смысле Фуксмана [3]. Смысл его заключается в том, что оно определяет структуру будущего кода: т.н. скелет приложения.

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

 

Признаки выделения ядра

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

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

2.   В коде присутствуют программные конструкции, влияющие на всю программу, как на единое целое: их наличие предписано синтаксисом языка; (Например, идентификаторы program и end. в языке Pascal, теги <HTML>,<BODY> в языке HTML)

3.   Существуют фрагменты кода, которые не могут иметь альтернативный код; (Например, проверка количества свободной оперативной памяти)

4.   Важно подключить определенные программные модули (например, с помощью #include, uses, use и т.д.), причем эти модули должны быть указаны независимо от возможных модификаций;

5.   В коде имеются идентификаторы, связанные с постоянными (или почти никогда неменяющимися) данными. (Например, ссылки на Web ресурсы; почтовые адреса; названия программных продуктов и др.)

 

Требования к построению ядра

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

1.   Объединять, где это возможно код, который не будет изменяться, а также выделять в цельные блоки модифицируемый код;

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

3.   Не использовать операторы типа goto, поскольку при определенных обстоятельствах (например, не отнесение части кода к ядру) может исчезнуть объект, на который необходимо перейти.

 

2.2. Локализация непостоянного кода

Вторым этапом, в противоположность ядру, является процесс локализации в существующем тексте программы такого кода, который имеет непостоянную изменчивую природу, т.е. выявление текста, который может быть заменен новым, причем основным требованием здесь является безболезненность подстановки. Если программа находится не в стадии редактирования, а в процессе создания, то локализация представляет собой указание мест возможного появления кода. Такой анализ кода сходен по смыслу с выделением или построением вертикальных слоев. Другими словами, второй этап проектирования сводится к определению “гнезд” (точек роста, мест привязки, поинтов, см. [4]) — потенциальных мест модификации программы. Для их объявления, предлагается использовать комментарии языка, на котором создается код. Единственным требованием здесь является возможность отличать такую конструкцию от действительного комментария. Например, в среде HTML такую роль могла бы сыграть следующая запись: <!-- !{название гнезда} -->

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

 

2.3. Определение структуры спецификации

Задачей третьего этапа является формирование структуры спецификации, которая необходима для подключения будущей компоненты. Предлагается в качестве такой структуры использовать XML, как язык разметки для спецификаций; и XSL, как инструмент, позволяющий обрабатывать XML для реализации компонент. Вообще, под спецификацией понимается ясное, недвусмысленное, формализованное описание высокого уровня  в терминах, характерных для проектируемой системы, а не для реализации [5].  Спецификация содержит набор параметров и их значения в виде древовидной структуры, согласно синтаксису XML. Подключаемая компонента, получая на входе спецификацию, должна в точности распадаться на фрагменты, идентичные тем, которые были локализованы на втором шаге. Для этого требуется произвести параметризацию — выработку набора параметров, которые должны влиять на генерируемые текстовые блоки. Параметры здесь схожи по смыслу с теми, которые передаются функциям в различных языках программирования. Их роль заключается в возможности варьирования кода, и именно данный факт позволяет нам формировать необходимые гибкие компоненты. Конечно, существуют примеры компонент, не зависящих от параметров, например, компонента, позволяющая определить количество свободной памяти, но как уже отмечалось в признаках выделения ядра, такой код часто относят к ядру. Иногда один и тот же текст программы постоянно используется в различных проектах — в такой ситуации бывает оправданно реализовывать необходимые фрагменты в виде компонент без параметров, несмотря на указанный признак. Так же, как и первые два этапа, параметризация кода — это неоднозначный процесс, связанный с мотивом выделения текстовых фрагментов и их оформления в цельную компоненту. На практике структура спецификации формируется и уточняется в течение долгого времени, нередко даже после реализации компоненты, что может быть объяснено необходимостью расширения ее функциональности вследствие появившихся новых требований. Таким образом, совершенно необязательно формировать сразу всю необходимую структуру спецификации (в отличии, например, от набора параметров функции). Далее, использование параметров позволяет избежать дублирования информации. Указав в спецификации некоторое значение, оно может быть использовано в будущем, в любом текстовом фрагменте. Например, задав в спецификации название пункта меню, мы можем использовать полученный параметр и для формирования заголовка окна и для создания другого меню.

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

 

2.4. Реализация компонент

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

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

Таким образом, допустимо появление значения управляющего параметра в конечном коде. Приведем пример: пусть требуется в тексте программы продублировать некоторый фрагмент текста n раз. Фрагмент текста — регулярный параметр, а число n — управляющий. В генерируемых фрагментах программного кода регулярные параметры принимают вид констант и переменных используемого языка программирования.

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

 

2.5. Определение плагин-модулей и их подключение

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

Плагин-модуль — это конкретная спецификация с определенными параметрами для подключения компоненты, основанная на структуре, выработанной на третьем этапе. Используя имя компоненты, плагин-модуль указывает на ее файл регистрации, который, в свою очередь, и определяет набор текстовых блоков. При использовании некого управляющего приложения, на основе плагин-модуля формируются текстовые фрагменты, которые инсталлируются в гнезда исходного кода, указанные в данной спецификации и в самой компоненте. Также к плагин-модулю будем относить совокупность гнезд, необходимых для работы компоненты. Их основной задачей является указание мест встраивания соответствующих частей компонент. Например, плагин-модуль для простой вставки html текста в гнездо вызова компоненты и в блок <STYLE>,может иметь следующий вид:

(1)<!--!{(point,2)} -->

(2)<COPY>  

(3) <MAKESTYLE>

(4)           .bg {background: #ffffff; text-align: center}

(5) </MAKESTYLE>

(6) <DATA>

(7)           <a href="picture1.html">pic1</a>

(8)           <a href="picture2.html">pic3</a>

(9)           <a href="picture3.html">pic4</a>

(10)</DATA>

(11)</COPY>

Здесь, в строке (1) указываются два управляющих параметра: имя гнезда для вставки тегов, заданных в строках (7)-(9) и приоритет для данного блока. Тег из строки (4) будет добавлен в блок <STYLE> генерируемого текста. Особенностью приведенного примера является тот факт, что у нас определено единственное гнездо, хотя предполагается появление двух независимых фрагментов (основного текста и стиля). Это объясняется тем, что недостающее гнездо явно указано в файле регистрации. Данный факт как раз показывает на “плавающий” характер управляющих параметров, часть которых может присутствовать в самом плагин-модуле, а часть — в файле регистрации.

 

3. Структурная схема взаимодействия компонент кода и управляющего приложения

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

 

 

Рис. 1. Структурная схема взаимодействия компонент кода и управляющего приложения

 

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

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

3.    Файл регистрации предоставляет управляющему приложению полную информацию о XSL стилях;

4.    Исходя из плагин-модуля, его структуры (XML) и полученных XSL стилей формируются необходимые текстовые блоки;

5.    На последнем этапе осуществляется инсталляция полученных фрагментов кода в найденные гнезда.

 

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

Предложенный подход описывает построение инструментальных свойств для эффективного сопровождения и написания программы, а также позволяет:

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

·      максимально повторно использовать код и данные;

·      отделять логику приложения от содержания и дизайна;

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

 

Список литературы

 

1.      Горбунов-Посадов М.М.  Безболезненное развитие программы. Открытые системы 1996, № 4, с. 65-70

2.      Информационные технологии // http://www.stu.ru/inform/glaves2/glava18/gl_18_3.html

3.      Фуксман А.Л. Технологические аспекты создания программных систем. – М.: Статистика, 1979, 184 с.

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

5.      Агафонов В.Н. Языки и средства спецификации программ // Сб. статей «Требования и спецификации в разработке программ». – М.: Мир, 1984, с. 285

Сетевой электронный научный журнал "СИСТЕМОТЕХНИКА", № 2, 2004 г.