Аспектно-ориентированное программирование
( Валентин Павлов )
Анализ вариантов применения аспектно-ориентированного подхода при разработке программных систем.
- Аннотация
- Благодарности
- Введение
- Работы в области аспектно-ориентированного програмирования
- Существующие подходы к разработке программных систем
- Введение в АОП
- Критерии сравнения аспектной реализации с объектно-ориентированной
- Варианты применения АОП на разных этапах ЖЦ
- Заключение
- Направления будущих исследований
- Список литературы
Аннотация
В статье рассматриваются вопросы применения новой парадигмы программирования — аспектно-ориентированного подхода, основным свойством которой является модуляризация сквозных требований на разных уровнях абстракции и их локализация в отдельных программных модулях — аспектах.
В процессе исследования проведен анализ существующих подходов к разработке, выделены проблемы, возникающие при наличии сквозных общесистемных требований, а также рассмотрена проблема роста сложности проектируемых программных систем. В статье дается представление об аспектно-ориентированном программировании, аспектной декомпозиции и отличии этого подхода от традиционного программирования, использующего только функциональную декомпозицию. Кроме того, показаны преимущества и недостатки новой методологии.
В работе приведены примеры применения аспектного подхода на разных этапах жизненного цикла программных систем. Приведенные примеры позволяют отметить основное достоинство применения аспектно-ориентированного подхода: улучшение модульности, что выражается в локализации сквозных требований в специальных программных единицах — аспектах, в упрощении сопровождения программной системы и внесении изменений, а также появлении новых возможностей повторного использования кода.
Возможность применения аспектного подхода показана для задач самого разного характера. Оценка применимости АОП сделана в рамках выбранных критериев сравнения ОО и АО реализаций предлагаемых вариантов применения новой методологии.
Благодарности
Данная статья является мастер-тезисом который защищиался в июне 2003 года в Санкт-Петербургском электротехническом университете (ЛЭТИ). В написании этого труда неоценимую помощь мне оказали мои руководители Журавлев Е.А. и Кирьянчиков В.А. Отдельное спасибо Изьюрову А.Л. за "жизненные примеры" применения новой парадигмы программирования.
Введение
Данная работа посвящена исследованию вариантов применения новой методологии — аспектно-ориентированного подхода при разработке программных систем.
Актуальность
Аспектно-ориентированное программирование (АОП) представляет собой одну из концепций программирования, которая является дальнейшим развитием процедурного и объектно-ориентированного программирования (ООП). Данная методология призвана снизить время, стоимость и сложность разработки современного ПО, в котором, как привило, можно выделить определенные части, или аспекты, отвечающие за ту или иную функциональность, реализация которой рассредоточена по коду программы, но состоит из схожих кусков кода. По оценкам специалистов [24], около 70% времени в проектах тратится на сопровождение и внесение изменений в готовый программный код. Поэтому достаточно важной в ближайшей перспективе становится роль АОП и подобных трансформационных подходов. Сравнительно новая технология уже получила довольно широкое распространение показав свою эффективность на тестовых приложениях, однако место этого подхода в индустрии ПО по ряду объективных причин все еще не определено.
Объект исследования
Существенная черта программной системы — уровень сложности: один разработчик практически не в состоянии охватить все детали системы, причем сложность присуща большинству современных программных систем. Данная сложность неизбежна: с ней можно справиться, но избавиться от нее нельзя. Сложность программных систем обусловлена четырьмя основными причинами: сложностью реальной предметной области, из которой исходит заказ на разработку; трудностью управления процессом разработки; необходимостью обеспечить достаточную гибкость программы; неудовлетворительными способами описания поведения больших дискретных систем [9].
Объектом исследования данной работы является аспектно-ориентированный подход при разработке программных систем. АОП предлагает языковые средства, позволяющие выделять сквозную функциональность в отдельные модули, и таким образом упрощать работу (отладку, модифицирование, документирование и т.д.) с компонентами программной системы, и снижать сложность системы в целом. Здесь и далее под "модулем" понимается некоторая четко выраженная структурная единица программы — процедура, функция, метод, класс или пакет. Программный модуль в терминах некоторой рассматриваемой парадигмы программирования (например, ООП) назовем компонентом.
Цель исследования заключается в анализе возможных вариантов применения нового подхода при решении проблемы растущей сложности программных продуктов на разных этапах жизненного цикла, сравнение реализаций предложенных вариантов с традиционной объектно-ориентированной реализацией, а также просмотре дальнейших перспектив в развитии новой методологии.
Для решения поставленной задачи, необходимо осуществить анализ существующих подходов при разработке программных систем, описать проблемы которые появляются при использовании функциональной декомпозиции при наличии сквозных системных требований, сделать обзор аспектной методологии и инструментов поддерживающих ее. Ввести критерии сравнения ОО и АО реализаций, а также представить варианты применения аспектно-ориентированного подхода на примерах. Для каждого примера приведена реализация на языке Java, ее аспектно-ориентированный аналог и их сравнительный анализ.
Научная новизна и практическая значимость
Появление новых парадигм программирования всегда являлось интересной темой, поскольку каждая новая методология разработки программного обеспечения позволяла решать проблемы, являющиеся посылками для ее появления, и значительно продвигала науку Computer Science и индустрию ПО в целом. Усложнение программных систем — как глобальная проблема требует постоянного внимания и изучения, поэтому появление АОП представляется широким полем для исследований с последующим их практическим применением.
В работе предлагаются пути использования аспектного подхода на разных этапах жизненного цикла ПО. Кроме того, в работе приводится описание АОП, дается обзор существующих подходов при разработке программных систем и предпринимается попытка локализации преимуществ и, главное, недостатков новой методологии.
Данная работа позиционируется как одна из первых работ на русском языке посвященных обзору принципов аспектного подхода и тематике применения аспектного программирования.
Работы в области аспектно-ориентированного програмирования
Основные идеи АОП были сформулированы наиболее явной форме в одной из ранних статей [1] идеологом методологии Грегором Кикжалесом (Gregor Kiczales). На сервере поддержки и развития АОП [22] можно получить актуальную информацию о развитии данной методологии, а также узнать о событиях и конференция проходящих по этой тематике. Хороший обзор по АОП представлен в диссертации [18].
АОП — методология, в основе которой лежат идеи, встречающиеся в области технологии программирования уже достаточно давно. Это субъектно-ориентированное программирование (subject-oriented programming) [25], композиционные фильтры (composition filters) [26], [14], адаптивное программирование (adaptive programming) [15]. АОП тесно связано с ментальным программированием (intentional programming), концепции которого изложены в работе Чарльза Саймони [23]. Другой близкой идеологией является так называемое порождающее, или трансформационное, программирование (generative programming, transformational programming) [16].
В [2] авторы предлагают краткое введение в проблематику АОП, представлены ключевые понятия АОП и рассмотрены способы интеграции аспектов, в том числе и на этапе выполнения программы. В [3] отмечаются преимущества АОП-подхода, недостатки существующих реализаций описания точек связывания аспектов и функциональных модулей. Предложена модификация модели AspectJ для задания точек связывания аспектов.
В статье [21] автор дает основные понятия АОП, вводит термин сквозная функциональность, а также предоставляет краткое описание языка AspectJ.
Поскольку все примеры разрабатывались с использованием наиболее популярной реализации AspectJ, то основная литература, изучаемая по реализациям АОП, — это литература касающаяся этого языка. Руководство пользователя [17] является основным документом, описывающим данное расширение языка Java. В [4] автор детально рассматривает язык AspectJ, эта книга использовалась в качестве альтернативного источника информации. Кроме того, из источника [22] можно получить информацию обо всех поддерживаемых на данный момент расширениях языков и других инструментах поддерживающих АОП. Например, в статье [19] описана связь между АОП и .NET
В классическом труде [6] описываются простые и изящные решения типичных задач, возникающих в объектно-ориентированном проектировании. Авторы излагают принципы использования шаблонов проектирования и приводят их каталог. В [7] описаны эффективные методы применения всех типов шаблонов проектирования применительно к платформе Java. В [5] и [13] авторы показывают применимость аспектного подхода для реализации протокола взаимодействия объектов при реализации шаблонов проектирования. В этих работах они рассматривают улучшения в терминах модульности, повышения степени повторного использования и улучшенного восприятия исходного кода классов участников шаблонов из каталога [6].
В [11] дается понятие "контрактного проектирования" (Design by Contract) — техники способствующей созданию надежного объектно-ориентированного программного обеспечения. В [12] рассматриваются общие подходы при обработке ошибок на этапе разработки ПО, даются советы по использованию АОП и делаются выводы о применимости нового подхода на данном этапе жизненного цикла.
Существующие подходы к разработке программных систем
Эволюция методологий разработки ПО
В начале эры вычислительной техники программы разрабатывались посредством прямого кодирования на машинном языке. Программисты тратили большое количество времени, раздумывая над особенностями использования того или иного набора машинных инструкций, чем непосредственно над стоящей перед ними задачей. Затем перешли к языкам более высокого уровня, которые позволяли абстрагироваться от машинного уровня. Потом пришла эра структурированных языков, теперь программисты могли проводить структурную декомпозицию проблем в терминах процедур, необходимых для выполнения той или иной задачи. Тем не менее, с ростом сложности программного обеспечения возникла потребность в другой технологии. Объектно-ориентированное программирование позволило представить систему как множество взаимодействующих объектов. Классы позволили скрыть детали реализации за интерфейсами. Механизм полиморфизма обеспечил общее поведение и интерфейс связанных концепций и позволил управлять поведением компонентов без доступа к реализации базовых концепций.
Методологии разработки ПО и языки программирования определили путь взаимодействия человека с компьютером. Каждая новая методология представляет новые пути декомпозиции проблем: машинный код, машинно-независимый код, процедуры, классы и т.д. Каждая новая методология позволяла все более естественно отобразить требования к продукту на программные конструкции. Эволюция программных методологий позволила создавать более сложные системы.
В настоящее время объектно-ориентированное программирование (ООП) является методологией, в пользу которой делается выбор большинством новых проектов разработки программных продуктов. Несомненно, методология ООП продемонстрировала свою силу при моделировании общего поведения разрабатываемой системы. Однако, как можно убедиться из опыта, ООП не в достаточной мере позволяет справляться с растущей сложностью программных систем.
Система как набор функциональных требований
Как правило, любая программная система состоит из нескольких частей: основной (предметно-ориентированной) и системной части, которые несут в себе требуемую функциональность. Например, ядро системы обработки кредитных карт предназначено для работы с платежами, тогда как функциональность системного уровня предназначена для ведения журнала событий, целостности транзакций, авторизации, безопасности, производительности и т.д. Большинство подобных частей системы, известны как сквозная функциональность [2], затрагивают множество основных предметно-ориентированных модулей. Можно рассматривать сложную программную систему как комбинацию модулей, каждый из которых включает в себя кроме бизнес-логики часть сквозной функциональности из набора требований к системе. Рисунок 1 показывает систему как набор таких требований разбитых на разные модули.

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

