Ацпект.

Сижу вот я на завалинке, читаю книжку про аспектное программирование. Блин, умные же люди бывают. И придумается им такое.

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

Так вот, возвращаясь к умным людям. Они, что же придумали. Что можно описать в отдельном файле что-нибудь типа — в начале всех функций класса A, и функций x,y класса B — делать проверочку прав и только если она прошла то вызывать саму функцию. Выходит, что и овцы целы (классы продолжают делать, что им положены) и волки сыты (авторизацию проводим).

Скажу сразу, что пока я не добрался до места в книге, где описано, как это реализовано. Какое-то странное предчувствие, что для языков с сложным синтаксисом и большим количеством разных изысков (читай C++) , движок это поддерживающий должен быть мягко говоря сложным.

Тем не менее, думаю, что это действительно следующий шаг за ООП. Хотя он конечно, призван не заменить, а дополнить его.

Интересно вообще, какого граница декомпозиции и компактности языков программирования? Думаю вопрос на самом деле не корректный, но тем не менее…

Просто, по большему счету фактически все развитие языков идет по следующему циклу

а. Решаем задачу

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

в. Изобретаем новые методы, решаем этими методами проблему из б)

г. Увеличиваем размер задач.  Переходим на пункт а)

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

Вот такая, вот сборка мыслей по поводу аспектного программирования.

Так что, очень рекомендую почитать что-то по этому поводу. Прекрасно прочищает мозги.

Кстати, у меня есть стандартный вопрос на собеседование — «Чем вам НЕ нравиться язык X (вставить нужное)?».Похоже, добавлю в свой список вопрос — «Чем вам НЕ нравиться ООП»? Вот будет забавно поглядеть, как люди буду дергаться и нервничать.

46 комментариев to “Ацпект.”

  1. Max:

    или неудачный пример или бред.
    Если нужно делать подобную проверку в каждой функции то это не значит что нужно дублировать код! Напиши выдели это в отдельную функцию и вызывай её в начале каждого метода. Если очень лень и человек в душе извращенец то можно написать тулзу которая сама будет вставлять вызов этой функции перед каждой из этих трёх, но ИМХО удобства это не добавляет до тех пор пока у нас не будет действительно ОГРОМНЫЙ проект где куча таких однотипных функций (но тут уже надо думать о адекватности архитектуры). Если это и весь смысл этого «аспектного» подхода, то похоже на очередные подпорки для гнилых и запущеных проектов и не более.

    • Nikolay:

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

      • Victor Ronin:

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

    • Dyatel:

      Аспект в основе именно и хочет заменить эту «отдельную функцию» и это и есть та «тулзу которая сама будет вставлять вызов этой функции перед каждой из этих трёх»

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

      Вобщем, мне не очень по душе, я люблю попроще вещи

      • Victor Ronin:

        Согласен основная проблема в этом — то что код не видишь и находится он где-то далеко.

        Хотя с наследование и полиморфизмом очень похожая ситуация. Так что это скорее вещи к которым надо просто привыкнуть.

    • Victor Ronin:

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

      >. Если очень лень и человек в душе извращенец то можно написать тулзу которая сама будет >вставлять вызов этой функции перед каждой из этих трёх, но ИМХО удобства это не >добавляет до тех пор пока у нас не будет действительно ОГРОМНЫЙ проект где куча таких >однотипных функций (но тут уже надо думать о адекватности архитектуры).

      Это и есть аспектное программирование (в простом его изложении). Могу побиться об заклад, даже на небольших проектах — такое разделение сильно урощает.

      >то похоже на очередные подпорки для гнилых и запущеных проектов и не более.

      Не согласен.
      С таким же успехом ООП можно назвать гнилыми подпорками для плохого проекта написанного на процедурном (не помню уж точно как называется) языке.

      Аспектное программирование как и ООП завязано на разделении сущностей. ООП — на разделение сущностей по понятия объект или процесс. Аспектное — по понятию аспект.

  2. Похоже, это стало модно — писать в блогах про АОП 🙂

    Виктор, Вы, наверное, на примере AspectJ рассматриваете? Если да, то отлично, если нет — советую переключиться на него, это — классика.

    Если в этой книге вдруг не напишут, как именно реализуется cross-cutting concerns, то подсмотрите сюда:)

    — для языков, в которых отсутствует метаинформация об AST (abstract sytnax tree), все решается статически, на основе препроцессинга исходных текстов (например, aspectc++ использует свой язык для описания aspect, concerns, pointcuts, joinpoints — извините, перевод на русский будет неинформативным);

    — для языков с присутствием метаинформации (читай «с reflection») можно пойти двояко:
    * использовать процессинг исходных текстов (AspectJ, Aspect#)
    * использовать процессинг байт-кода (.spect на основе RAIL4MSIL)
    * динамически, в процесе исполнения, «навяливать» прокси-объекты, к которым цепляются аспекты; эти прокси также снабжаются правилами, по каким обращениям и как именно нужно вызывать цепочку аспектов (Spring, Spring.NET, TheCastleProject, также разработки под общим названием AopAliance)

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

    У меня в блоге есть небольшой обзор на примере MS Enterprise Library vs Spring.net.
    Ссылками засорять не хотел, если надо — найдете все, что упомянул 😉

    Надеюсь, моя информация будет полезной.

    • Victor Ronin:

      Все примеры там а AspectJ. Но мои проекты на C++, так что я в конечном итоге именно хотя в эту сторону копать.

      Спасибо, за объяснение насчет реализаци.. Где-то так я и думал. Просто интересно покопаться именно в том исходных кодах aspectc++, поглядеть как они это сделали.

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

      Согласен на 120%. Это именно метод решения проблем, которые предыдущим методом решались плохо.

      Кстати, если можешь запости сюда ссылочки на свой обзор.

  3. Чим не підходить звичайний proxy pattern? Здається він ідеальний для рішення такої задачі.

    • Victor Ronin:

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

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

  4. Мы в C#-проекте такую задачу через атрибуты решали. Очень удобно.

    • Атрибуты в .net — и есть частный случай пометки pointcut; своего рода еще одно измерение, в котором можно располагать информацию о коде.

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

    • Victor Ronin:

      А если можно поподробнее. Что такое атрибуты в C#?

      • Аттрибуты в C# — это классы, наследуемые от специального класса System.Attribute, входящего во фреймворк. Назначение данных классов — расширять метаинформацию, хранящуюся в .net сборках/.net модулях, причем делать это: а)декларативно б)без привлечения внешних файлов типа xml и configs.

        Для примера — код. Опустив детали, давайте условимся, что MySecurityAttribute — это наш пользовательский класс-аттрибут, у которого мы объявили публичные поля/свойства Roles и Users. Мы имеем в виду, что хотим этим аттрибутом помечать методы,которые разрешено вызывать только пользователям, входящим в перечисленные группы, или пользователям с конкретными логинами.

        [MySecurityAttribute(Roles=»meowth’s friends, Guest», Users=»V.Ronin»)]
        public static void MyMethod() {
        Console.Out.WriteLine( «Whoa, Mommy, look — Victor Ronin is reading me %)!» );
        }

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

        ОДНАКО это — только информация; необходим еще пользовательский engine, который сможет запросить у .net fw метаинформацию для данного метода и получить экземпляр класса MySecurityAttribute с полями, инициализированными как показано выше. Теперь он сможет решить, как трактовать полученную информацию.

        Если вы знакомы с последней java (java1.5 ‘Tiger’), то это примерно то же, что и ее аннотации (кстати, существует мнение, что они были списаны с аттрибутов .net).

        Как-то так.

        • Victor Ronin:

          Ага. Идею понял.
          Интересно.

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

    • Как раз хотел написать… Что в лоб можно решить атрибутами.

      А «по-умному», подобные задачи призвана решать концепция MVC, в которой на Controller и выносится такой функционал, как авторизация, протоколирование, бизнес-транзакции и т.п.

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

      • Victor Ronin:

        Увы MVC не решает, то что решеат АОП.

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

        Простейшие функции в том же контролере с временем обрастают кучей инфраструктуры.

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

          • Victor Ronin:

            ok. Переформулирую.

            А АОП — это называет «glue code». Так или иначе — в контролере надо вызвать движок. А где-то в движке авторизацию (причем в нескольких местах).

            АОП позволяет вызывать код не добавляя в него glue code. Это одна из вещей, которая мне понравилась.

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

  5. Всем кто заинтересовался темой рекомендую серию статей на DevelopersWorks и хоть и старую, но полезную книжку с примерами «AspectJ in Action»

  6. Andriy Lazarchuk:

    На Питоне я реализацию представляю как декоратор. Простой вариант и минимум синтаксического мусора.

    • Victor Ronin:

      Насколько я понимаю по честному АОП не решается через pattern’ы. Так как pattern — это создание кода специальным методом.

      По идеи АОП можно/нужно использовать без модификации исходного кода.

      • Да, так и есть. Только надо учитывать, что если АОП реализован не через генерацию исходного кода, а например через прокси объекты (пример из .NET), то это может заметно повлиять на производительность.

        • Victor Ronin:

          Лично меня не столь беспокоит производительность, сколько читаемость кода.
          Все равно, чаще всего тормоза производительности находятся в одном bottleneck и остальной код очень не сильно влияет.

        • Aop имеет смысл использовать в крупных проектах, к enterprise и туда ближе, так что там о проиводительности между прокси и не-прокси говорить не приходится — на уровне приложения это незаметно. А вот код писать упрощает, это да.

          Кроме того, последние генераторы прокси выдают «на гора» такой код, который мало оказывает влияния на производительность — вызов через такой прокси ничего не стоит, это вам не ContextBoundObject из System.Remoting — там да, вызов через CBO занимает в 100-1000 раз больше времени

          • Victor Ronin:

            Да, я согласен. Важность увеличивается для больших проектов, где много кода и много чего наслаивается.

      • Andriy Lazarchuk:

        > Насколько я понимаю по честному АОП не решается через pattern’ы. Так как pattern — это
        > создание кода специальным методом.

        Pattern — это не всегда создание кода специальным методом. Я бы сказал что это очень зависит от средств которые предоставляет язык. Тогда интерпретация pattern’ов совсем другая. Если паттернов в языке нет по определению значит мы оформляем код специальным методом.
        Я так понимаю что АОП это абстракция пошире чем ООП + паттерны и как следствие АОП легко может сделано с помощью этим двух техник. Чем лучше язык поддерживает их — тем проще создавать АОП.
        Я думаю что не стоит разделять АОП и pattern’и или ставить их в противоположность один другому — ИМХО это множества решений некой задачи которые могут быть объедены.

        > По идеи АОП можно/нужно использовать без модификации исходного кода.
        Возьмем пример из статьи: «Что можно описать в отдельном файле что-нибудь типа — в начале всех функций класса A, и функций x,y класса B — делать проверочку прав и только если она прошла то вызывать саму функцию». ИМХО нужно думать прагматично — каждый кусок кода потенциально может иметь в себе ошибки, та же проверка прав если она работает таким вот замысловатым образом где-нибудь упадет и мы как всегда забыли как-то обработать ошибку и выдать вменяемую информацию о ней — ми имеем большую проблему. А если это писал не я … тогда, как у нас говорят, «ховайся в жито».
        Как по мне, если мы хотим использовать АОП — каждый метод/класс который ми хотим вызывать способом как показано в статье должен быть «облагорожен» каким-то прокси или декоратором.

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

        • Victor Ronin:

          Мне тяжело обсудить или аргументировать, так как пока мой суммарный опыт АОП — дай богу пару дней (причем в основном обсуждений).

          Так же в Питоне у меня тоже опыта нету.

          Поэтому просто попытаюсь, проанализировать все сверху вниз (может потом в отдельную статью вынесу).

          Итак, если несколько проблемы за которые борятся разные языки, методики и т.п.

          а) Читаемость кода
          Сделать так, чтобы программист легче понимал, что делает/должна делать программа.
          б) Легкость модификации кода.
          Тут есть два подпункта
          б.1) Легкость расширения кода
          Когда есть уже что-то готовое и к нему надо добавить
          б.2) Легкость изменения кода.
          Когда уже существуещее надо поменять.
          в) Уменьшение вероятности ошибок.
          Это тесно связанно с легкостью модификаци. В смысле, что самое интересное уменьшать возможность ошибок именно в момент модификации.

          ООП — я бы назвал бы просто de-facto базой. То есть она предоставляет __средства__, для того, чтобы решать описанные задачи. И учитывая, что это база,
          до из-за ее особенностей, ей присущи определенные недостатки и ограничения.

          Далее. Pattern’ы — это просто наработки по решению типичных задач. Причем эти наработки сильно сконценрированы на б) (мое личное мнение) и только слегка задевают в) и а).

          АОП — надстройка над ООП. Оно не противоположность ООП, а именно дополнение.
          Причем АОП действительно похоже на некоторые паттерны, как было уже замечено (proxy и decorator). Просто по идеи АОП должна предоставлять более легковестный
          и элегантный способ реализовать эти паттерны.

          Итого, ООП — база. Паттерны самый верхний слой, который можно вообще положить на все что угодно. АОП — промежуточный слой, который частично лежит на ООП и который с другой стороны является pattern’ом.

          • panda:

            Дело в том, что в Python декораторы могут выглядеть по смыслу очень похоже на аспекты. Сравните, например, запись:

            def my_function(request):
            # текст функции

            my_function = login_required(my_function)

            и

            @login_required
            def my_function(request):
            # текст функции

            Это пример из веб-фреймворка Django.

  7. dzh:

    Я думаю, что совершенствовать код можно до бесконечности, ибо идеал недостижим (в отличие от процесса архивирования). Так что имеет смысл возвращаться к уже работающему коду снова и снова!

    • Victor Ronin:

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

      • dzh:

        Хм… То есть код — это антипод известному органу, получается? Чем он меньше, тем больше поводов для гордости?

        • Victor Ronin:

          Так оно и есть. Лучшие программисты гордяться полным отсутствие кода 🙂

          • dzh:

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

  8. zahardzhan:

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

    • Victor Ronin:

      Не уверен, кто такой Петер Норвиг, но Java находится на первом месте по полуряности языков программирования, а Лисп на двадцать первом. Видно, что-то нужно еще кроме настругивания макросов.

      • zahardzhan:

        Очевидно, нужен Clojure — лисп для виртуальной машины Java.

        PS. Питер Норвиг (англ. Peter Norvig) — американский ученый в области вычислительной техники. В данный момент работает директором по исследованиям (ранее — директор по качеству поиска) в корпорации Google. Автор книг Artificial Intelligence: A Modern Approach и Paradigms of Artificial Intelligence Programming.

        • panda:

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

          • zahardzhan:

            Clojure вобщем-то, устранил все былые недостатки лиспа — у него теперь кроссплатформенность Java, скорость Java и все библиотеки Java, так что недостатка в промышленных библиотеках и платформах нет. И что существенно, он сильно проще , чем старый добрый Коммон лисп.
            Относительно «высокой сложности». У миллионов леммингов другая беда — они необычайно трудолюбивы и упорны — ведь чтобы научиться правильно использовать, а потом еще и писать на Java — упорства и трудолюбия программисту нужно ну очень много. Я, например, сделал выбор в пользу лиспа из-за своей лени, которая зашкаливает за все разумные пределы — мне проще не заморачиваться с синтаксисом и суровыми наворотами из нескольких десятков паттернов проектирования, а быть ближе непосредственно к тому, что моя программа делает на самом деле.

  9. zahardzhan:

    Относительно вопроса “Чем вам НЕ нравиться ООП”?

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

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

    • Victor Ronin:

      Собственно говоря, где-то такой ответ я и хотел бы слышать от собеседуемого. Идеально, дополненный каким-нибудь примером.

      • zahardzhan:

        За примерами далеко ходить не надо — в предыдущем треде я привел цитату об АОП в лиспе.

  10. Аноним:

    Насчет границы компактности языков. Некоторое время назад довелось написать маленькую библиотечку для реализации базовых идей АОП: оборачивания функций в аспектный код, тобишь добавление кода вокруг, до и после вызова ф-ции (around, before, after). На Clojure весь код библиотеки уместился в 50 (!!!) строк. Честно говоря, такая компактность меня очень удивила.

    • Victor Ronin:

      Интересно. Но, я скорее говорил о компактности относительно функциональности (то что может использовать пользователь).

  11. zahardzhan:

    Я, собственно, о том же.