Привет.
Не так давно я снова начал чувствовать себя хорошо по поводу своего кода. Не то чтобы я ненавидел то, что писал раньше, но я чувствовал, что в этом чего-то не хватает. Хотя может показаться странным говорить об эстетике чего-то, единственная цель которого - помочь компании зарабатывать деньги, и это не видно многим людям, но давайте, какой разработчик не верит в «красивый» код? Код, который не только удобочитаем, но и прост для понимания, с ним приятно работать, поэтому его внешний вид очень хорошо влияет на продуктивность.
Тем не менее, я снова начал чувствовать себя хорошо по поводу своего кода, потому что мой новый проект потрясающий, и я могу использовать любую подходящую технологию. В моем случае это JavaScript, похваленный мной в этом блоге. Там, среди других преимуществ JS по сравнению с другими языками с несколькими парадигмами, я упомянул тот факт, что он позволяет вам писать своего рода функциональный код и имеет библиотеки, такие как Ramda и Lodash FP, которые сделать функциональное программирование с помощью JavaScript еще более доступным.
Составление функционального стихотворения
Само собой разумеется, что однажды, получив возможность использовать серверный JS в коммерческом приложении, я решил пойти ва-банк, следуя своим собственным советам. Мне потребовалось несколько попыток, но в конце концов я придумал технику написания своего рода функционального кода, который действительно имел смысл. Основная идея и краеугольный камень этого подхода - использовать концепции компоновки и конвейера функционального программирования, чтобы сначала написать список операторов, а затем начать описывать, что означает каждый из этих операторов. Звучит не очень убедительно? Пожалуйста, не закрывайте вкладку, и позвольте мне проиллюстрировать идею с помощью некоторых некачественных текстов helloworld.
При условии, что мы хотим реализовать службу, которая проверяет объект helloworld, помещает его в репозиторий, планирует уведомления по электронной почте и отвечает статусом операции.
Во-первых, давайте посмотрим, как выглядел бы этот код, если бы он был написан программистом, который знает о обещаниях и не против пойти на компромисс в отношении эстетики:
Теперь забудьте этот код и представьте, что мы начинаем с самого начала с таким модулем:
Выше показан модуль, который импортирует два локальных модуля, а также пакет Ramda npm. Как упоминалось ранее, Ramda - это библиотека, добавляющая в ваш код некоторую функциональную магию. У него есть API pipeP (), который позволяет нам конвейерно выполнять функции, возвращающие обещания, и возвращает функцию, возвращающую обещание 🤯. В конвейере каждая функция получает на вход возвращаемое значение предыдущей функции. Труба выполняется сверху вниз. Вы можете прочитать о них, например, в Википедии.
Но давайте продолжим. Теперь опишем, что делают некоторые функции:
Как видите, я «реализовал» два метода: validateInput () и createResponse (), возвращающие обещания. Примечательно, что validateInput () в нашем случае выдает исключение, если переданный ему объект hello недействителен, а в противном случае возвращает объект ввода. Еще одна вещь, которая изменилась, - это способ импорта зависимостей - вместо включения всего модуля мы используем деструктурирование для импорта только интересующих нас функций с именами, которые имеют смысл в текущем контексте.
Проблема со вторым изменением заключается в том, что у нас может не быть контроля над входами и выходами импортированных функций. Так, если, например, addHelloToRepo () не возвращает никакого значения или возвращает что-либо, кроме входного объекта, то scheduleNotifications () не получит ожидаемого аргумента и все взорвется! Как жаль! Вероятно, нам следует отказаться от этого подхода и забыть о функциональном программировании, поскольку мы забыли JQuery, Angular и COBOL.
Или мы должны? Попробуем спасти положение небольшим изменением в нашем модуле:
Решением нашей проблемы является оболочка под названием forwardInput (), которая использует концепцию под названием каррирование для частичного применения своих аргументов, чтобы мы могли выполнить функцию и вернуть ее входные параметры впоследствии. Это так называемая функция высокого порядка - функция, возвращающая другую функцию. С этого момента всякий раз, когда мы добавляем новую функцию в канал, мы можем обернуть ее с помощью forwardInput (), чтобы вернуть входные параметры функции, а не ее возвращаемое значение.
Что ж, это очень краткий пример того, как мы можем написать функциональный код, который имеет смысл. Преимущества использования этого подхода для меня выглядят так:
- Код более читабельный - в нем легко ориентироваться и понимать, что на самом деле происходит.
- Он побуждает вас писать чистые функции -, следовательно, есть некоторая уверенность в том, что никакие дальнейшие изменения не повлияют на существующие функциональные возможности.
- Модульное тестирование стало проще, поскольку каждая новая функция в главном конвейере будет означать новый набор модульных тестов и не должна сильно влиять на существующие модульные тесты.
- Добавление изменений в такой код в первую очередь аналогично его реализации. Таким образом, его должно быть легко поддерживать и улучшать.
Кстати об обслуживании… Давайте немного обновим наш код. Я хотел бы добавить некоторые функции ведения журнала, а также заполнитель для функции, которая будет хранить объект приветствия в озере данных или другом месте такого рода. Нравится:
Как видите, я немного соврал. Хотя я действительно реализовал функцию под названием saveToDataLake (), она мало что делает и просто расширяет функцию skip (), регистрируя имя пропущенного шага и перенаправляя его входные данные. . Предположим, что функция будет реализована, когда операторы и специалисты по обработке данных определят инфраструктуру для озера данных 🙄.
Теперь, когда наш модуль почти совершенен, я внесу в него несколько небольших изменений, прежде чем завершить эту статью. Я собираюсь добавить некоторую проверку, чтобы проверить, был ли добавлен объект hello в БД без проблем:
Теперь единственная функция нашего модуля делает следующее:
- Проверяет входной объект приветствия.
- Добавляет его в некоторый репозиторий и выдает ошибку, если операция не удалась.
- Планирует уведомления по электронной почте.
- Пропускает нереализованный шаг сохранения объекта в озере данных.
- Создает ответ.
- Записывает информацию в журнал после каждого шага.
- Остается читабельным.
- Ого.
Некоторые из функций в приведенном выше примере (например, skip (), forwardInput () и т. Д.) Являются очень общими и могут повторно использоваться другими модулями. Поэтому в реальном сценарии их следует переместить в отдельный модуль или даже в библиотеку.
Это оно?
Почти.
Хотя я использовал Ramda, чтобы проиллюстрировать мой новый любимый подход к написанию кода, это не единственный способ сделать это. Вы можете использовать метод compose вместо pipe и lodash вместо Ramda. Если вы используете Koa, вы можете добиться того же результата с помощью функций промежуточного программного обеспечения. Или даже с «обычными» обещаниями, если вас устраивает синтаксис thenthenthenthen. Возможности безграничны, и я серьезно сомневаюсь, что существует основной язык программирования, который не позволит вам реализовать код, аналогичный тому, что мы сделали выше. Попробуйте это сейчас, и вы, скорее всего, будете благодарны за это!
Есть также несколько ссылок, которые помогут вам понять некоторые концепции и / или технологии, о которых я говорил сегодня:
Ааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааа в
Пишите красивый код, и до следующего раза!