Разработчик создает программную систему как результат обработки множества требований. Можно явно выделить из этого множества требования к логике конкретного модуля и общесистемные требования. Многие из системных требований могут быть ортогональными друг другу и требованиям конкретного модуля. Требования системного уровня имеют тенденцию пересекаться с множеством основных требований. Например, типичная система уровня предприятия включает в себя такие виды сквозной функциональности как аутентификация, ведение журнала событий, управление ресурсным пулом, администрирование, анализ производительности и управление носителями информации. Каждое из этих требований к системе затрагивает множество подсистем, например, требование по управлению носителями информации затрагивает каждый сохраняемый бизнес-объект.
Современные технологии разработки ПО на уровне языков программирования предоставляют удобные средства для выделения логики функционирования программы в отдельные модули, но не одна из них не предлагает удобного способа локализации в отдельные модули функциональности, которая должна распространяться на всю систему.
Из-за того, что реализация сквозной функциональности не может быть обособлена средствами языка программирования в отдельном программном модуле, элементы этой реализации присутствуют в том или ином виде в большинстве модулей, образующих программную систему. Рассмотрим пример java-класса, основной задачей которого является реализация некоторой логики:
public class SomeBusinessClass {
// Бизнес-данные
// вспомогательные данные
public void performSomeOperation(OperationInformation info) {
// проверить уровень доступа к данным
// проверить соответствие входных данных контракту
// запретить доступ к данным другим потокам выполнения
// проверить состояние кэша данных
// занести в журнал отметку о начале операции
// ==== Реализация логики данного класса ====
// занести в журнал отметку о конце операции
// разрешить доступ к данным другим потокам выполнения
}
}
В этом примере можно выделить две проблемы. Во-первых, определяемые вспомогательные данные не относятся к требованиям, накладываемым на данный модуль, а требуются для работы сквозной функциональности. Во-вторых, реализация performSomeOperation(..) выглядит более нагруженной, чем просто "Реализация логики данного класса". Для правильной работы данного метода по требованиям необходимо выполнить ряд действий, не относящихся конкретно к данному модулю — проверить уровень доступа к данным, проверить соответствие входных данных контракту, запретить доступ к данным другим потокам выполнения, проверить состояние кэша данных, занести в журнал отметку — это требования системного уровня. К тому же, многие из этих общих требований должны быть реализованы в других модулях.
На рисунке 3 схематично изображено распределение функциональности "ведение журнала событий" по модулям некоторой программной системы. В каждом модуле отмечен код, реализующий требование "ведение журнала событий".

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

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

Признаки.
Несколько признаков, которые могут указывать на проблемную реализацию сквозной функциональности при использовании существующих подходов. Разобьем эти признаки на две категории:
- Запутанный код: В модуле может быть реализовано несколько требований. Например, часто разработчики одновременно решают проблемы связанные с бизнес логикой, производительностью, синхронизацией потоков и безопасностью. В результате множество элементов из разных требований присутствует в разрабатываемом модуле, что приводит к запутанному коду.
- Рассредоточенный код: Так как сквозная функциональность по определению распространяется на множество модулей, то вызовы этой функциональности будут рассредоточены по всей системе. Например, если в системе используется требование по слежению за производительностью работы базы данных, то такая сквозная функциональность затронет все модули, работающие с базой данных.
Следствия.
Наличие запутанного и рассредоточенного кода влияют на проектирование и реализацию во многих отношениях:
- Плохое прослеживание назначения модуля: Одновременная реализация нескольких требований в одной модульной единице делает неясным соответствие между отдельным требованием и его реализацией, в результате затруднительно понять, что реализует конкретный модуль.
- Непригодность кода для повторного использования: В связи с тем, что модуль может использовать в себе некоторую сквозную функциональность с жесткой привязкой к этой функциональности, другие части системы или другие проекты, где может потребоваться уже написанный модуль (но с другими требованиями к сквозной функциональности) не могут использовать уже написанный модуль.
- Большая вероятность ошибок: Запутанность кода влечет за собой код с множеством скрытых проблем. Более того, реализация нескольких ортогональных требований в одном модуле может привести к тому что ни одно из них не получит достаточного внимания разработчика.
- Трудность в сопровождении: Появление дополнительных требований в будущем потребует переработки текущей реализации, и это может затронуть большинство существующих модулей. Модификация каждой отдельной подсистемы в отдельности под новые требования может привести к несовместимости.
С тех пор как сложность программных систем возросла и появилась сквозная функциональность, находящаяся на срезе системы, не удивительно, что появились несколько подходов для решения этих проблем. Эти подходы включают в себя классы-примеси (mix-in) [20], шаблоны проектирования [6] и специфичные доменные решения.
При использовании классов-примесей реализацию сквозной функциональности можно поместить в них. Компоненты содержат экземпляр класса mix-In и позволяют другим частям системы устанавливать свой экземпляр.
Поведенческие шаблоны проектирования, такие как Visitor или Template Method позволяют поместить сквозную функциональность в главные классы, которые как в случае mix-In классов будут обходить компоненты, при этом вызывая логику специфичную для посещения данного компонента или вызывая специфичный шаблонный метод.
Специфичные доменные решения, такие как, например, каркасы (framework) и сервера приложений, позволяют разработчикам выносить некоторые сквозные требования на уровень этих решений. Например, архитектура EJB позволяет вынести на уровень сервера приложения сквозную функциональность следующего вида — безопасность, администрирование, анализ производительности и управление поведением перманентных объектов, и сфокусироваться только на разработке компонентов уровня предприятия. Специфичные доменные решения предлагают специализированный механизм для решения специфичных проблем, однако при смене технологии приходится заново изучать новые подходы для решения тех же проблем.
Перед системным архитектором при проектировании системы возникает дилемма по выбору технологии при реализации сквозных требований — либо воспользоваться одним из существующих на данный момент решений конкретной проблемы либо воспользоваться новой технологией призванной решать подобные проблемы.
Введение в АОП
АСПЕКТ (от лат. aspectus — вид), точка зрения, с которой рассматривается какое-либо явление, понятие, перспектива. (Большой энциклопедический словарь)
Исследователи изучили различные пути выделения в отдельные модули сквозной функциональности в сложных программных системах. Аспектно-ориентированное программирование (АОП) является одним из этих решений. АОП предлагает средства выделения сквозной функциональности в отдельные программные модули — аспекты.
С точки зрения АОП в процессе разработки достаточно сложной системы программист решает две ортогональные задачи:
- Разработка компонентов, то есть выявление классов и объектов, составляющих словарь предметной области.
- Разработка сервисов, поддерживающих взаимодействие компонентов, то есть построение структур, обеспечивающих взаимодействие объектов, при котором выполняются требования задачи.
Современные языки программирования (такие как, например, C++, VB и т.п.) ориентированы, прежде всего, на решение первой задачи. Код компонента представляется в виде класса, т.е. он хорошо локализован и, следовательно, его легко просматривать, изучать, модифицировать, повторно использовать. С другой стороны, при программировании процессов, в которые вовлечены различные объекты, мы получаем код, в котором элементы, связанные с поддержкой такого процесса, распределены по коду всей системы. Эти элементы встречаются в коде множества классов, их совокупность в целом не локализована в обозримом сегменте кода. В результате мы сталкиваемся с проблемой "запутанного" кода.
В рамках АОП утверждается, что никакая технология проектирования не поможет решить данную проблему, если только мы будем оставаться в рамках языка, ориентированного только на разработку компонентов. Для программирования сервисов, обеспечивающих взаимодействие объектов, нужны специальные средства, возможно специальные языки. После этапа кодирования компонентов и аспектов на соответствующих языках выполняется автоматическое построение оптимизированного для выполнения (но не для просмотра и модификации) кода. Этот финальный процесс называется слиянием или интеграцией (weaving).
Основные концепции АОП
Аспектно-ориентированный подход рассматривает программную систему как набор модулей, каждый из которых отражает определенный аспект — цель, особенность функционирования системы. Набор модулей, образующих программу, зависит от требований к программе, особенностей ее предметной области. Наряду с функциональными требованиями к программе предъявляются и общесистемные требования, например: целостности транзакций, авторизованного доступа к данным, ведения журнала событий и т. д. При проектировании программной системы разработчик выбирает модули так, чтобы каждый из них реализовывал определенное функциональное требование к системе. Однако реализация некоторых требований к программе зачастую не может быть локализована в отдельном модуле в рамках процедурного или объектно-ориентированного подхода. В результате код, отражающий такие аспекты функционирования системы, будет встречаться в нескольких различных модулях. Традиционные парадигмы программирования используют при проектировании программы функциональную декомпозицию и не позволяют локализовать сквозную функциональность в отдельных модулях. Необходимость реализации сквозной функциональности имеющимися средствами ведет к тому, что некоторый компонент содержит код, отражающий множество ортогональных требований к системе. Это делает такой модуль узкоспециализированным, ухудшает возможности его повторного использования и в некоторых случаях приводит к дублированию кода. В свою очередь, это вызывает повышение вероятности внесения ошибок, увеличение времени отладки, снижает качество программы и в большой степени затрудняет ее сопровождение. Аспектно-ориентированный подход в некоторых случаях позволяет избежать описанных проблем и улучшить общий дизайн системы, обеспечивая возможность локализации сквозной функциональности в специальных модулях — аспектах.
АОП позволяет реализовывать отдельные концепции в слабосвязанном виде, и, комбинируя такие реализации, формирует конечную систему. АОП позволяет построить систему, используя слабосвязанные разбитые на отдельные модули (аспекты) реализации общесистемных требований.
Разработка в рамках АОП состоит из трех отдельных шагов:
- Аспектная декомпозиция: разбиение требований для выделения общей и сквозной функциональности. На этом шаге необходимо выделить функциональность для модульного уровня из сквозной функциональности системного уровня. Например, в примере с кредитными картами можно выделить три вещи: ядро обработки кредитных карт, журнал событий, аутентификация.
- Реализация функциональности: Реализовать каждое требование отдельно. В примере с кредитными картами необходимо отдельно реализовать модуль обработки кредитных карт, модуль журнала, модуль аутентификации.
- Компоновка аспектов: На этом шаге аспектный интегратор определяет правила для создания своих модулей — аспектов, составляя конечную систему. В примере с кредитными картами необходимо определить, в терминах языка реализующего АОП, при вызове каких операций необходимо вносить запись в журнал, и по завершению каких действий необходимо сообщать об успехе/неуспехе операции. Также можно определить правила, по которым будет вызываться модуль аутентификации перед доступом к бизнес-логике обработки кредитных карт.

АОП отличает многое от традиционных подходов ООП при реализации сквозной функциональности: здесь нужно по-другому представлять себе процесс декомпозиции, а архитектура получающегося программного продукта в значительной степени выходит за рамки представлений, традиционных для объектного программирования. При разработке на АОП концепции реализуются абсолютно независимо друг от друга, так как все существующие между ними связи (сквозная функциональность) могут быть локализованы в аспектных модулях, описывающих протокол взаимодействия концепций. Например, в модуле обработки кредитных карт может отсутствовать запись в журнал или вызов модуля авторизации, однако при работе может вызываться подобная сквозная функциональность, если она описана в протоколе взаимодействия. Это серьезный шаг в развитии методологий от ООП.
Аспектом в АОП является системный модуль, в который вынесена сквозная функциональность. Аспектный модуль — это результат аспектной декомпозиции, на этапе которой выявляются какие-либо явления, понятия, события которые могут быть применены к группе компонентов, полученных после объектной декомпозиции. Аспект представляет собой языковую концепцию, схожую с классом, но только более высокого уровня абстракции.
Аспекты могут затрагивать многие компоненты и используют так называемые точки вставки для реализации регулярных действий, которые обычно рассредоточены по всему тексту программы. В аспектном модуле описываются срезы точек — точки выполнения программы, в которые встраиваются инструкции языка, которые должны выполняться до, после или вместо строго определенной точки выполнения программы. Подобные инструкции языка являются функциональностью, поддерживающей взаимодействие компонентов. Кроме того, в аспектном модуле могут описываться роли компонентов, на которые может воздействовать данный аспект. В отдельных реализациях АОП при помощи аспектных модулей можно влиять на существующую схему наследования. С точки зрения АОП аспект является сервисом, связывающим компоненты системы.
Пример интеграции аспектов.
Для иллюстрации работы интегратора аспектов вернемся к примеру обработки кредитных карт. Для краткости рассмотрим только 2 операции — кредит и дебет:
public classCreditCardProcessor {
public void debit(CreditCard card, Currency amount)
throwsInvalidCardException, NotEnoughAmountException,
CardExpiredException {
// логика по дебету
}
public void credit(CreditCard card, Currency amount)
throws InvalidCardException {
// логика по кредиту
}
}
и интерфейс журнала событий:
public interface Logger {
public void log(Stringmessage);
}
Для получения желаемой композиции требуется применение следующих правил, выраженных на обычном языке:
- записать в журнал начало каждой операции
- записать в журнал окончание каждой операции
- записать в журнал каждую исключительную ситуацию, которая может возникнуть в процессе работы этого модуля.
Интегратор аспектов, применяя такие правила, получит код эквивалентный данному:
public class CreditCardProcessorWithLogging {
Logger _logger;
public void debit(CreditCard card, Money amount)
throws InvalidCardException, NotEnoughAmountException,
CardExpiredException {
_logger.log("Starting CreditCardProcessor.debit(CreditCard,
Money) "+ "Card: " + card + " Amount: " + amount);
// Debiting logic
_logger.log("Completing CreditCardProcessor.debit(CreditCard,
Money) " + "Card: " + card + " Amount: " + amount);
}
public void credit(CreditCard card, Money amount)
throws InvalidCardException {
System.out.println("Debiting");
_logger.log("Starting CreditCardProcessor.credit(CreditCard,
Money) " + "Card: " + card + " Amount: " + amount);
// Crediting logic
_logger.log("Completing CreditCardProcessor.credit(CreditCard,
Money) " + "Card: " + card + " Amount: " + amount);
}
}
Автоматическая компоновка аспектов и традиционных модулей программы — компонентов является ключевым свойством АОП, которое определяет основное преимущество данной технологии: делает возможной инкапсуляцию сквозной функциональности в отдельных программных модулях.

