Pull to refresh

AOP vs Функции

Reading time3 min
Views6.8K
Аспе́ктно-ориенти́рованное программи́рование (AOP) довольно популярная парадигма программирования. В статье на Wikipedia хорошо объяснена мотивация этого подхода.

image

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
Tags:
Hubs:
Total votes 9: ↑6 and ↓3+3
Comments2

Articles