
AOP является отличным инструментом для глобальных концепций, таких как: логирование, которое, напрямую, не влияют на логику кода.
Однако, проблемы с AOP обнаруживаются, когда он используется для бизнес-требований, таких как авторизация. Такие аспекты должны быть четко видны в соответствующем коде, чтобы разработчик мог сразу увидеть, при чтении исходного кода, правильно ли они реализованы. AOP фреймворки обычно решают эту проблему с помощью аннотаций:
@RequireRole(Role.Admin) // clearly visible aspect fun updateUserPermissions(…) { // logic here }
Однако, с точки зрения читабельности, он не сильно отличается от функционального подхода, использования метода requireRole вместо аннотации.
Примечание переводчика: в оригинальной статье используется выражение when rewritten in a functional way или functional approach что можно трактовать как использование функционального подхода так и прямого/конкретного вызова функции. В статье есть примеры которые можно трактовать по-разному.
Так же:
1. В дальнейшем мы вернемся к понятию Higher-Order Functions
2. В статье встречается слово аспект, что является англицизмом и понятием в AOP aspect
fun updateUserPermissions(…) { requireRole(Role.Admin) // throws SecurityException // logic here }
Более того, функциональный подход имеет преимущество масштабирования до более сложных проверок прав доступа, таких как анализ параметров метода, прежде чем решить, какая роль пользователя требуется.
То же самое верно и для других типов аспектов, таких как транзакции. К сожалению, функционально представлять более сложные концепции в Java громоздко и неудобно, что создает искусственную популярность AOP фреймворков в Java экосистеме.
В Kotlin вместо Java-подобного подхода к транзакциям с помощью AOP и аннотаций, вроде этого:
@Transactional fun updateUserPermissions(…) { // logic here }
Он так же читабелен и выглядит чисто, когда переписан без применения аннотаций, с функциональным подходом.
fun updateUserPermissions(…) = transactional { // logic here }
Преимущество этого подхода заключается в том, что всегда можете нажать Ctrl / Cmd + Click на объявлении функции transactional в вашей IDE и сразу увидеть, что именно она делает. Что обычно невозможно с любым из обычно используемых AOP фреймворков. Даже когда навигация к коду аспекта обеспечивается с помощью плагина IDE, для расшифровки его логики требуется знание отдельного многофункционального API и/или соглашений.
К сожалению, есть проблемы с масштабированием способа замены AOP аннотаций в Kotlin. Для случая когда несколько аспектов применяются к одной и той же функции, с накоплением фигурных скобок и отступов:
fun updateUserPermissions(…) = logged { transactional { // logic here } }
Обходное решение — создать higher-order function, чтобы сохранить приемлемый код при применении нескольких аспектов:
fun updateUserPermissions(…) = loggedTransactional { // logic here }
Другим недостатком функционального подхода является то, что такие аспекты, как логирование, требуют доступа к параметрам метода. Они обычно доступны в AOP фреймворках через специальные API, но с использованием стандартных функций языка Kotlin нельзя легко получить к ним доступ. Таким образом, для того, чтобы на самом деле представить реальный пример аспекта логирования, в чисто функциональном виде, все еще нужно написать значительное количество boiler-plate кода:
fun updateUserPermissions(params: Params) = logged("updateUserPermissions($params)") { // logic here }
Это соображение все еще делает AOP предпочтительным инструментом для логирования, когда вам действительно необходимо иметь его глобально и согласованно в вашем приложении. При этом использование AOP для такой функциональности как авторизация и транзакции, является злоупотреблением, учитывая богатые функциональные абстракции, которые доступны в Kotlin. Функции справляются с этими требованиями лучше и чище.
В заключение я бы сказал, что дальнейшее усовершенствование функциональных абстракций для обеспечения еще лучшей замены АОП могло бы стать многообещающим вектором будущего развития языка Kotlin. AOP фреймворки, основанные на Java, обычно специфичны для JVM и воспринимаются как магия, в то время как функциональные абстракции Kotlin действительно кроссплатформенны и прозрачны для пользователя.
Примечание переводчика:
1. Cтатья на medium (eng) .
2. Автор оригинальной статьи является Roman Elizarov (Team Lead JetBrains, working on Kotlin coroutines and libs, sports programming/ICPC, concurrency & algorithms, math/quantitative finance; formerly Devexperts). На Хабре elizarov