Автоматизированная компоновка аспектов и компонентов является мощным средством генерации кода и в общем случае гарантирует, что аспект будет применен ко всем модулям-компонентам, которые он затрагивает, чего сложно добиться, если вносить сквозную функциональность в модули (вручную). Реализация автоматической компоновки аспектов и компонентов во многом определяет возможности той или иной аспектно-ориентированной платформы. В настоящее время обсуждаются два подхода к интеграции аспектов:
- Статическая интеграция на этапе компиляции
- Динамическая интеграция на этапе выполнения программы
Подходы к интеграции аспектов определяются языком, поддерживающим АОП, и детально изложены в [2] .
Преимущества использования АОП
АОП помогает избежать вышеупомянутых проблем, вызванных запутанным и рассредоточенным кодом. Ниже представлены дополнительные преимущества предоставляемые АОП:
- Улучшение декомпозиции системы на отдельные модули: АОП позволяет инкапсулировать функциональность, которая не может быть представлена в виде отдельной процедуры или компонента. АОП позволяет реализовать каждое требование отдельно с минимальным связыванием, в результате получается модуль, содержащий данное требование к системе без внешних лишних зависимостей, даже если это требование — сквозная функциональность. При такой реализации модули содержат минимальное количество дублируемого кода. Поскольку каждое требование реализуется отдельно, это позволяет избежать запутанного кода. В результате получается система, которую легче понимать и поддерживать.
- Упрощение сопровождения программной системы и внесения в нее изменений: Так как могут существовать модули, на которые могут воздействовать аспекты, становится достаточно легко добавлять новую функциональность путем создания новых аспектов. Более того, если добавляется новый модуль в систему, то существующие аспекты начинают воздействовать и на него без дополнительных усилий. При использовании АОП системный архитектор может отложить решения, касающиеся потенциально возможных требований, поскольку впоследствии эти решения смогут быть реализованы как отдельные аспекты, что не затронет существующую функциональность.
- Появление возможностей повторного использования кода, реализующего сквозную функциональность: Следует из того, что при использовании АОП сквозная функциональность может быть реализована в виде аспектов и в виде слабосвязанных модулей.
Технология вынесения сквозной функциональности в отдельные аспектные модули стала важным эволюционным шагом в развитии таких концепций как абстракция и повторное использование программного кода. Идеи абстракции и повторного использования кода занимают одно из центральных мест в программировании.
Абстракция — это метод, с помощью которого разработчики решать сложные проблемы, последовательно разбивая их на более простые. Затем можно использовать имеющиеся готовые решения полученных типовых простых проблем в качестве строительных блоков, из которых разработчики получают решения, пригодные для реализации повседневных сложных проектов [7] .
Повторное использование не менее важно для разработки ПО, так как этот фактор является целью, к которой устремлена технология разработки по своей природе [7]. В процессе эволюции методологий разработки ПО было придумано несколько методов повторного использования кода и концепций разработки ПО:
- Раньше других появился метод повторного использования, основанный на технологии "скопируй и вставь", или, проще говоря, на вставке в новые программы фрагментов ранее созданных программ. Данный подход является абсолютно неэффективным, кроме того, он не дает никаких сколько-нибудь заметных качественных преимуществ с точки зрения абстракции кода.
- Более гибкий метод повторного использования состоит в повторном использовании алгоритмов. В соответствии с этим методом, разработчик может использовать любой однажды разработанный алгоритм.
- Функциональное повторное использование программного кода и повторное использование структур данных позволяют обеспечить непосредственное повторное использование абстракции кода.
Двумя расширениями концепции повторного использования кода являются библиотеки функций и API. Они представляют разработчику получить полный пакет функциональности, доступный для всех последующих приложений без необходимости копирования программного кода из приложения в приложение [7].
В ходе развития объектно-ориентированных языков программирования был совершен огромный скачок вперед в области абстракции и повторного использования программного кода. С помощью ООП была создана целая плеяда высокопроизводительных методов его создания. Однако рост сложности программных систем привел к тому, что появилась необходимость в повторном использовании сквозной функциональности. Поэтому появилась потребность в новом подходе повторного использования кода, который бы охватил этот тип функциональности и решил бы проблемы, которые не могли решить предыдущие подходы.
Для иллюстрации важности повторного использования кода в таблице 1 приведена сравнительная характеристика из [7] различных подходов к повторному использованию.
| Метод | Повторное использование | Абстракция | Универсальность подхода |
|---|---|---|---|
| Копирование и вставка | Очень плохо | Отсутствует | Очень плохо |
| Структуры данных | Хорошо | Тип данных | Средне — хорошо |
| Функциональность | Хорошо | Метод | Средне — хорошо |
| Типовые блоки кода | Хорошо | Типизируемая | Хорошо |
| Алгоритмы | Хорошо | Формула | Хорошо |
| Классы | Хорошо | Данные + Метод | Хорошо |
| Библиотеки | Хорошо | Функции | Хорошо — очень хорошо |
| API | Хорошо | Классы утилит | Хорошо — очень хорошо |
| Компоненты | Хорошо | Группы классов | Хорошо — очень хорошо |
| Шаблоны проектирования | Отлично | Решения проблем | Очень хорошо |
| Сквозная функциональность | Средне — хорошо | Отсутствует | Плохо |
| Сквозная функциональность | Хорошо | Аспект | Очень хорошо |
В столбце "Абстракция" данной таблицы указаны сущности, для которых выполняется абстракция, а в столбце "Универсальность подхода" показано то, насколько легко применить существующий метод, не прибегая к изменениям или переработке кода. Показатель степени повторного использования очень сильно зависит от эффективности применения того или иного метода на практике.
Можно сказать что, слабосвязанная реализация сквозной функциональности — это ключ к реальному повторному использованию кода. АОП позволяет получить менее связанную реализацию, чем ООП. При использовании этой парадигмы программирования должно тратиться меньше времени на сопровождение и внесение изменений в готовый программный код, поэтому роль АОП становится все важнее.
АОП не является заменой существующих технологий. Наоборот, также как процедурное программирование используется в объектно-ориентированном программировании (ООП) для реализации поведения объектов, АОП использует существующие подходы для реализации своих модулей — аспектов, то есть исполняет роль расширения, позволяющего обеспечить модуляризацию сквозной функциональности. В зависимости от технологии и языка программирования соответствующая реализация АОП будет обладать различными возможностями.
Недостатки аспектного подхода
В настоящий момент аспектно-ориентированный подход обладает рядом недостатков:
- Не полностью проработана методология АОП-разработки программ. В данный момент законченный и оттестированный компилятор имеется только для нескольких языков, что ограничивает применение данной технологии. С другой стороны, реализация концепций АОП только на уровне языковых расширений представляется неполной и неэффективной. Более перспективным стоит считать проникновение самих базовых идей АОП и использование их на различных уровнях средств разработки — языков (С++, Pascal), платформ (.NET, Java), и технологий (COM, CORBA и т.д.), по аналогии с уже традиционным использованием ООП.
- Недостаточно качественная реализация расширений языков. В настоящее время существующие реализации АОП-расширений для различных языков и платформ различаются по своим возможностям, но можно выделить общие черты: каждая реализация АОП должна предоставить механизм описания логики сквозной функциональности и механизм описания точек программы, в которых данная логика будет применяться. Однако каждая реализация по-своему определяет виды точек связывания, в которых можно применить аспект, а также решает вопрос их описания, что затрудняет понимание и использование общих принципов АОП.
- Недостаточно проработан механизм привязки аспектов к компонентам. В распространенных в настоящее время АОП-реализациях точки связывания описываются в терминах программных конструкций — классов, методов, полей класса. При этом получается тесная связь между аспектом и компонентом, к которому он применяется. Логика привязки аспекта к данной точке кода выражена неявно и целиком определяется программной конструкцией, в терминах которой построено описание. Тесная связь между аспектом и компонентом делает аспект зависимым от компонента и при этом нарушается одна из основных идей АОП — независимость компонентов от применяемых к нему аспектов.
- Не полностью исследованы случаи, когда аспекты удобно и целесообразно было бы применить. Эту проблему частично решает данная статья.
AspectJ как одна из реализаций АОП
АОП можно поддерживать в рамках уже существующих языков. Так, в частности, исследовательский центр Xerox PARC разработал систему AspectJ, поддерживающую АОП в рамках языка Java. Этот пакет встраивается в такие системы разработки, как Eclipse, Sun ONE Studio, Forte 4J и Borland JBuilder. В данной работе AspectJ был выбран из-за того, что данная реализация АОП обладает наиболее широкими возможностями.
AspectJ — это простое и практическое расширение языка Java, которое добавляет к Java возможности предоставляемые АОП. Пакет AspectJ состоит из компилятора (ajc), отладчика (ajdb), и генератора документации (ajdoc). Поскольку AspectJ является расширением Java, то любая программа, написанная на Java, будет правильной с точки зрения семантики AspectJ. Компилятор AspectJ выдает байт-код совместимый с виртуальной машиной Java. Поскольку в качестве базового языка для AspectJ был выбран язык Java, то он унаследовал от Java все преимущества и спроектирован таким образом, что будет легко понятен разработчикам Java. Добавленные расширения касаются в основном способов задания правил интеграции аспектов и java-объектов. Данные правила выражаются в ключевых понятиях AspectJ:
- JoinPoint — строго определенная точка выполнения программы, ассоциированная с контекстом выполнения (вызов метода, конструктора, доступ к полю класса, обработчик исключения, и т.д.)
- Pointcut — набор (срез) точек JoinPoint удовлетворяющих заданному условию.
- Advice — набор инструкций языка java, выполняемых до, после или вместо каждой из точек выполнения (JoinPoint), входящих в заданный срез (Pointcut)
- Aspect — основная единица модульности AspectJ. В аспектах задаются срезы точек выполнения (Pointcut) и инструкции, которые выполняются в точках выполнения (Advice)
- Introduction — способность аспекта изменять структуру Java-класса путем добавления новых полей и методов, так и иерархию класса.
Pointcut и Advice определяют правила интеграции. Аспект — единица, напоминающая класс в ООП, соединяет элементы pointcut и элементы advice вместе, и формирует модуль на срезе системы.
Рассмотрим пример, на котором можно понять, как язык AspectJ реализует принципы АОП. В качестве примера возьмем модель простого графического редактора. Данный графический редактор может работать с двумя типами графических элементов — точкой и линией. Диаграмма классов графического редактора представлена на рисунке 8. Классы Point и Line реализуют интерфейс FigureElement содержащий метод перемещения фигуры. Операциями, влияющими на обновление экрана, являются операции перемещения фигур. После перемещения фигуры необходимо обновить изображение (Display). Обновление изображения в данном случае является сквозной функциональностью, которая должна вызываться при некоторых условиях (изменении положения фигур).

