Прощай, Ramda

Приходилось ли вам когда-нибудь возиться со старым кодом, ощущая себя дешифровщиком древней рукописи? Признайтесь, часто в этом участвовала Ramda, побуждая вас искать более подходящие альтернативы.

Введение

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

Дивная современная Ramda

Ramda  —  это библиотека JavaScript, которая реализует принципы функционального программирования и обладает внушительным набором служебных функций. Ramda позволяет работать с неизменяемыми данными, компоновать функции и привносить в код декларативность. Используя Ramda, вы можете наслаждаться автоматическим каррированием, легкой композицией функций и удобной работой с неизменяемыми данными. Кроме того, эта библиотека обладает элегантностью стиля бесточечной нотации, при котором функции определяются без явного упоминания их аргументов. Ramda действительно делает функциональное программирование в JavaScript-проектах более доступным, выразительным и приятным.

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

Вытеснение Ramda решениями JavaScript

Однако по мере развития JavaScript я, как и многие мои коллеги, стал все больше полагаться на нативные решения JavaScript. Встроенные средства и возможности языка постепенно вытесняли такие библиотеки, как Ramda, Lodash и другие.

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

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

Начиная с ES5, JavaScript стремительно прогрессировал, пройдя путь от ES2015 и ES2017 до современного ES2020. Каждое обновление приносило новые возможности, которые значительно упрощали нашу жизнь и улучшали функциональность и декларативность JavaScript-кода.

Совершенствование языка началось с внедрения функций высшего порядка, таких как map(), reduce() и filter(). Они существенно облегчили работу с массивами.

Затем мы получили замечательные анонимные функции. Эти новые дополнения были изящны и понятны, что сделало код более легким для чтения и восприятия.

В дальнейшем появились шаблонные литералы, которые изменили работу со строками. Больше не нужно было заниматься конкатенацией  —  только плавное и надежное создание строк.

Еще одним приятным дополнением стал синтаксис spread, позволяющий легко расширять массивы и объекты, сохраняя неизменяемость. И, наконец, удобное трио: деструктурирующее присваивание, опциональная цепочка и оператор нулевого слияния. Они сотворили чудеса с JS-кодом, сделав его чище и легче для восприятия.

Использование нативных функций JavaScript дает ощущение комфорта и простоты, что избавляет разработчика от лишнего обучения. Многих из нас привлекает суть языка, та самая ткань, из которой он соткан. А почему бы и нет? Эти нативные функции  —  не только сердце JavaScript: они легки и разработаны с учетом производительности.

По сравнению с такими библиотеками, как Ramda, которые обволакивают код дополнительным слоем абстракций, нативный JavaScript быстр, как течение реки.

Этим возможностям способствуют оптимизации JavaScript-движков, таких как V8. Создатели V8, движка для Google Chrome и Node.js, постоянно оптимизируют нативные функции. Это обеспечивает еще более плавное и быстрое выполнение.

Но дело не только в скорости. Речь идет также об интеграции и совместимости. Нативные функции плавно сочетаются с другими частями языка, гармонично сочетаясь с различными API и широкой экосистемой JavaScript.

Дополнительными преимуществами нативного JavaScript являются его совместимость с различными версиями ECMAScript и минимальное количество зависимостей. Этот язык постоянно совершенствуется благодаря непрерывным обновлениям ECMAScript. Постоянное развитие и адаптация  —  причины того, что нативный JavaScript становится лучшим выбором для многих разработчиков. Зачем же искать что-то новое, если оригинал и так хорошо справляется со своей задачей?

Проблемы с включением Ramda-кода в TypeScript

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

Одна из основных проблем заключается в том, чтобы убедиться, соответствуют ли типы в Ramda-функциях типизации TypeScript. Ramda предоставляет некоторые сценарии типизации TypeScript, но далеко не все. К тому же они не всегда актуальны для самых свежих версий Ramda. Это может привести к несоответствию между ожидаемыми и предполагаемыми типами, что заставит вас обрабатывать аннотации типов вручную либо создавать собственные типы.

Другая проблема связана с функциональным стилем программирования Ramda, с его причудливыми каррированием и композицией функций. Вывод типов в TypeScript не всегда справляется со всеми этими особенностями. Порой приходится добавлять явные аннотации типов и дополнительные утверждения, чтобы все было под контролем. Это нередко приводит к путанице и ухудшению читабельности и сопровождаемости кода.

Кроме того, в Ramda-коде, написанном без учета TypeScript, могут отсутствовать соответствующие аннотации типов и документация. Это означает, что придется потратить дополнительное время и усилия на рефакторинг и адаптацию кодовой базы к условностям TypeScript, а также обеспечение безопасности типов. 

С другой стороны, использование нативных функций JavaScript в TypeScript решает многие из этих проблем. TypeScript может точно обрабатывать типы без лишних хлопот, значительно облегчая вам жизнь. Плавная интеграция и улучшенная поддержка TypeScript  —  это те особенности, которые делают JavaScript-функции привлекательным выбором для TypeScript-проектов, особенно если вы стремитесь к безопасности типов и эффективному процессу разработки.

Довольно слов  —  перейдем к коду

Рассмотрим Ramda-код. В приведенном ниже примере нет сложной логики  —  случай элементарный. Однако мне приходится тщательно поразмыслить, чтобы понять логику даже такого простого фрагмента.

const result = R.pipe(
R.filter(R.both(
R.propSatisfies(R.gt(R.__, 25), 'age'),
R.pathEq(['address', 'country'], 'USA')
)),
R.map(R.prop('name'))
)(users)

// 🤔 🤔 🤔

Этот Ramda-код фильтрует список пользователей по их возрасту и стране проживания, в частности ищет пользователей старше 25 лет, проживающих в США. Здесь применяются принципы функционального программирования с использованием R.pipe() для объединения нескольких операций. Хотя тут логика не является самой запутанной из всех, которые я когда-либо видел, многоуровневая композиция функций требует некоторого времени для осмысления.

Перепишем этот фрагмент с помощью Lodash, сделав его немного проще и понятней.

const resultL2 = _.map(
_.filter(
users,
user => user.age > 25 && user.address.country === 'USA'
),
'name'
)

// 👌👌👌

Lodash-версия выглядит более доступной. Она упрощает нам жизнь, непосредственно выражая условия фильтрации в функции обратного вызова, используя filter() и map(). И все же, несмотря на ощутимое улучшение, можно добиться еще большего упрощения. 

Пойдем дальше и перепишем этот фрагмент, используя только нативный JS:

const result = users
.filter(
user => user.age > 25 && user.address.country === 'USA'
)
.map(user => user.name)

// 🥹🥹🥹🥹

Превосходно!

Синтаксис прост, интуитивно понятен, без всяких абстракций, отвлекающих от основной логики. Список операций легко читается и понимается. Для выполнения тех же операций фильтрации и сопоставления использованы методы массивов filter() и map(). Условия фильтрации записаны непосредственно в функции обратного вызова, что позволяет получить более читабельный фрагмент кода.

Для тех, кто привык к традиционному стилю написания кода, использование в Ramda функционального программирования и каррирования в сочетании с непривычными соглашениями об именах может сделать код сложным для понимания. Задача усложняется, если приходится переключаться между различными стилями кода в рамках одного проекта. По сути, для человека, незнакомого с этими специфическими практиками, чтение Ramda-кода может стать похожим на попытку разгадать головоломку. Непривычность кода затрудняет его чтение и понимание, особенно при необходимости жонглировать несколькими парадигмами создания кода.

Несколько слов в свое оправдание

Ramda, несмотря на высказанные мною ранее опасения, несомненно, является мощным инструментом при правильном применении.

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

JavaScript хорошо зарекомендовал себя. Он все лучше справляется с функциональным стилем, не требуя от разработчика особых усилий. В свою очередь, Lodash заявил о себе как универсальный инструмент, способный на многое, но без лишней суеты.

Придерживаться единого стиля написания кода во всем проекте также полезно. Это создает ощущение единства и позволяет новичкам быстро влиться в команду. Чем быстрее они адаптируются, тем быстрее смогут внести свой вклад в общее дело.

Заключение

Ramda, безусловно, обладает сильными сторонами и занимает значительное место в сфере функционального программирования. Но эта библиотека не всегда может оказаться самым подходящим вариантом, особенно если в проекте используются различные стили создания кода. В таких ситуациях стоит придерживаться современного нативного JavaScript и Lodash. В конечном счете наша цель  —  написать эффективный и понятный код, а не бездумно использовать сложные инструменты ради них самих.

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Pavel Marianov: Farewell, Ramda

Предыдущая статьяРост производительности машинного обучения с Rust
Следующая статьяКак избежать повторных обновлений представлений SwiftUI