Движ

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

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

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

В статье аспект рассматривается на примере ООП (объектно-ориентированное программирование). Но вообще, есть такое понятие, как АОП (аспектно-ориентированное программирование).

Место, с которым взаимодействует аспект — методы реализации. Если программист следует принципам SOLID, каждый его метод реализует только одну обязанность. Например, getCost должен только возвращать сумму товаров корзины, а все остальное (например, логгер событий и проверка доступа) должно быть где-то в другом месте. Так вот, где-то в другом месте существует и аспект, он знает обо всех модулях и дает указания getCost, как считать сумму, учитывая условия всей среды, учитывая мнение всех участников процесса, учитывая условия конкретной сложившийся ситуации (например, рождественские скидки). Аспект может быть поддержан только теми реализациями, что позволяют влиять на свое поведение извне. Может получить так, что DbCart прослужит 10 лет развития проекта, так как поддерживает аспект, а CookieCart только 1 год, хотя обе реализации в общем-то полиморфны.

Место аспекта в приложении. Аспект проще всего увидеть и применить в приложениях с модульной архитектурой, там четко просматривается нужное место для него (DiC). Любой хороший модуль представляет из себя некую большую реализацию с узким горлышком (сервисом), именно в сервис должен инжектиться[1] аспект.

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

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

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

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

3. Связанность кода. Функциональные части системы должны быть слабо связаны между собой - например, аспект «Мохнатые уши» не должны зависеть от конкретной кошки и ее головы. Если мы однажды описали уши, то они должны быть полиморфны, то есть применимы к любому животному, которое соответствует интерфейсу Animal.

Данные критерии, сформулированные автором как «Критерии ДПС», являются отражением набора принципов, аббревиатура SOLID. SOLID-модули можно легко переносить из проекта в проект, тестировать, продавать и распространять в Open Source. Информация о всех модулях приложения и о их зависимостях друг от друга правильно хранить в том самом размытом «отдельном месте» приложения (DiC и ServiceLocator). В DiC хранится уникальность приложения, он формирует предметную область приложения. Как правило, в любой SOLID код можно заинжектить аспект.

Пример из жизни: предприятие автомойка. Здесь в корзину добавляются услуги. Но есть уникальные для этой предметной области требования: не используется мелочь, любые суммы округляются до десятков рублей. Вот это называется изменчивостью, ведь в других областях (например, в торговле бананами оптом) этот модуль ничего не округлял.

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

Инкапсуляция изменчивости. Чтобы мы могли как-то изменять поведение модуля из предметной области, где он установлен, необходимо отделить (инкапсулировать) всю изменчивость. Приложению необходимо дать возможность давать указания во время выполнения какого-либо действия в модуле. В случае с корзиной — в методе getCost мы должны вставить некий портал, куда приложение может получить доступ и подсказать, что число $cost сейчас должно быть на 10% меньше, чем есть на самом деле, ведь услугами хочет воспользоваться постоянный клиент со скидкой. А за клиентов отвечает другой модуль. Задачей приложения является проследить момент getCost, узнать у модуля Client, есть ли и у покупателя скидка, и дать нужные указания. Такие указания и называются аспектом.

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

Когда мы поняли, что такое изменчивость и аспект, нужно перейти к действию — инкапсулировать эту самую изменчивость, чтобы перенести из модуля в проект. Лучше всего реализацию аспекта можно представить в виде callback функции, которая подписана на триггер в каком-то сервисе. Функция получает данные и внедряет свои коррективы в поведение программы модуля здесь и сейчас. Пример из ServiceLocator фреймворка YII2.

Данный код выполняется в приложении до выполнения любых действий модулей, здесь мы просто подписываемся на события, которые еще не возникли. Само событие 'cost_calculate' встроено где-то внутри распространяемого модуля и указывает, что здесь может потребоваться указание извне.

Команда «yii::$app->get('cart')» указывает на инстанс компонента модуля, который является сервисом (singleton, узкое горлышко доступа, доступное глобально).

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

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



[1]

[1] Инжект здесь и далее используется для передачи смысла «провести инъекцию», «ввести и растворить», ввиду отсутствия адекватного синонима в русском языке.