Как стать автором
Обновить

Комментарии 55

Без комментариев не будет работать?
Будет, но на текущем уровне этот вариант задания срезов точек более предпочтительный, так как движок будет разбирать скоро грамматику срезов, выполняя построение AST. Задавать срезы руками с помощью классов будет значительно сложнее и менее читабельно. Но планы есть и на ваш случай ) Спрос рождает предложение.
Вот ещё вопрос. Аспект же может переопределить значения аргументов вызываемой функции или её результат работы. Если так, то возникает проблема правильной очередности работы аспектов, если их несколько на метод. Или я заблуждаюсь?
Аспекты, как и события, следует использовать в тех случаях, когда очередность не важна.

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

А за аспект, который увеличивает параметр на единицу, надо убивать.
Может быть аспект проверяющий доступ или кэш. Аргументы не трогает, но от очередности исполнения зависит в целом работа системы.
Отличный аргумент, однозначно в список собственных рекомендаций!
Аспект позволяет сейчас изменить только те значения, которые были переданы по ссылке. Но можно добавить и возможность менять все аргументы, это дело одного сеттера ) Но нужно ли это сейчас? Думаю, что пока рано. А вот результат вызова метода изменять можно, так работает самый мощный тип совета — Around, с помощью которого делается кэширование и многое другое. Если советов несколько, они будут применяться в определенном порядке.
И да, советы имеют сортировку, которой можно управлять, в документации есть пример.
Лучше согласиться с комментатором выше. Для управления сортировкой нужно владеть полной информацией об аспектах или иметь заданный архитектурой порядок, например как у событий в DOM.
Типичный программист без особых раздумий сделает так, как приведено выше: внесет вызов нужных методов в код методов «кушать» и «спать», нарушив принцип единственной ответственности каждого из этих методов. Благо, условия простые, можно разобраться с тем, почему это было сделано именно здесь.

Вообще с помощью классов описываются не только состояния но и процессы.
И в данном случае в классе Human не было бы метода eat. Был бы метод принимать пищу. А вот процесс кушать, был вынесен в отдельный класс:
class Eat { 
   function __construct(Human $human) { 
       //...
   }
   public function eat(Food $food) {
        $this->human->washUp();
        echo "Eating...", PHP_EOL;
        $this->human->cleanTeeth();
   }
}

Или же с помощью контекста:
class HumanContext { 
   public function eat(Human $human, Food $food) {
        $human->washUp();
        echo "Eating...", PHP_EOL;
        $human->cleanTeeth();
   }
}


В случае же с аспектами, вы словно затыкаете костылями не верную архитектуру проекта. И когда изучаешь код, ты получаешь очередной дополнительный уровень сложности в связи с аспектами. Рефакторинг начинает приносить ТОННЫ геморроя.
Плюс ко всему к этому, IDE для поддержки аспектов нет.

Выражаю сейчас своё большое ИМХО, которое вообще не претендует на объективность. По мне, так АОП в таком виде огромное зло, которое ничего хорошего не принесет.
Насчет поддержки в IDE — наверное, вы не заметили inline-докблок в теле советов. Моя IDE прекрасно это понимает, давая мне возможность легко делать рефакторинг кода, находить места использования, использовать автодополнение и даже создавать новые методы в классе «человек», при этом находясь внутри класса аспекта.
Но определить точки, где внедряются аспекты — это да, IDE не подскажет без плагина. Тут с вами соглашусь.
Насчет всего остального — я пытаюсь поделиться с сообществом своими знаниями и решениями. Как и все новое, оно будет и должно вызывать недоверие. Поэтому я очень ценю любые конструктивные замечания и предложения, чтобы попытаться о них подумать. А вот приводить альтернативы в виде шаблонов OOP не нужно, их и так все знают, в сети полно материала на эту тему.
Можно узнать, а что у вас за IDE?

А вот приводить альтернативы в виде шаблонов OOP не нужно, их и так все знают, в сети полно материала на эту тему.

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


Ну ладно, соглашусь, что с шаблонами я не совсем к месту, но все же, в чем тогда прелесть АОП?
Рефакторинг, когда IDE не подходит под парадигму задача не тривиальная.
IDE — старый добрый phpStorm, но строчные докблоки должны поддерживаться любой нормальной IDE, поэтому этот код вполне удобно рефакторить.

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

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

Хм, наврное мы друг друга не совсем поняли. И тут моя вина, но я говорил про такую конструкцию, которая не понимает IDE (в том числе и PHPStrom). Соответственно про сложность рефакторинга я говорил, смотря на данную конструкцию. Но об этом вы уже меня предупредили выше.
/**
 * @Before("execution(public Human->sleep())")
 */
