Беспощадная битва происходит каждый день во всемирной паутине. Его цель — решить, какой вариант программирования лучше: ООП или FP? Я предполагаю, что императивное и процедурное программирование не входят в число претендентов.
Аргументы варьируются от фактических до неуместных и совершенно глупых. Пару лет назад я хотел послушать видео Мартина Одерски (известного по Scala). Я не помню ни точного разговора, ни темы. Что я помню, так это вступление: он объяснил, что FP более популярен, чем ООП… потому что было гораздо больше конференций, посвященных первому, чем второму.
В то время я не думал, что популярность является существенным фактором, который помогает мне реализовывать проекты. На момент написания этой статьи я все еще не знаю. Более того, это все равно, что сказать, что электричество не популярно, потому что ему не посвящены конференции. Боюсь, М. Одерский принял популярность в научных исследованиях за актуальность. Я остановился после его «аргументов» и по сей день больше никогда не видел ни одного его выступления.
При этом моя цель не в том, чтобы очернить М. Одерского, а в том, чтобы подчеркнуть полную бессодержательность некоторых аргументов. Например, для поклонников FP его неизменность не является чем-то особенным, так как ООП также может с пользой использовать его. Единственное отличие состоит в том, что неизменность является требованием FP. С другой стороны, слишком далекое продвижение ООП приводит к таким языкам, как Java, где каждый метод должен принадлежать классу, даже статический. В этом случае классы — это просто дополнительное пространство имен для методов — они не несут ООП «ценности».
Область применения выходит далеко за рамки ООП и ФП. Рассмотрим следующие фрагменты:
// Snippet 1 fun router(repo: PersonRepository) = router { val handler = Handler(repo) GET("/person", handler::getAll) } class Handler(private val repo: PersonRepository) { fun getAll(r: ServerRequest) = ok().body(repo.findAll()) } // Snippet 2 fun router(repo: PersonRepository) = router { val handler = Handler(repo) GET("/person/{id}", handler::getOne) } class Handler(private val repo: PersonRepository) { fun getAll(r: ServerRequest) = ok().bodyValue(repo.findAll()) }
Очевидно, разница заключается в ok().body()
и ok().bodyValue()
. Если вы не знакомы с инфраструктурой Spring, вы вряд ли правильно идентифицируете левый фрагмент как WebMVC.fn, а правый — как Web Flux. Это может быть еще более запутанным, если repo.findAll()
будет изменен с блокирующего на неблокирующий, так как вы не заметите никакой разницы. Вы можете отличить одно от другого, только взглянув на импорт пакетов:
- Блокировка:
org.springframework.web.servlet.function.*
- Неблокирующий:
org.springframework.web.reactive.function.server.*
Вы можете переписать оба приведенных выше фрагмента, используя аннотации вместо обработчиков.
// Snippet 1 @RestController class PersonController(private val repo: PersonRepository) { @GetMapping fun getAll() = repo.findAll() } // Snippet 2 @RestController class PersonController(private val repo: PersonRepository) { @GetMapping fun getAll() = repo.findAll() }
Оба фрагмента выглядят одинаково на первый взгляд, но для импорта. Косметика идентична, а внутренности — блокирующие и неблокирующие — принципиально разные.
Давайте посмотрим на сопрограммы Kotlin. Вот фрагмент из документации Kotlin:
measureTimeMillis {
val one = somethingUsefulOne() // 1
val two = somethingUsefulTwo() // 1
runBlocking {
println("The answer is ${one.await() + two.await()}")
}
}
- Функция указывает на приостановку вычисления
Код сопрограмм внешне кажется императивным, несмотря на то, что он асинхронный.
Это преимущество сопрограмм:
- На первый взгляд это выглядит императивным; следовательно, это достаточно легко понять
- За кулисами библиотека запускает код асинхронно.
Код имеет косметические и внутренние характеристики. Я надеюсь, что приведенные выше примеры убедили вас в том, что они полностью ортогональны. Вы можете добиться одних и тех же свойств с помощью разных косметических средств и наоборот.
Мы постоянно спорим о косметике, например,, аннотациях и «функционале», но в основном это вопрос личного вкуса. Чтобы решать проблемы, нам нужно гораздо больше времени уделять внутренностям: акторам, асинхронности и т. д.
Первоначально опубликовано на A Java Geek 31 июля 2022 г.