Беспощадная битва происходит каждый день во всемирной паутине. Его цель — решить, какой вариант программирования лучше: ООП или 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()}")
    }
}
  1. Функция указывает на приостановку вычисления

Код сопрограмм внешне кажется императивным, несмотря на то, что он асинхронный.
Это преимущество сопрограмм:

  • На первый взгляд это выглядит императивным; следовательно, это достаточно легко понять
  • За кулисами библиотека запускает код асинхронно.

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

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

Первоначально опубликовано на A Java Geek 31 июля 2022 г.