Ах, вы про это. Да, здесь помощи от IDE не получишь, как и в регулярных выражениях. А срез, по-сути, это и есть регулярное выражение для кода. Но внутри можно спокойно задавать срезы ручками с помощью кода, связывая с замыканиями в нужных точках. Это для advanced-пользователей. Позже будет конфиг аспектов в виде xml с валидной схемой, а также yml.
НЛО прилетело и опубликовало эту надпись здесь
Беглый поиск в гугле выдает www.aspectc.org/. Конечно, это реализовано в виде препроцессора C++-кода.
НЛО прилетело и опубликовало эту надпись здесь
Думаю, что АОП в C++ можно сделать еще следующим образом: берем таблицу методов класса, подменяем указатель на метод своим указателем, а потом просто вызываем оригинальный метод, передавая ему управление. Такие вещи можно делать даже в рантайме, будет работать очень быстро. Аналогичный подход используется для эксплуатации уязвимостей переполнения буфера в программе, так что однозначно — можно.
НЛО прилетело и опубликовало эту надпись здесь
Такой подход намного проще реализовать на ObjectiveC :) Там, по идее, достаточно вариации на тему NSProxy.
Библиотека Go! использует уникальную для PHP технологию Load-Time Weaving, которая позволяет отследить момент загрузки класса и изменить этот код до его анализа парсером PHP.

Уникальную в том смысле, что другие библиотеки не используют препроцессинг, или в том смысле, что в других средах (не-PHP) такой возможности нет?
Вы меня подловили ) Данную фразу правильней трактовать так: «больше нет библиотек на PHP, которые делают Load-Time-Weaving». Т.е. относится только к PHP. В других языках препроцессинг конечно же есть и существует там очень давно.
Производительность тестировали? Особенно интересует сравнительное тестирование с варианте с включенным кэшированием опкода. У меня серьезные подозрения, что все будет плохо.

Если не совсем ясно, с чем сравнивать — могу предложить сравнить с использованием вместо аспектов замыканий и ключевого слова yield (в php 5.5 alpha) или явной передачей Callable $yieldFn в php 5.4.
Для моего сферического примера в вакууме для этой статьи получились следующие цифры:

Вариант | время без опкод-кэшера/с опкод-кэшером (apc.stat=off)

Оригинальный код (без логики) 0.2-0.3мс/0.19-0.25мс
Хардкод логики в методах 0.22-0.34мс/0.20-0.25мс
Оригинальный код с вызовами callable замыканий 0.5-0.7мс/0.25-0.37мс
Оригинальный код c АОП (debug=false) 7-8мс/1,6-2.2мс

Это само время выполнения кода, сюда не входит автозагрузка классов, инициализация кэша и прочее.
Лучше, конечно, делать цикл вызовов одинаковых методов с помощью разных способов (callable, call_user_func, __call, AOP и т.д), чтобы получить более правильные цифры, они будут отличаться от единичных вызовов и разница уже будет не 6-8 раз, а меньше. АОП внутри использует кэширование invocation-ов, что позволяет запускать даже огромные проекты типа ZF2, полностью заменив ВЕСЬ код аспектным (см. примеры на гитхабе).
Более того, есть возможность сделать это еще быстрее (хотя уже сейчас скорость моей библиотеки сопоставима со скоростью работы экстеншена PHP-AOP, а иногда и превышает ее из-за технической реализации)
АОП внутри использует кэширование invocation-ов

А можете прогнать все 4 варианта (с опкод-кэшером) через ab -n3000 -c30 и опубликовать RPS?
Интересно, одному мне не нравится идея программирования внутри комментария?
Ага, как-то это от лукавого… Комментарии нужны для комментариев, а если язык не поддерживает изначально парадигму, то не нужно лепить ее поверх с помощью библиотек. Такой код явно будет уступать по производительности нативному, усложнит общее восприятие, да и вообще не факт, что будет с этого профит. Имхо.
Профит с этого есть. Мы в свое время использовали аспектный подход в Джаве и он показал себя удобным для того, чтобы не влезая в код бизнес логики, навесить мониторинг производительности, логгирование входящих параметров, кэширование и прочие сервисные вещи. Очень было удобно и как раз очень хорошо сегрегировался бизнес код от каких-то инструментальных вещей.
Если вы читаете php.internals, то обнаружите, что с вами солидарно очень много разработчиков. Но есть необходимость в метаданных, поэтому и возникают разные RFC: annotations, annotations-in-docblock.
Популярные фреймворки используют свои парсеры для анализа аннотаций в док-блоках и позволяют управлять проектом «in one place» — сам код и конфиг к нему находится в одном месте, что очень удобно в некоторых случаях. Посмотрите на Symfony2, Doctrine2, FLOW
Долго не мог понять, почему в статье упомянут PHP, а потом говорится про библиотеку Go! Сразу с языком Go про ассоциировалось. Я так понимаю, это какая-то библиотека, которую вы разрабатываете? Вот нашел, это не она — github.com/lisachenko/go-aop-php?
В конце статьи именно эта ссылка и указана. Исторически так сложилось, что в момент закладки фундамента этой библиотеки я еще не знал про язык Go, он еще не был популярен так, как сейчас.

