Грегор Кизалес и др. «Аспектно-ориентированное программирование»

от автора

Полный список авторов: Грегор Кизалес, Джон Лэмпинг, Анураг Мандекар, Крис Маэда, Кристина Видейра-Лопес, Жан-Марк Лонтье, Джон Ирвин

Аннотация

Мы обнаружили множество проблем программирования, при работе над которыми ни процедурные, ни объектно-ориентированные техники не позволяют достаточно четко выразить некоторые важные проектные решения, реализуемые программой. Из‑за этого реализация таких решений оказывается разбросанной по всему коду, и в результате мы получаем «запутанный» код, который крайне трудно развивать и сопровождать. Мы анализируем, почему некоторые проектные решения так сложно четко зафиксировать в реальном коде. Мы называем свойства, затрагиваемые этими решениями, аспектами и показываем, что их сложно зафиксировать, поскольку они насквозь пересекают базовую функциональность системы. Мы излагаем основы новой техники программирования — аспектно-ориентированного программирования, — которая позволяет четко описывать программы, включающие такие аспекты, и обеспечивает изоляцию, композицию и повторное использование кода аспектов. Наше обсуждение опирается на системы, построенные нами с применением этой техники.

1. Введение

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

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

Мы полагаем, что сегодняшнее состояние исследований в области АОП аналогично состоянию исследований в области ООП двадцатилетней давности. Базовые понятия начинают оформляться, и растущая группа исследователей уже использует их в своих работах [1, 4, 13, 28]. Более того, хотя АОП как таковое — идея новая, уже существуют системы, обладающие свойствами, подобными АОП. Вклад этой статьи заключается в анализе проблем, которые призвано решить АОП, а также в введении первоначального набора терминов и понятий, обеспечивающих возможность явного проектирования систем на основе АОП.

В этой статье АОП представляется через примеры — все обобщения и определения выводятся из примеров, а не даются заранее. В разделе 3 используется пример среднего масштаба, чтобы продемонстрировать проблему запутывания аспектов, которую решает АОП; раздел завершается определением понятия аспект. В разделе 4 приводится несколько небольших примеров аспектов. В разделах 5 и 6 приводится по одному примеру полноценной АОП-системы. В оставшихся разделах представлены перспективы дальнейших исследований, смежные работы и заключение.

2. Исходные предпосылки

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

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

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

Методы проектирования, которые развивались для работы с языками ОП, как правило, разбивают системы на поведенческие или функциональные модули. Этот стиль называют функциональной декомпозицией [25–27]¹. Точная природа декомпозиции, само собой, различается в разных языковых парадигмах, однако каждый модуль инкапсулирован в процедуру/функцию/объект, и в каждом случае естественно говорить об инкапсулированном элементе как о функциональном модуле общей системы. Это последнее замечание может быть настолько привычным, что кажется несколько избыточным. Но сейчас важно явно уделить ему внимание, поскольку в ходе этой статьи мы будем рассматривать модули системной декомпозиции, не являющиеся функциональными.

3. Что такое аспекты?

Чтобы читателю было легче понять истоки проблем запутанности и то, как АОП их решает, мы выстроили этот раздел вокруг подробного примера, основанного на реальном приложении, над которым мы работали [18, 22]. Существуют три реализации этого приложения: простая для понимания, но неэффективная; эффективная, но сложная для понимания; и реализация на основе АОП, которая одновременно проста для понимания и эффективна. Здесь мы будем опираться на три аналогичные, но упрощенные реализации.

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

3.1. Базовая функциональность

Достичь первой цели сравнительно просто. Старое доброе процедурное программирование можно использовать, чтобы реализовать систему четко, кратко и в хорошем соответствии с моделью предметной области. При таком подходе фильтры можно определить как процедуры, которые принимают несколько входных изображений и выдают одно выходное. Набор примитивных процедур будет реализовывать базовые фильтры, а фильтры более высокого уровня будут определяться на основе примитивных. Например, примитивный фильтр or!, который принимает два изображения и возвращает их пиксельное логическое ИЛИ, можно реализовать так2:

(defun or! (a b)  (let ((result (new-image)))     (loop for i from 1 to width do ; перебор всех пикселей входных изображений      (loop for j from 1 to height do        (set-pixel result i j ; сохранение пикселей в результирующем изображении          (or (get-pixel a i j) ; операция, выполняемая над пикселями              (get-pixel b i j)))))    result))

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

функциональность

реализация

попиксельные логические операции

написаны с использованием примитива цикла, как показано выше

сдвиг изображения вверх, вниз

написан с использованием примитива цикла; структура цикла слегка изменена

разность двух изображений

(defun remove! (a b)
(and! a (not! b)))

пиксели на верхней границе области

(defun top-edge! (a)
(remove! a (down! a)))

пиксели на нижней границе области

(defun bottom-edge! (a)
(remove! a (up! a)))

пиксели на горизонтальной границе

(defun horizontal-edge! (a)
(or! (top-edge! a)
(bottom-edge! a)))

Заметьте, что лишь примитивные фильтры явно имеют дело с циклическим перебором пикселей в изображениях. Фильтры более высокого уровня, такие как horizontal-edge!, четко выражаются через примитивные фильтры. Получившийся код легко читать, анализировать, отлаживать и расширять — короче говоря, он отвечает первой цели.

3.2. Оптимизация использования памяти

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

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

(defun horizontal-edge! (a)  (let ((result (new-image)); будет создано        (a-up (up! a))    ; лишь три        (a-down (down! a)))    ; результирующих изображения    (loop for i from 1 to width do    ; общая структура цикла компонентных фильтров      (loop for j from 1 to height do        (set-pixel result i j          (or (and (get-pixel a i j)    ; операции                   (not (get-pixel a-up i j)))    ; для               (and (get-pixel a i j)    ; многих                   (not (get-pixel a-down i j))))))); подфильтров    result))

По сравнению с первоначальным вариантом этот код крайне запутан. Он включает в себя все фильтры, на основе которых определен horizontal-edge!, и выполняет слияние многих, но не всех, циклов. (Слияние циклов для up! и down! не производится, поскольку эти операции имеют другую структуру цикла³.) Короче говоря, переработка кода ради более эффективного использования памяти разрушила первоначальную чистоту компонентной структуры.

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

Рис. 1: Две диаграммы неоптимизированного фильтра horizontal-edge!. Слева — функциональная декомпозиция, которая непосредственно соответствует модели предметной области. Справа — диаграмма потоков данных, в которой прямоугольники обозначают примитивные фильтры, а ребра — потоки данных между ними во время выполнения. Прямоугольник a внизу — это входное изображение.

Рис. 1: Две диаграммы неоптимизированного фильтра horizontal-edge!. Слева — функциональная декомпозиция, которая непосредственно соответствует модели предметной области. Справа — диаграмма потоков данных, в которой прямоугольники обозначают примитивные фильтры, а ребра — потоки данных между ними во время выполнения. Прямоугольник a внизу — это входное изображение.

3.3. Сквозное пересечение

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

Стоит отметить, что овал слияния не включает horizontal-edge! целиком. Более того, он не совпадает ни с одним из иерархических модулей слева. И хотя оба реализуемых свойства — функциональность и слияние циклов — берут начало в одних и тех же примитивных фильтрах, при композиции фильтров они должны компоноваться по-разному. Функциональность строится иерархически традиционным способом. Слияние же циклов осуществляется путем слияния циклов тех примитивных фильтров, которые имеют одинаковую структуру цикла и непосредственно соседствуют в графе потоков данных. Каждое из этих правил композиции легко понять, глядя на соответствующую диаграмму. Однако эти два типа композиционных отношений настолько фундаментально пересекаются, что одно крайне сложно разглядеть на диаграмме другого.

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

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