Класс Line содержит в себе 2 экземпляра класса Point. Классы, реализующие точку и линию, могут быть представлены в следующем виде:
class Line implements FigureElement{
private Point p1, p2;
Point getP1() { return p1; }
Point getP2() { return p2; }
void setP1(Point p1) { this.p1 = p1; }
void setP2(Point p2) { this.p2 = p2; }
void moveBy(int dx, int dy) { ... }
}
class Point implements FigureElement {
private int x = 0, y = 0;
int getX() { return x; }
int getY() { return y; }
void setX(int x) { this.x = x; }
void setY(int y) { this.y = y; }
void moveBy(int dx, int dy) { ... }
}
Предположим, клиент переместил фигуру на 2 пункта, при этом отработал метод lineInst.moveBy(2,2). На схеме вызываемых функций (рисунок 9) можно увидеть, как происходит работа этого метода. В теле вызываемого метода moveBy()содержится логика по перемещению фигуры; метод имеет входные параметры, возвращаемое значение и набор инструкций языка, среди которых находится вызов аналогичного метода у двух точек — элементов класса Line. Метод moveBy() у класса Point, также имеет входные параметры, возвращаемое значение и набор инструкций языка. Каждая строго определенная точка выполнения программы, ассоциированная с контекстом выполнения, с учетом ограничений языка AspectJ является JoinPoint-ом. Выполнение метода moveBy() у класса Line, вызов метода moveBy у Point — являются точками JoinPoint.

Здесь необходимо ввести различие в терминологию точек выполнения программы в рамках AspectJ. Точки выполения бывают двух типов — точка выполнение метода, которая представляет собой выполнение всего тела метода, и точка вызова метода, которая представляет собой точку непосредственного вызова метода. Другими словами тело метода может быть представлено точкой со стороны клиента либо только вызов метода может быть представлен как точка.
Язык AspectJ позволяет описывать несколько типов подобных точек выполнения программы.
- Вызов методов и конструкторов
- Выполнение методов и конструкторов
- Доступ к полям класса
- Обработка исключительных ситуаций
- Статическая и динамическая инициализация классов
При вызове клиентом метода moveBy все точки выполнения (joinPoint) находятся внутри потока управления, начиная с точки удовлетворяющей условию. Средствами языка AspectJ можно влиять на поток управления, описав соответствующий поток управления как набор точек pointcut.

Конструкцией языка позволяющей описывать набор точек JoinPoint удовлетворяющих заданному условию является pointcut. Например, call(void Line.setP1(Point)) соответствует каждой точке выполнения программы, если она является вызовом метода с сигнатурой "void Line.setP1(Point)". При конструировании подобных срезов можно использовать логические операции &&, || и !, что дает большую гибкость при описании наборов точек выполнения программы. Срезы точек могут иметь имя, что позволяет их повторно использовать при конструировании других срезов и описании набора инструкций Advice.
Advice — набор инструкций языка java, выполняемых до, после или вместо каждой из точек выполнения, входящих в заданный срез, набор инструкций, выполняемый в некоторой точке выполнения по определенным правилам. Язык AspectJ позволяет описывать подобные инструкции по следующим правилам:
- before. Набор инструкций выполняется перед выполнением инструкций входящих в описываемую точку выполнения.
- afterreturning. Набор инструкций выполняется после возвращения значения из описываемой точки выполнения.
- afterthrowing. Набор инструкций выполняется после возникновения исключительной ситуации в описываемой точке выполнения.
- after. Набор инструкций выполняется после возвращения из описываемой точки выполнения в любом случае.
- around. Набор инструкций выполняется вместо описываемой точки выполнения.
Приведенные правила формирования набора выполняемых инструкций в точке выполнения программы дают много возможностей при встраивании сквозной функциональности. При этом сложности, которые возникают при наличии в системе сквозной функциональности описанные выше, исчезают. При этом в системе нет запутанного и рассредоточенного кода сквозной функциональности мешающей пониманию сущности каждого конкретного компонента.
Аспект — единица модульности AspectJ. В аспектах задаются срезы точек выполнения и инструкции, которые выполняются в точках выполнения. Аспект имеет сходство с классом — аспект может содержать методы и поля, расширять другие классы или аспекты или реализовывать интерфейсы.
Далее представлено два варианта кода рассматриваемого графического редактора — с использованием языка AspectJ и без него. В варианте без использования AspectJ видно, что при изменении координат фигур в методы, отвечающие за эту функциональность, встроен код реализующий обновление дисплея. В примере с использованием аспектного подхода классы содержат код, отвечающий только за свою логику. Набор инструкций, реализующий сквозную функциональность "обновление дисплея" находится в аспектном модуле — aspect DisplayUpdating. На этом примере наглядно показано основное преимущество аспектного подхода — локализация сквозной функциональности в отдельных модулях и встраивание подобной функциональности в требуемые участки системы.
Пример кода графического редактора без использования языка AspectJ:
class Line implements FigureElement {
private Point p1, p2;
Point getP1() { return p1; }
Point getP2() { return p2; }
void setP1(Point p1) {
this.p1 = p1;
Display.update(this);
}
void setP2(Point p2) {
this.p2 = p2;
Display.update(this);
}
}
class Point implements FigureElement {
private int x = 0, y = 0;
int getX() { return x; }
int getY() { return y; }
void setX(int x) {
this.x = x;
Display.update(this);
}
void setY(int y) {
this.y = y;
Display.update(this);
}
}
Пример кода графического редактора с использованием языка AspectJ:
class Line implements FigureElement {
private Point p1, p2;
Point getP1() { return p1; }
Point getP2() { return p2; }
void setP1(Point p1) {
this.p1 = p1;
}
void setP2(Point p2) {
this.p2 = p2;
}
}
class Point implements FigureElement {
private int x = 0, y = 0;
int getX() { return x; }
int getY() { return y; }
void setX(int x) {this.x = x; }
void setY(int y) {this.y = y; }
}
aspect DisplayUpdating {
pointcut move(FigureElement figElt):
target(figElt) &&
(call(void FigureElement.moveBy(int, int) ||
call(void Line.setP1(Point)) ||
call(void Line.setP2(Point)) ||
call(void Point.setX(int)) ||
call(void Point.setY(int)));
after(FigureElement fe) returning: move(fe) {
Display.update(fe);
}
}
При разработке программных систем с использованием средств языка AspectJ можно полностью следовать трем принципам разработки аспектного подхода: выделять в отдельные модули сквозную функциональность — провести аспектную декомпозицию; реализовать каждое требование отдельно; интегрировать аспекты в программный код. В примере с графическим редактором на этапе аспектной декомпозиции была выявлена сквозная функциональность — обновление дисплея. Данное требование было реализовано в аспектном модуле DisplayUpdating. В этом аспекте определяется срез точек move(..), которые включают в себя точки выполнения программы, после которых будет встроена требуемая сквозная функциональность. На рисунке 11 схематично изображен аспект, находящийся на срезе модели графического редактора.

Интеграции аспектов (weaving) происходит в момент компиляции. Модель построения готовой программной системы при использовании компилятора ajc изображена на рисунке 12.

После компиляции получаем готовую систему с интегрированной сквозной функциональностью по правилам, описанным в аспектных модулях.
Другие реализации АОП
В данный момент, кроме представленного выше языка AspectJ, известно несколько систем реализующих принципы АОП:
- ANGIE Generation Now! Предоставляет новый структурированный язык для реализации различных генераторов, в том числе интеграторов аспектов.
- AspectC++. Это аспектно-ориентированное расширение языка С++.
- AspectR. Система аспектно-ориентированного программирования на основе языка Ruby, которая позволяет в классах оборачивать код вокруг существующих методов.
- AspectS. Это один из первых прототипов, который дает возможность аспектно-ориентированного программирования в среде Squeak/Smalltalk.
- Apostle система близкая к AspectJ, которая является АОП расширением языка Smalltalk.
- AspectC. Простое АОП расширение языка С, похожее на AspectJ.
- AspectC# реализация, добавляющая поддержку аспектов к языку C#.
- Caesar. Аспетно-ориентированный язык программирования, который фокусируется на многостороннем подходе к декомпозиции и повторном использовании аспектного кода.
- DemeterJи DJ. Делаются шаги в сторону структурной инкапсуляции сквозной функциональности.
- Hyper/J. Предоставляет поддержку "многомерного" разделения и интеграции концепций для стандартной java-платформы.
- JAC. Представляет собой основу, написанную на языке Java, для создания распределенных аспектно-ориентированных программных систем.
- JMangler. Представляет собой каркас на языке Java для преобразования программ на этапе загрузки, с поддержкой бесконфликтной композиции из независимой друг от друга разработанных аспектов (реализуемых в виде преобразующих компонентов системы JMangler). Также есть возможность интеграции аспектов в базовые классы Java .
- MixJuice это расширение языка Java, базирующегося на механизме поиска различий в модулях.
- MozartProgramming System это расширенная платформа для разработки "разумных и интеллектуальных" распределенных приложений.
- ObjectEverywhere — статья, описывающая АОП каркас, базирующийся на COM и языке Delphi.
- PROSE — аспектно-ориентированная платформа, базирующаяся на виртуальной машине Java, и позволяющая динамическое встраивание и вычленение аспектного кода.
- Pythius проект с открытым исходным кодом, добавляющий принципы АОП к языку Python.
- SmartTools — аспектно и XML ориентированный генератор семантических каркасов.
- UMLAUT представляет собой систему, которая позволяет встраивать многомерные высокоуровневые UML проектные модели в модели подходящие для каждого конкретного случая реализации.
- Weave.NET проект, направленный на исследование механизма поддержки АОП без привязки к конкретному языку программирования внутри компонентной модели .NET Framework.
Критерии сравнения аспектной реализации с объектно-ориентированной
Для объективного анализа представленных вариантов использования аспектных реализаций по сравнению с объектно-ориентированными реализациями необходимо ввести критерии сравнения этих реализаций.
В процессе развития информационных технологий и программирования в частности были предприняты попытки дать количественную оценку характеристикам качества программного обеспечения путем разработки численных показателей, для чего осуществляется их формализация вводом метрик. Применение метрик позволяет упорядочить разработку, испытания, эксплуатацию и сопровождение программного продукта. В зависимости от характеристик и особенностей показателя качества применяются различные виды метрик и шкал для измерения показателей.
Первый вид метрик — это метрики, которым соответствует интервальная шкала, характеризуется относительными величинами или реально измеряемыми физическими показателями. Например, используя этот вид метрик можно сказать, что одна программа труднее или эффективнее другой программы на 10 единиц.
Второй вид метрик — это метрики, которым соответствует порядковая шкала. Она позволяет ранжировать некоторые характеристики путем сравнения с опорными значениями, т.е. измерение по этой шкале фактически определяет взаимное положение конкретных модулей или программных систем. Для объекта измерения устанавливается приоритетность признаков.
Третий вид метрик — это метрики, которым соответствует номинальная или категорированная шкала. Данный вид метрик характеризует наличие рассматриваемого свойства или признака у объекта, в частности у программного модуля, без учета градаций по данному признаку. Фиксируется, есть или нет данное качество в зависимости от наличия рассматриваемого показателя у комплекса программ (например, наличие структурирования, гибкости, простоты освоения). Например, такую характеристику как сложность модуля можно количественно оценить значениями этой метрики: [нетрудная для понимания], [умеренно трудная для понимания], [трудная для понимания], [очень трудная для понимания].
Существующие качественные оценки программных систем можно сгруппировать по нескольким интересующим в данной работе направлениям:
- Оценки топологической и информационной сложности программ.
- Оценки уровня языковых средств и их применения.
- Оценки трудности восприятия и понимания программных текстов, ориентированные на психологические факторы, существенные для сопровождения и модификации программ.
Топологическая и информационная сложность программного модуля
Традиционной характеристикой размера программ является количество строк исходного текста. Оценка размера программ есть оценка по номинальной шкале, на основе которой определяются только категории программ без уточнения оценки для каждой категории. К данной группе оценок можно отнести метрику Холстеда. Основу этой метрики составляют четыре измеряемые характеристики программы:
NUOprtr (Number of Unique Operators) — число уникальных операторов программы, включая символы — разделители, имена процедур и знаки операций (словарь операторов);
NUOprnd (Number of Unique Operands)- число уникальных операндов программы (словарь операндов);
Noprtr (Number of Operators) — общее число операторов в программе;
Noprnd (Number of Operands) — общее число операндов в программе.
Опираясь на эти характеристики, получаемые непосредственно при анализе исходных текстов программ, М. Холстед вводит следующие оценки:
- словарь программы (Halstead Program Vocabulary) HPVoc = NUOprtr + NUOprnd;
- длина программы (Halstead Program Length) HPLen = Noprtr + Noprnd;
- объем программы (Halstead Program Volume) HPVol = HPLen log2 HPVoc.
Далее Холстед вводит сложность программы (Halstead Difficulty), которая вычисляется как HDiff = NUOprtr/2* (NOprnd / NUOprnd)
Используя HDiff Холстед вводит оценку HEff (Halstead Effort) Heff = HDiff* HPVol, с помощью, которой описывается усилия программиста при при разработке.
Вторая наиболее представительная группа оценок сложности программ — метрики сложности потока управления программ. Как правило, с помощью этих оценок оперируют либо плотностью управляющих переходов внутри программ, либо взаимосвязями этих переходов. И в том и в другом случае программа представляется в виде управляющего графа. Впервые графическое представление программ было предложено Маккейбом. Основной метрикой сложности он предполагает считать цикломатическую сложность графа программы, или, как ее еще называют, цикломатическое число Маккейба, характеризующее трудоемкость тестирования программы.
Для вычисления цикломатического числа Маккейба CC (Cyclomatic Complexity) применяется формула:
CC = L — N + 2P,
где L — число дуг ориентированного графа;
N — число вершин;
P — число компонентов связности.
К метрикам сложности также относится:
- NORM (Number Of Remote Methods) — количество вызываемых удаленных методов. При формировании значения этой метрики просматриваются все конструкторы и методы класса, и подсчитывается количество вызываемых удаленных методов. Удаленным методом является метод, который не определен в классе и его родителях.
- RFC (Response For Class) Отклик на класс — количество методов, которые могут вызываться экземплярами класса. Эта метрика вычисляется как сумма количества локальных методов и количества удаленных методов.
- WMPC1 (Weighted Methods Per Class 1) — взвешенная насыщенность класса — дает относительную меру его сложности; если считать что все методы имеют одинаковую сложность, то это будет просто число методов в классе. Эта метрика определяется суммой сложности всех методов класса, где каждый метод взвешивается подсчетом его цикломатического числа. Количество методов и их сложность связаны для определения времени и усилий, которые потребуются для разработки и поддержки класса. Вообще, класс, который имеет большее количество методов среди классов одного с ним уровня, является более сложным; скорее всего, он специфичен для данного приложения и содержит наибольшее количество ошибок. Для расчета данной метрики используются только методы определенные в конкретном классе, все методы, наследуемые от родительского класса, не включаются.
- WMPC2 (Weighted Methods Per Class 2) Эта метрика является мерой сложности класса, полагающая что класс с большим количеством методов чем другой является более сложным, и что метод с большим количеством параметров также является более сложным. Для расчета данной метрики используются только методы определенные в конкретном классе, все методы, наследуемые от родительского класса, не включаются.
- LOCOM1 (Lack Of Cohesion Of Methods 1) — недостаток связности методов. Связность — это степень взаимодействия между элементами отдельного модуля, характеристика его насыщенности. Наименее желательной является связность по случайному принципу, когда в одном модуле собираются совершенно независимые абстракции. Связанность объектов — мера их взаимозависимости. Разработчики стремятся спроектировать не зацепленные (то есть слабо связанные) объекты, поскольку они имеют больше шансов на повторное использование. Для каждой пары методов класса определяется набор полей, к которым они имеют доступ. Если множество полей имеет доступ к непересекающемуся множеству полей класса, то число P увеличивается на 1. Если оба метода используют хотя бы одно общее поле, то параметр Q увеличивается на 1. После рассмотрения каждой пары методов результат вычисляется как RESULT = (P > Q) ? (P — Q) : 0. Высокое значение этой метрики говорит о высокой связности методов — это означает, что потребуются большие усилия при тестировании этих методов, так как методы могут воздействовать на одни и те же атрибуты класса. Это также говорит о низкой готовности к повторному использованию. Метрика была определена Чидамбером и Кемерером (Chidamber & Kemerer) в 1993.
- LOCOM2 (Lack Of Cohesion Of Methods 2) Связанность методов — мера насыщенности абстракции. Класс, который может вызывать существенно больше методов, чем равные ему по уровню классы, является более сложным. У класса с низкой связанностью можно подозревать случайную или неподходящую абстракцию: такой класс должен быть переабстрагирован на несколько классов или его обязанности должны быть переданы другим существующим классам. Метрика подсчитывает процентное отношение методов, не имеющих доступа к специфичным атрибутам класса ко всем атрибутам данного класса. Высокое значение связности означает, что класс хорошо спроектирован. Хорошо связный класс имеет тенденцию к высокой степени инкапсуляции, тогда как отсутствие связности уменьшает инкапсуляцию и увеличивает сложность.
- LOCOM3 (Lack Of Cohesion Of Methods 3) Измеряет степень различия методов в классе по атрибутам. Рассмотрим множество методов M1, M2, ... , Mm. Эти методы имеют доступ к набору атрибутов данных класса A1, A2, ... , Aa. Положим a(Mk) = число атрибутов с которым работает метод Mk , а m(Ak) = число методов которые имеют доступ к атрибуту Ak. Тогда LOCOM3 = (1/a * SUM1a(m(Ai)-m))/(1-m)*100. Данная метрика была предложена Хендерсоном-Шеллером в 1995. Низкое значение этой меры говорит о хорошей декомпозиции в классе выражаемой в его простоте и понятности и готовности к повторному использованию. Высокое значение отсутствия связности увеличивает сложность, повышает вероятность ошибок в процессе разработки.
В эту группу метрик также попадают базовые метрики:
- LOC (Lines Of Code) количество строк кода
- NOC (Number Of Classes) количество классов
Улучшение значений метрических характеристик из этой группы говорит о положительном эффекте от применения оптимизирующего метода/подхода. Например, уменьшение значений метрик Холстеда, цикломатической сложности говорит о том, что полученная реализация в целом лучше по соображениям топологической сложности. Уменьшение уровня связности, взвешенности метода на класс или отклика класса говорит об улучшенной функциональной декомпозиции. Уменьшение количества строчек кода также говорит о положительном эффекте, если ту же функциональность можно изложить при помощи нового улучшающего подхода за меньшее количество строк.
Данная группа метрик будет собрана при помощи автоматизированного программного средства — TogetherJ. Настоящая (6 версия) продукта TogetherJ не поддерживает сбор метрик для аспектных модулей языка AspectJ, следовательно, в случае аспектной реализации собранные метрики будут подсчитываться только для компонентов реализованных на языке Java, то есть для "чистых" компонентов без учета сквозной функциональности. Можно утверждать, что данная грубая оценка будет верна в случае не больших тестовых проектов, где отношение разницы кода объектной реализации и кода компонента к коду сквозной функциональности (например, в терминах LOC) не будет значительно превышать 1, что будет говорить о небольшом количестве вынесенной сквозной функциональности, но достаточном для того, чтобы оценить модульность полученных компонент в терминах топологической и информационной сложности. Сравнение метрик компонента до и после вынесения сквозной функциональности, позволит оценить, в какую сторону изменилась реализация данного компонента. Для учета топологической сложности вынесенной сквозной функциональности и для анализа больших реальных проектов необходимо вырабатывать другие метрики анализа.
Уровень языковых средств и их применения
Для оценки степени применимости аспектного подхода к конкретной программной системе введем следующую метрику:

где P — размер кода программной системы без применения аспектного подхода;
Q — размер кода компонентов реализующих основную логику системы;
Z — размер кода аспектных модулей реализующих сквозную функциональность.
В результате любое число больше чем 1 говорит о положительном эффекте от применения аспектного подхода к данной программной системе.
Трудность восприятия и понимания программных текстов
Данные метрики не имеют количественной оценки и характеризуют наличие рассматриваемого свойства или признака у объекта, в частности у программного модуля, без учета градаций по данному признаку.
- Модульность. Сквозная функциональность может находиться в аспектных модулях, не затрагивая при этом модули, реализующие основные функциональные требования, то есть все зависимости между кодом реализованном в аспекте и компонентами-участниками локализованы в коде аспекта. Участвующие компоненты абсолютно свободны от контекста и как следствие эти компоненты полностью готовы к повторному использованию. В [9] модульность описывается как "свойство системы, которая была разложена на внутренне связные, но слабо связанные между собой модули". Правильное разделение программы на модули является почти такой же сложной задачей, как выбор правильного набора абстракций.
- Готовность к повторному использованию. Компоненты, которые не связаны между собой специфической для данной программной системы сквозной функциональностью, легко могут быть использованы повторно. Причем в разных частях системы один компонент может играть разные роли, в зависимости от аспектных модулей. Кроме того, код аспектов и их составных частей также можно повторно использовать посредством наследования и это дает еще более широкие возможности.
- Понятность кода. Поскольку сквозная функциональность вынесена в аспектные модули, композиция классов в системе выглядит более понятной, так как она не обременена кодом, не относящимся непосредственно к сущности, которую представляет класс.
Анализируя данный набор метрик для каждого конкретного случая можно сделать вывод о применимости или неприменимости аспектного подхода на базе приведенных далее примеров.
Варианты применения АОП на разных этапах ЖЦ
Приведем варианты использования аспектно-ориентированного подхода на различных этапах жизненного цикла программной системы.
Использование АОП на этапе проектирования
Как отмечает Дейкстра, "Способ управления сложными системами был известен еще в древности — divide et imperia (разделяй и властвуй)" [10]. При проектировании сложной программной системы необходимо разделять ее на все меньшие и меньшие подсистемы, каждую из которых можно совершенствовать независимо. Декомпозиция вызвана сложностью программирования системы, поскольку именно эта сложность вынуждает делить пространство состояний системы. При построении системы необходимо провести алгоритмическую декомпозицию, объектно-ориентированную декомпозицию и аспектно-ориентированную. Объектно-ориентированная декомпозиция позволяет выделить из требований компоненты, описывающие проблемную область, алгоритмическая — описать взаимодействие этих компонентов и сконцентрировать внимание на порядке происходящих событий [9]. Аспектно-ориентированная декомпозиция на этапе анализа и проектирования позволяет выделить сквозную функциональность на разных уровнях абстракции и локализовать ее в отдельных модулях-аспектах. Такие аспекты являются неотъемлемой частью результирующей программной системы.
Реализация протокола взаимодействия объектов для шаблонов проектирования
Шаблоны проектирования — это весьма ценное инструментальное средство в арсенале разработчика ПО, позволяющее существенно повысить эффективность создаваемого кода. Под шаблоном проектирования в [6] понимается следующее: "Шаблон проектирования — это описание взаимодействия компонентов, адаптированных для решения общей задачи проектирования в конкретном контексте. Шаблон проектирования именует, абстрагирует и идентифицирует ключевые аспекты структуры общего решения, которые и позволяют применить его для создания повторно используемого дизайна. Он вычленяет участвующие компоненты, их роль и отношения, а также функции. При описании каждого шаблона внимание акцентируется на конкретной задаче объектно-ориентированного проектирования".
Однако, исходя из приведенного определения и личного опыта применения шаблонов проектирования, можно сделать следующий вывод: несмотря на то, что шаблоны проектирования являются средством, увеличивающим способность кода к повторному использованию, и абстрактная модель шаблона проектирования может быть неоднократно использована, конкретную реализацию шаблона повторно удается использовать очень редко, так как она сильно зависит от контекста применения. К тому же, участвующие в реализации шаблона компоненты использовать в другом контексте практически невозможно, так как они сильно привязаны друг к другу в контексте поведения реализуемого шаблона. Реализация приводит к "растворению в коде" абстрактной модели шаблона и потере модульности, при этом достаточно сложно из конкретной реализации выделить абстрактную модель шаблона. Добавление и удаление реализации шаблона проектирования из кода системы является достаточно сложной задачей, которая влечет за собой большой рефакторинг программного кода.
Каталог шаблонов проектирования [6] включает в себя 23 известных шаблона, их назначение, сценарии, иллюстрирующие задачи проектирования и то, как они решаются при помощи конкретного шаблона, описание ситуаций, в которых можно применить шаблон, и графическое представление компонентов в шаблоне. В работе [5] проведен сравнительный анализ аспектной реализации шаблонов проектирования и сравнение с "традиционной" объектно-ориентированной реализацией. В данной работе показаны улучшения для 17 из 23 шаблонов. Улучшения выражаются в терминах метрик с номинальной шкалой — модульности кода, готовности к повторному использованию, и понятности кода. Под модульностью кода в этой работе подразумевается локализация сквозной функциональности — в данном случае абстрактной модели шаблона, в коде аспекта. Под готовностью кода к повторному использованию — возможность использовать компоненты-участники и аспектные модули в других контекстах, так как для шаблона был разработан общий протокол взаимодействия компонентов-участников. Под понятностью кода подразумевается то, что код реализации не является запутанным. Это означает, что компоненты-участники могут быть заняты в нескольких шаблонах проектирования, шаблоны могут разделять между собой компоненты-участники. Также в этой работе введена еще одна метрика — способность к встраиванию, которая характеризует возможность удалить или добавить в систему аспектный код, реализующий данный шаблон, без усилий и модификации компонентов-участников шаблона, что говорит о готовности данных компонентов к повторному использованию в других контекстах. В таблице 2 приведены полученные в [5] результаты сравнения реализаций шаблонов проектирования.
| Имя шаблона | Модульность | Готовность к повторному использованию | Понятность кода | Способность к встраиванию |
|---|---|---|---|---|
| Facade | аспектная и объектная реализации идентичны | |||
| Abstract Factory | o | o | o | o |
| Bridge | o | o | o | o |
| Builder | o | o | o | o |
| Factory Method | o | o | o | o |
| Interpreter | o | o | o | |
| Template Method | # | o | o | # |
| Adapter | x | o | x | x |
| State | # | o | # | |
| Decorator | x | o | x | x |
| Proxy | # | o | # | # |
| Visitor | # | x | x | # |
| Command | # | x | x | x |
| Composite | x | x | x | # |
| Iterator | x | x | x | x |
| Flyweight | x | x | x | x |
| Memento | x | x | x | x |
| Strategy | x | x | x | x |
| Mediator | x | x | x | x |
| Chain of responsibility | x | x | x | x |
| Prototype | x | x | # | x |
| Singleton | x | x | x | |
| Observer | x | x | x | x |
Символом o в таблице показано отсутствие свойства у реализации, символом x — наличие свойства. Символ # означает наличие свойства с некоторыми ограничениями.
Рассмотрим пример реализации шаблона Observer. Шаблон Observer определяет зависимость "один ко многим" между объектами так, чтобы при изменении состояния одного объекта все зависящие от него оповещаются об этом и автоматически обновляются. Этот шаблон применяется в тех случаях, когда система обладает следующими свойствами:
- Существует, как минимум, один объект, рассылающий сообщения
- Имеется не менее одного получателя сообщений, причем их количество и состав может изменяться во время работы приложения.
Диаграмма классов шаблона Observer представлена на рисунке 14.

Составными частями данного шаблона являются
- Subject(Объект) Знает своих наблюдателей. Любое количество объектов может наблюдать за объектом. Обеспечивает интерфейс для присоединения и отсоединения объектов Observer.
- Observer(Наблюдатель) Определяет интерфейс модифицирования объектов.
- ConcreteSubject (Конкретный объект) Состояние, представляющее интерес для объектов ConcreteObserver. Посылает уведомление своим наблюдателям, когда его состояние изменяется.
- ConcreteObserver (Конкретный наблюдатель) Поддерживает ссылку на объект ConcreteSubject. Сохраняет состояние, которое должно остаться совместимым с состоянием объекта. Реализует Observer-а, модифицирующего интерфейс.
Затруднения при использовании данного шаблона вытекают из реализуемой в нем модели построенной на обмене сообщениями. Можно использовать как специальную, так и универсальную стратегию рассылки сообщений, каждая из которых имеет свои недостатки.
При использовании универсальной стратегии сложнее определить, что конкретно происходит с наблюдаемым объектом, поскольку сообщения носят универсальный характер. Кроме того, универсальная стратегия характеризуется избыточным потоком сообщений — некоторые события, пересылаемые наблюдателям, последними никак не обрабатываются, а лишь влекут за собой увеличение накладных расходов. В качестве примера применения данного шаблона и демонстрации увеличения накладных расходов при обработке лишних сообщений можно привести пример графического редактора, обсуждаемого ранее. Вызов обновления дисплея Display.update(this) может происходить, как и при вызове метода установки точки — setX, так и при вызове setXY или moveBy которые инкапсулируют в себе setX. Наконец универсальная стратегия требует дополнительных затрат на разработку классов наблюдателей, так как им нужно анализировать сообщения и вычленять из них необходимую информацию.
При специальной стратегии сообщения, направленные на решение узкой задачи обмена информацией наблюдаемого объекта с наблюдателями, выдвигают повышенные требования относительно программирования первого, поскольку он должен генерировать последовательности извещений при возникновении определенных условий. Это может повлечь за собой и увеличение сложности объектов-наблюдателей, так как им придется обрабатывать сообщения разных типов.
Для "традиционной" объектно-ориентированной реализации проведен расчет метрических характеристик при помощи автоматизированного программного средства. Значения представлены в таблице 3.
Рассмотрим аспектную реализацию данного шаблона. В структуре шаблона Observer можно выделить части, которые являются общими для всех экземпляров этого шаблона, также можно выделить специфичные для каждой конкретной реализации. На рисунке 15 представлена модель шаблона Observer реализованного средствами АОП. На диаграмме присутствует абстрактный аспект, который инкапсулирует в себе реализацию общих повторно используемых частей для всех реализаций шаблона и определяет поведение шаблона, конкретные расширения абстрактного аспекта содержат части специфичные для конкретной реализации.

Общими частями в структуре шаблона Observer являются:
- Существование ролей Subject и Observer. Роли реализованы как вложенные интерфейсы Subject и Observer в абстрактный аспект. Их главное назначение разделять роли Observer и Subject, для которых в абстрактном аспекте определяется поведение в шаблоне. Конкретные реализации шаблона установят эти роли на конкретные классы.
- Отображение Subject на Observer. Реализовано в виде хеш-таблицы (perSubjectObservers) связанных списков для хранения связи объектов типа Observer для каждого объекта Subject. Каждое расширение этого абстрактного аспекта будет иметь свой собственный экземпляр отображения. Внесение изменений в отображение Observer-Subject реализовано посредством методов addObserver() и removeObserver(), которые могут быть вызваны для установления соответствия между объектами Observer и Subject.
- Логика оповещения. В повторно используемом абстрактном аспекте логика оповещения определяется общая концепция, по которой могут измениться объекты Subject (abstract pointcut subjectChange), что должно привести к обновлению всех его обозревателей (вызов абстрактного метода updateObserver для всех обозревателей измененного объекта Subject). Здесь не вводится понятие того, что может привести к обновлению объекта Subject или то каким образом должны быть обновлены объекты Observer. Здесь определяется набор абстрактных точек выполнения программы, которые могут привести к изменению состояния объекта Subject и после изменения состояния вызвать оповещение соответствующих объектов Observer. Определяется абстрактный механизм обновления объектов обозревателей — выделяется сквозная функциональность на этом уровне абстракции.
Каждая конкретная реализация шаблона определяет собственный вид отслеживания взаимосвязей между объектами. В конкретной реализации абстрактного аспекта определяется следующее:
- Определяются компоненты, которые играют роли Subject и Observer в этой конкретизации шаблона Observer.
- Переопределяются точки выполнения программы, объявленные в родительском аспекте как абстрактные, влияющие на обновление связанных с Subject объектов Observer посредством выделения желаемого поведения из объектов, играющих роль Subject
- Реализуется механизм уведомления и обновления объектов обозревателей посредством конкретизации метода updateObservers()
На рисунке 15 представлено две различных реализации шаблона Observer с использованием компонентов Point, Line и Screen. В первом случае компоненты Point и Line играют роль Subject, а компонент Screen играет роль Observer. Во втором Point и Line играют роль Observer, а Display — Observer и Subject.
В аспектной реализации шаблона Observer весь код касающийся взаимосвязи между ролями Observer и Subject перемещен в аспекты и компоненты, играющие эти роли, не связаны друг с другом.
Для аспектно-ориентированной реализации также проведен расчет метрических характеристик, и результат расчета представлен в таблице 3.
| Метрика | Объектно-ориентированная реализация | Аспектно-ориентированная реализация |
|---|---|---|
| Цикломатическая сложность (CC) | 11 | 7 |
| Сложность программы (HDiff) | 16 | 9 |
| Усилия разработчика (HEff) | 7319 | 3563 |
| Длина программы (HPLen) | 283 | 190 |
| Словарь программы (HPVoc) | 37 | 35 |
| Объем программы (HPVol) | 1434 | 898 |
| Количество строк кода (LOC) | 109 | 56 |
| Недостаток связности методов (LOCOM1) | 15 | 3 |
| Недостаток связности методов (LOCOM2) | 68 | 57 |
| Недостаток связности методов (LOCOM3) | 75 | 66 |
| Количество классов (NOC) | 5 | 3 |
| Общее число операндов в программе (Noprnd) | 149 | 96 |
| Общее число операторов в программе (Noprtr) | 134 | 94 |