А название было дано как внутренний мотиватор — «давай, делай!». Потому что написание этой библиотеки — сплошная борьба со словом «нельзя»: нельзя подменить класс в PHP проксей (сохранив старое название класса), нельзя перехватить метод в финальном классе (потому что нельзя сделать extends), нельзя перехватить статический метод (потому что в коде они вызываются напрямую, не используя возможные прокси), нельзя сохранить LSB и решить проблему scope при перехвате статического метода, нельзя перехватить обращение к публичному свойству объекта, нельзя сделать прокси для трейта, нельзя перехватить метод в трейте, нельзя изменить код класса так, чтобы xdebug по-прежнему работал с основным классом, нельзя динамически изменить код класса при этом сохранив возможность использовать опкод-кэшер, нельзя сериализовать замыкания и прочие технические проблемы.
Помнится была у меня мысль попробовать скрутить АОП и PHP, но даже малая часть того, что вы перечислили, привела к мысли, что без статической кодогенерации или расширений языка не обойтись и мысль умерла. Респект!
Тут как говорится: «терпение и труд — все перетрут» ) Спасибо за оценку, это меня очень вдохновляет.
Как раз в текущую минуту рождается еще одна часть, которая очень нужна, но которую непросто сделать — лексер и грамматика для срезов. Это позволит разбирать любые правильные конструкции в деревья и проверять их, подсказывая пользователю ошибки в синтаксисе при разборе, а также позволит использовать логические объединения срезов с помощью AND, OR, NOT.
Еще нащупал почву, как делать runtimе-проверки (сейчас только статические на этапе вплетения аспектов). Это позволит определять замыкание, которое будет само принимать решение, выполнять совет или нет в зависимости от свойств объекта, или переданных ему параметров.
Жесть!
В языках поддерживающих метаданные, такие как данные рефлекции всё куда понятнее выглядит.
Это и есть та самая знаменитая метрика: WTF/строчку кода.

Интересно, насколько больше единицы у нас?
Я такие штуки нахожу постоянно в любом коде, включая и свой. Хороший показатель — не больше пары WTF/10000 строк кода.
Вообще говоря, тема интересная, но ведь это страшно ухудшает читаемость кода. Частично согласен с комментарием выше в том, что это похоже на сложную систему костылей и подпорок. Разбирая код, с учётом DI, наследования и прочих штучек ООП, не всегда поймёшь, откуда что берётся, а тут ещё и такое: зовёшь конкретно один метод eat(), а выполняются два. И попробуй потом найти, почему так.
Короче говоря, писать в таком ключе — интересно, а читать и отлаживать код — закат солнца вручную =)
Не совсем согласен. Часто ли вы отлаживаете код кэширования внутри метода, или логирования? Как правило, отладка начинается с установки точки останова внутри метода, отсеивая вызовы кэширования, логирования и прочие как ненужные. Когда отлаживается код — отлаживается работа конкретного метода, а потом уже всей системы в целом. Читать код с аспектами значительно проще, потому что весь мусор из методов и классов вычищается в отдельный класс-аспект и применяется по всему коду. Достаточно знать, как вплетаются аспекты в конечный код, после этого наступает озарение и понимание сути всех вещей.
Но без озарения для новичков это будет довольно весело и интересно ) Сумерки. Кодинг. Рассвет.
Может, есть у кого идеи, как это сделать понятно? Что мне нужно дать разработчику, чтобы он знал о содержимом? В принципе, при отладке доступны объекты точки доступа, в них видны сами советы.
Сильно похоже, что никак. Это из той же оперы, что и вызов функций/методов по имени (типа $func()): писать удобно и интересно, а разбираться потом — не очень.
Спасибо, весьма познавательно, вовремя и доходчиво. На данный момент AOP для меня одна из горячих тем. В проекте над которым сейчас работаем думаю он был бы весьма полезен, во всяком случаи разгрузил бы кучу вызовов методов в сложной логике проекта.
Очень интересно было бы получить фидбек, если вы будете это использовать. Пока у меня несколько положительных отзывов о реальном использовании: в одном случае добавлено кэширование в проекте с упрощением кода сервисов, в другом — сделано логирование всех действий в админке Symfony2 на базе SonataAdmin.
Конечно. Попробуем, отпишемся.
Отличное завершение этой ветки ) Очень приятно видеть, что моя работа может пригодиться в реальных проектах. Если будут вопросы — связывайтесь со мной, с удовольствием помогу.
А по-моему поддержка кода становится только сложнее:
— если добавлять аннотации — их всё равно нужно добавлять, если не добавлять — всё становится ещё менее очевидно
— на препроцессинг кода тратятся такты процессора
— смотришь на метод «кушать», но можешь не догадываться, что там будет вызвано «мытьё рук» — теряется читабельность, усложняется отладка
— код расползается на объекты/аспекты, что тоже усложняет поддержку
— нужны дополнительные библиотеки