Теперь мы можем более четко определить два важных понятия:


Применительно к системе и ее реализации на языке, основанном на ОП, свойство, которое должно быть реализовано, является:

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

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


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

4. Другие примеры того, как аспекты насквозь пересекают компоненты

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

приложение

язык ОП

компоненты

аспекты

обработка изображений 

процедурный

фильтры

слияние циклов

совместное использование результатов

выделение памяти на этапе компиляции

цифровая библиотека

объектно-ориентированный

хранилища, принтеры,
сервисы

минимизация сетевого трафика

ограничения синхронизации

обработка сбоев

матричные алгоритмы

процедурный

операции линейной алгебры

представление матриц

перестановка

ошибка с плавающей точкой

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

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

5. Первый пример АОП

В этом разделе мы вернемся к примеру обработки изображений и используем его, чтобы наметить реализацию этого приложения на основе АОП. Изложение основано на разработанной нами системе, но представлено в несколько упрощенном виде. Полная система описана в [22]. Цель этого раздела — быстро представить полную структуру реализации на основе АОП, а не подробно объяснять эту структуру. Такое объяснение последует в разделе 6.

Структура реализации приложения на основе АОП аналогична структуре реализации на основе ОП. Реализация на основе ОП состоит из: (i) языка, (ii) компилятора (или интерпретатора) для этого языка и (iii) программы, написанной на языке и реализующей приложение; в то время как реализация на основе АОП состоит из: (i.a) компонентного языка для программирования компонентов, (i.b) одного или нескольких аспектных языков для программирования аспектов, (ii) вплетателя аспектов (aspect weaver), работающего с этими языками, (iii.a) компонентной программы, реализующей компоненты на компонентном языке, и (iii.b) одной или нескольких аспектных программ, реализующих аспекты на аспектных языках. Как и в случае с языками ОП, языки АОП и вплетатели могут быть спроектированы так, что вплетение откладывается до времени выполнения (вплетение во время выполнения) либо выполняется на этапе компиляции (вплетение на этапе компиляции).

5.1. Компонентный язык и программа

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

(define-filter or! (a a) (pixelwise (a b)  (aa bb) (or aa bb)))

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

5.2. Аспектный язык и программа

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

(cond ((and (eq (loop-shape node) ’pointwise)            (eq (loop-shape input) ’pointwise))       (fuse loop input ’pointwise             :inputs (splice …)             :loop-vars (splice …)             :body (subst …))))

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

5.3. Вплетение

Вплетатель аспектов принимает компонентную и аспектную программы и выдает на выходе программу на C. Эта работа выполняется в три отдельных этапа, как показано на рис. 2.

На первом этапе вплетатель использует разворачивание как технику генерации графа потока данных на основе компонентной программы. В этом графе узлы представляют примитивные фильтры, а ребра — изображение, передаваемое от одного примитивного фильтра к другому. Каждый узел содержит одну конструкцию цикла. Так, например, узел A содержит следующую конструкцию цикла, где #<…> указывает на ребра, входящие в узел:

(pointwise (#<edge1> #<edge2>) (i1 i2) (or i1 i2))
Рис. 2: Вплетатель аспектов для приложений по обработке изображений работает в три этапа.

Рис. 2: Вплетатель аспектов для приложений по обработке изображений работает в три этапа.

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

 (pointwise (#<edge1> #<edge2> #<edge3>) (i1 i2 i3)   (or (and (not i1) i2) (and (not i3) i2))))

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

Ключевое свойство этой системы в том, что вплетатель не является «умным» компилятором, который может быть крайне сложен в проектировании и разработке. Используя АОП, мы делаем так, что все важные решения по стратегии реализации — всю действительно сложную логику — предоставляет программист с помощью соответствующих аспектных языков. Работа вплетателя — совмещать, а не создавать⁶.

5.4. Результаты

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

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

Наша реализация приложения на основе АОП содержит 1039 строк кода, включая компонентную программу и все три аспектные программы. Сам вплетатель аспектов, включая повторно используемый компонент кодогенерации, содержит 3520 строк (реальное ядро вплетателя — 1959 строк). Производительность новой реализации сопоставима с запутанной версией в 35213 строк (эффективность по времени ниже, а эффективность по памяти выше7).

Как и в случае многих других проектов по разработке программного обеспечения, крайне сложно дать количественную оценку преимуществам использования АОП без крупного экспериментального исследования с участием множества программистов, использующих как АОП, так и традиционные техники для разработки и сопровождения различных приложений [6, 21, 36]. Такое исследование пока выходит за рамки нашей работы, хотя мы надеемся провести его в будущем. А пока мы разработали один из способов измерения того, в какой степени применение техник АОП может упростить приложение. В этом подходе мы сравниваем реализацию приложения на основе ОП с реализацией того же приложения на основе АОП. Он показывает, насколько компактнее записаны аспекты в реализации на основе АОП по сравнению с реализацией, не основанной на АОП. Общее уравнение для этого измерения, а также численные значения для данного конкретного приложения, выглядят следующим образом:

В этой метрике любое число больше 1 указывает на положительный результат применения АОП. Это приложение демонстрирует исключительно высокий выигрыш от использования АОП; в других разработанных нами приложениях выигрыш варьируется от 2 до указанного числа [8, 14, 22]. Можно сказать, что в сумму делителя следует включить объем самого вплетателя. С этим можно поспорить, поскольку вплетатель можно использовать в любом количестве схожих приложений по обработке изображений, а не только в распознавателе таблиц. Но заметим, что даже при включении вплетателя значение этой метрики будет равно 9.

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

6. Второй пример АОП

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

Функциональность этой системы хорошо описывается с помощью объектно-ориентированной модели. При таком подходе объектами являются документы, хранилища, различные печатные формы документов (PDF, PS, RIP…), принтеры, серверы и т.д. Здесь есть несколько важных аспектов, включая:

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

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

  • Обработку сбоев, под которой мы понимаем контекстно-чувствительную обработку множества различных видов сбоев, которые могут возникнуть в распределенной системе.

Пока что мы ограничимся лишь аспектом коммуникации. Работа с коммуникацией и координацией при помощи АОП обсуждается в [14]. Обработка сбоев с помощью АОП — предмет будущих исследований.

6.1. Компонентный язык и программа

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

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

В этом примере компонентные программы должны реализовывать такие элементы, как книги, хранилища и принтеры. Чтобы аспектная программа, отвечающая за коммуникацию, могла заниматься коммуникацией, компонентная программа не должна делать этого сама. В данном случае Java™ вполне подходит в качестве компонентного языка. Он предоставляет объектную модель, реализующую соответствующие компоненты, и не затрагивает аспект коммуникации⁸. Итак, при использовании Java в качестве компонентного языка определения двух простых классов — для книг и хранилищ книг — выглядят следующим образом:

public class Book {  String title, author;  int isbn;  OCR ocr;  PDF pdf;  Postscript ps;  RIP rip;    public String get_title()  {    return title;  }    public String get_author()  {    return author;  }    public int get_isbn()  {    return isbn;  }}public class Repository {  private Book books[];  private int  nbooks = 0;    public Repository (int dbsize)  {books = new Book[dbsize];  }    public void register (Book b)   {books[nbooks+] = b;  }    public void unregister (Book b)  {… }    public Book lookup (String s)  {… }}

6.2. Аспектный язык и программа

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

Один из способов добиться этого — предоставить доступ к вызову методов во время выполнения через рефлексию. Как показано в [7, 23, 35, 37], доступ через рефлексию можно использовать для управления аспектом коммуникации в распределенной объектной системе. Однако доступ через рефлексию — настолько мощный инструмент, что его использование может быть опасным или сложным. Поэтому в данном случае мы решили предоставить более высокоуровневый аспектный язык, специально адаптированный для управления копированием при удаленных вызовах методов.

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

remote Repository {  void register (Book);  void unregister (Book: copy isbn);  Book: copy isbn lookup(String);}

6.3. Вплетатель аспектов

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

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

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

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

7. Открытые вопросы

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

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

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

Другая важная область исследования — пространство разных способов проектирования компонентных и аспектных языков. Можем ли мы разработать целый набор компонентных и аспектных языков и комбинировать их по-разному для разных приложений? Можем ли мы использовать фреймворки метауровня [2, 3, 20, 38] для создания такого набора?

Какое теоретическое обоснование можно разработать для АОП? Какие теории наилучшим образом описывают взаимодействие между аспектами и компонентами и то, как их следует вплетать? Могут ли такие теории помочь в разработке практического инструментария для вплетения?

Как обстоит дело с анализом и процессом проектирования? Какие принципы проектирования лучше всего подходят для аспектной декомпозиции? Какие «модульные» структуры лучше всего подходят для аспектных программ? Как научить людей выявлять аспекты, четко отделять их, писать аспектные программы, отлаживать и документировать АОП-системы?

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

8. Смежные работы

В этом разделе мы даем краткий обзор работ, связанных с нашей. Мы начинаем с более близких по теме работ и постепенно переходим к менее близким.

8.1. Работы, явно связанные с АОП

Несколько других групп исследователей начали явно рассматривать свои работы в терминах АОП. Среди них:

  • Мехмет Акшит и другие исследователи из Университета Твенте разработали объектную модель композиционных фильтров, позволяющую управлять сообщениями, которые отправляет и принимает объект [1]. В их работе компонентный язык — это традиционное ООП, а механизм композиционных фильтров предоставляет аспектный язык, который можно использовать для управления целым набором аспектов, включая синхронизацию и коммуникацию. Вплетение в основном происходит во время выполнения; точками соединения служат динамические отправка и прием сообщений объектом.

  • Кэлтон Пу и другие исследователи из Орегонского институте аспирантуры в своих работах по Synthetix разрабатывают высокопроизводительные, легко портируемые и высокоадаптивные ядра ОС [19, 28]. В их работах компоненты — это привычные функциональные элементы ядра ОС. Аспекты — это в основном оптимизации, основанные на инвариантах, которые зависят от способа использования сервиса. Их технология вплетения использует частичное вычисление для эффективной специализации кода ядра под конкретные сценарии использования. Их код структурирован таким образом, чтобы в качестве точек соединения выделять те места, где инвариант становится или перестает быть истинным.

  • Карл Либерхер и другие исследователи из Северо-Восточного университета разрабатывают техники, которые облегчают повторное использование объектно-ориентированных программ и делают их менее хрупкими при выполнении типичных эволюционных задач [13, 15, 31]. В их работах компонентными языками выступают существующие реализации ООП, такие как C++ и Java. Краткие спецификации обхода [13] и контекстные объекты [31] предоставляют аспектные языки, которые можно использовать для решения множества сквозных задач. Вплетение аспектных программ, использующих краткие спецификации обхода, выполняется на этапе компиляции; представление точек соединения — это граф классов. Вплетение аспектных программ, использующих контекстные объекты, в большей степени происходит во время выполнения, а точки соединения представлены динамическими вызовами методов и функций.

8.2. Рефлексия и метаобъектные протоколы

Аспектно-ориентированное программирование имеет глубокую связь с работами по вычислительной рефлексии и метаобъектным протоколам [11, 20, 24, 32, 34, 38]. Система рефлексии предоставляет базовый язык и один или несколько метаязыков, позволяющих управлять семантикой и реализацией базового языка. Метаязыки дают возможность взглянуть на вычисление так, как это не может сделать ни один компонент базового языка, — например, увидеть весь стек исполнения или все вызовы объектов некоторого класса. Таким образом, они насквозь пересекают базовый уровень вычислений. В терминах АОП метаязыки — это низкоуровневые аспектные языки, точки соединения которых представляют собой перехватчики, предоставляемые системой рефлексии. АОП — это цель, а рефлексия — один из мощных инструментов для ее достижения.

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

Эта связь особенно очевидна в разделе 6, где представленные нами аспектные языки могли бы быть надстроены поверх архитектуры, основанной на рефлексии. Схожим образом аспект слияния циклов, описанный в разделе 5, может быть достаточно эффективно реализован с использованием механизма комбинации методов в метаобъектном протоколе CLOS [11, 33]. Эта связь также очевидна в работах, упомянутых в разделе 8.1; как система «Деметра», так и композиционные фильтры описаны как средства, основанные на рефлексии [16].

8.3. Программная трансформация

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

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

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

8.4. Субъективное программирование

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

8.5. Другие инженерные дисциплины

Множество других инженерных дисциплин основаны на устоявшейся аспектной декомпозиции. Например, инженеры-механики используют в процессе проектирования статические, динамические и температурные модели одной и той же системы. Эти различные модели сквозным образом пересекают друг друга, поскольку разные свойства системы по-разному компонуются. Аналогичным образом некоторые инструменты разработки программного обеспечения явно поддерживают конкретные виды аспектной декомпозиции — например, инструменты для техники объектного моделирования [29, 30] позволяют программистам создавать разные диаграммы функционирования объектов.

9. Выводы

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

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

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

Благодарности

Благодарим Карла Либерхера, Карин Лукас, Гейл Мерфи и Бедира Текинердогана за их обширные и подробные замечания к ранним черновикам этой статьи, а также Энди Берлина, Джеффа Чейза, Патрика Чунга, Джона Гилберта, Артура Ли, Кэлтона Пу, Алекса Сильвермана, Марвина Таймера и Марка Йима, с которыми мы множество раз обсуждали АОП.

Также благодарим всех участников AOP Friends Meetings, с которыми мы провели приятные два дня, обсуждая АОП и смежные идеи: Мехмета Акшита, Лодевика Бергманса, Пьера Куанта, Уильяма Харрисона, Жака Маленфана, Сатоси Мацуоку, Кима Менса, Харольда Осшера, Кэлтона Пу, Иэна Симмондса, Перри Тарра, Бедира Текинердогана, Марка Скиппера и Патрика Стейарта.

Примечания

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

2. Здесь мы выбрали синтаксис Common Lisp, но это достаточно просто записать на любом другом Algol-подобном языке.

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

4. Компоненты друг от друга, аспекты друг от друга и компоненты от аспектов.

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

6. Хотя требование, чтобы программист явно описывал реализацию аспектов, может показаться шагом назад, наш опыт работы над открытой реализацией говорит о том, что это не так [9, 10, 12, 17]. Когда программист описывает реализацию аспекта, связанного с памятью, правильное использование АОП предполагает, что он выражает стратегию реализации на подходящем уровне абстракции через соответствующий аспектный язык с соответствующим уровнем локальности. Он не описывает детали реализации и не работает напрямую с запутанной реализацией. Оценивая реализацию на основе АОП, важно сравнивать ее как с наивной и неэффективной, так и со сложной и эффективной реализациями.

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

8. В [14] объясняется, что для поддержки аспектного языка координации из Java следует убрать некоторые средства низкоуровневой синхронизации, прежде чем его можно будет использовать в качестве компонентного языка. Речь идет о ключевом слове synchronized, а также о методах wait, notify и notifyAll.

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

Библиография

[1] Aksit M., Wakita K., et al. Abstracting Object Interactions Using Composition Filters // Guerraoui R., Nierstranz O., Rivelli M. (eds.) Proceedings of the ECOOP’93 Workshop on Object-Based Distributed Programming. Berlin: Springer-Verlag, 1993.

[2] Bobrow D., DeMichiel L.G., et al. Common Lisp Object System Specification // ACM SIGPLAN Notices, vol. 23, special issue, September 1988.

[3] Chiba S. A Metaobject Protocol for C++ // Proceedings of the Conference on Object-Oriented Programming Systems, Languages, and Applications, ACM SIGPLAN Notices, vol. 30, № 10, October 1995.

[4] Consel C. Program Adaptation Based on Program Transformation // Proceedings of the ACM Workshop on Strategic Directions in Computing Research, 1996.

[5] Harrison W., Ossher H. Subject-Oriented Programming (A Critique of Pure Objects) // Proceedings of the Conference on Object-Oriented Programming: Systems, Languages, and Applications, ACM SIGPLAN Notices, vol. 28, № 10, October 1993.

[6] Henry S., Kafura D. Software Structure Metrics Based on Information Flow // IEEE Transactions on Software Engineering, vol. 7, № 5, September 1981.

[7] Ichisugi Y., Matsuoka S., et al. Rbc1: A Reflective Object-Oriented Concurrent Language Without a Run-Time Kernel // Yonezawa A., Smith B. (eds.) Proceedings of the IMSA ’92 International Workshop on Reflection and Meta-Level Architecture. Tokyo: Research Institute of Software Engineering, 1992.

[8] Irwin J., Loingtier J.-M., et al. Aspect-Oriented Programming of Sparse Matrix Code // Xerox PARC, Palo Alto, California. Technical Report SPL97-007 P9710045, February 1997.

[9] Kiczales G. Foil for the Workshop on Open Implementation // http://www.parc.xerox.com/spl/eca/oi/workshop-94/foil/main.html.

[10] Kiczales G. Why are Black Boxes so Hard to Reuse? // http://www.parc.xerox.com/spl/eca/oi/gregor-invite.html.

[11] Kiczales G., des Rivères J., et al. The Art of the Metaobject Protocol. Cambridge: MIT Press, 1991.

[12] Kiczales G., Lamping J., et al. Open Implementation Design Guidelines // International Conference on Software Engineering, 1997 (готовится к публикации).

[13] Lieberherr K.J., Silva-Lepe I., et al. Adaptive Object-Oriented Programming Using Graph-Based Customization // Communications of the ACM, vol. 37, № 5, May 1994.

[14] Lopes C.V., Kiczales G. D: A Language Framework for Distributed Programming // Xerox PARC, Palo Alto, California. Technical Report SPL97-010 P9710047, February, 1997.

[15] Lopes C.V., Lieberherr K.J. Abstracting Process-to-Function Relations in Concurrent Object-Oriented Applications // Tokoro M., Pareschi R. (eds.) Object-Oriented Programming: 8th European Conference, ECOOP’94. Berlin: Springer-Verlag, 1994.

[16] Lopes C.V., Lieberherr K.J. AP/S++: Case-Study of a MOP for Purposes of Software Evolution // Kiczales G. (ed.) Proceedings of the First International Conference on Metalevel Architectures and Reflection (Reflection ’96). San Francisco: Xerox PARC.

[17] Maeda C., Lee A., et al. Open Implementation Analysis and Design // Proceedings of Symposium on Software Reuse, 1997 (готовится к публикации).

[18] Mahoney J.V. Functional Visual Routines // Xerox PARC, Palo Alto, California. Technical Report SPL95-069, July 30, 1995.

[19] Massalin H., Pu C. Threads and Input/Output in the Synthesis Kernel // ACM SIGOPS Operating Systems Review, vol. 23, № 5, November 1989.

[20] Matsuoka S., Watanabe T., et al. Hybrid Group Reflective Architecture for Object-Oriented Concurrent Reflective Programming // America P. (ed.) ECOOP ’91 European Conference on Object-Oriented Programming. Berlin: Springer-Verlag, 1991.

[21] McClure C. A Model for Program Complexity Analysis // ICSE ’78: Proceedings of the 3rd international conference on Software engineering. New York: IEEE Computer Society Press, 1978.

[22] Mendhekar A., Kiczales G., et al. RG: A Case-Study for Aspect-Oriented Programming // Xerox PARC, Palo Alto, California. Technical Report SPL97-009 P9710044, February, 1997.

[23] Okamura H., Ishikawa Y., et al. Al-1/d: A Distributed Programming System With Multi-Model Reflection Framework // Yonezawa A., Smith B. (eds.) Proceedings of the IMSA ’92 International Workshop on Reflection and Meta-Level Architecture. Tokyo: Research Institute of Software Engineering, 1992.

[24] Okamura H., Ishikawa Y., et al. Metalevel Decomposition in AL-1/D // Nishio S., Yonezawa A. (eds.) Proceedings of the First JSSST International Symposium on Object Technologies for Advanced Software. Berlin: Springer-Verlag, 1993.

[25] Parnas D.L. Designing Software for Extension and Contraction // ICSE ’78: Proceedings of the 3rd International Conference on Software Engineering. New York: IEEE Computer Society Press, 1978. 

[26] Parnas D.L. On a ‘Buzzword’: Hierarchical Structure // IFIP Congress ’74, North Holland Publishing Company, 1974.

[27] Парнас Д. О критериях для разбиения систем на модули // https://habr.com/ru/articles/957968/.

[28] Pu C., Autrey T., et al. Optimistic Incremental Specialization: Streamlining a Commercial Operating System // Jones M.B. (ed.) SOSP ’95: Proceedings of the Fifteenth ACM Symposium on Operating Systems Principles. New York: Association for Computing Machinery, 1995.

[29] Rational. Rational Web Pages // http://www.rational.com.

[30] Rumbaugh J., Blaha M., et al. Object-Oriented Modeling and Design. Englewood Cliffs: Prentice Hall, 1991.

[31] Seiter L.M., Palsberg J., et al. Evolution of Object Behavior Using Context Relations // ACM SIGSOFT Software Engineering Notes, vol. 21, № 6, October 1996.

[32] Smith B.C. Reflection and Semantics in a Procedural Language // LCS Technical Report, M.I.T., Cambridge, Massachusetts, 1982.

[33] Steele G.L.Jr. Common Lisp — The Language. Bedford: Digital Press, 1984.

[34] Wand M., Friedman D.P. The Mystery of the Tower Revealed: A Non-Reflective Description of the Reflective Tower // LFP ’86: Proceedings of the 1986 ACM Conference on LISP and Functional Programming. New York: Association for Computing Machinery, 1986.

[35] Watanabe T., Yonezawa A. Reflection in an Object-Oriented Concurrent Language // Meyrowitz N. (ed.) OOPSLA ’88: Conference Proceedings on Object-Oriented Programming Systems, Languages and Applications. New York: Association for Computing Machinery, 1988.

[36] Yau S., Collofello J. Some Stability Measures for Software Maintenance // IEEE Transactions on Software Engineering, vol. 6, № 6, November 1990.

[37] Yokote Y. The Apertos Reflective Operating System: The Concept and its Implementation // Proceedings of the Conference on Object-Oriented Programming: Systems, Languages, and Applications, ACM SIGPLAN Notices, vol. 27, № 10, October 1992.

[38] Yonezawa A., Watanabe T. An Introduction to Object-Based Reflective Concurrent Computation // Proceedings of the ACM SIGPLAN Workshop on Object-Based Concurrent Programming, ACM SIGPLAN Notices, vol. 24, № 4, September 1988.

ссылка на оригинал статьи https://habr.com/ru/articles/1058826/