Если нужен такой функционал — скройте методы, и добавьте __call(), в котором с помощью switch/case навешивайте дополнительные обработчики на вызовы нужных методов.

Это мое личное мнение, могу быть не прав.
Насчёт вашего метода реализации такого функциональности не согласен. Прелесть аспектов как раз в том, что они не требуют изменения исходного кода аспектируемого (термин мой :) класса. Ладно если код свой и между проектами не шарится. А если «ещё вчера» понадобилось «внезапно» логировать или кэшировать вызовы методов какой-то сторонней библиотеки? Форкать её, получая весь геморой с поддержкой форка? Писать враппер/прокси и перелопачивать весь код, заменяя вызовы, рискуя где-то пропустить?

Тут можно спорить о реализации (аннотации/что-то другое, удобная/неудобная, сильно тормозит/не сильно), но сам принцип вроде хорош, если не пихать его везде где есть возможность, а четко понимать области его применения и последствия. Честно говоря, пример с человеком мне кажется не очень удачным, поскольку вызовы метода объекта вызывают его же методы — это хорошо, что методы тут чистые, не изменяют свойства объекта, что для ООП вообще не характерно. А так, да, действительно, отладка может в ад превратиться, особенно если аспекты ещё и параметры вызова меняют. Но вот для ортогональных логике аспектируемого класса вещей — то же логирование, кэширование, генерация или подсчёт событий, отладочный вывод, в общем какие-то сервисные вещи, АОП по-моему чуть ли не идеал (при хорошей реализации).
Пожалуй, соглашусь, область применения несколько уже, чем описана в статье, и применять нужно очень осторожно.
Вижу, вы уловили и понимаете суть вещей в АОП. Это хорошо! Потому что пример в статье действительно не является рекомендацией делать так, как там описано. Пример был выбран специально для упрощения понимания того, как и что происходит в аспектной парадигме, как это описывается и что получается. Уровень — начальный, ознакомительный.
В реальной же жизни — все проще и банальнее, выносим в аспекты только то, что мусорит в коде — логирование, кэширование, авторизацию. Для этих вещей лучше ничего не придумаешь.
Если пойти еще дальше, то на верхнем уровне будут такие вещи как Autowire, когда все классы сами укладываются при статической инициализации в контейнер и инжектятся в виде сервисов в другие сервисы (пример Google.Guice), реализация паттернов в АОП (те же события можно генерировать в заданной точке и обрабатывать в традиционном понимании с помощью диспетчеров) и много-много всего интересного.
Интересовался темой на примере Java, но, как писал выше, реализация в PHP казалась неоправданно трудной. Мысль перехватить autoload в голову не пришла.

А пример можно было сделать в той же предметной области, но введя ещё один объект, например, свет в ванной комнате. Чтобы не мусорить включением/выключением в основном коде, когда руки моешь или зубы чистишь, а автоматический выключатель на любые действия в ванной повесить :) Хотя и так нормально получилось.
Но похожих результатов можно добиться с помощью событий?
Да, можно, заранее делая программу сложнее, потому что вам придется рефакторить ваш код, добавляя логику обработки события. Более того, события будут работать медленней АОП )
В прошлой своей статье я как раз приводил сравнение с событиями, почитайте на хабре.
Да, почитаю. Спасибо.
Собственно, код в любых случаях придётся рефакторить. Так как логика исполнения переехала из метода — в комментарии к методу. А ведь самая вкусная часть в АОП — это как раз «срезы».
И эти люди запрещают мне ковырять в носу использовать global
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории