Я работаю профессиональным инженером-программистом уже 10 лет. На моей стороне удача — я избежал пузыря доткомов и увидел рассвет Web2.0 и то, как он формирует наш мир сегодня. Мне также повезло в том, что карьера, которую я начинал, имела самый низкий барьер для входа, чем когда-либо прежде. Этот барьер, по моему скромному мнению, был прямым результатом парадигмы объектно-ориентированного программирования, которая получила широкое распространение 5–10 лет назад в виде Java и C#.

Как и большинство разработчиков того времени, я вскочил на подножку. ООП было там, где были рабочие места, и все, от крупных предприятий до стартапов, писали свой код на языках ООП. Им нужны были разработчики, и я был счастлив оседлать эту волну — не полностью оценив работу, которую очень умные люди вложили в языки ООП, чтобы дать возможность такому человеку, как я, перейти в эту область с небольшим знанием фундаментальных аспектов компьютерной инженерии. : управление памятью, жизненные циклы объектов, связывание и т. д. Этим в значительной степени занимался я, поэтому я писал полезные вещи быстро и полностью наслаждался тем, что был «инженером-программистом».

В течение 12 месяцев, по мере того, как мой опыт рос, я начал обнаруживать некоторые ограничения объектно-ориентированных языков. Эти ограничения были относительно незначительными, но могли иметь серьезные последствия для создания более сложного программного обеспечения. Чтение об архитектуре программного обеспечения помогло мне понять, «почему» происходят определенные вещи, и я мог кодировать многие из выбоин и передавать эти выводы команде вокруг меня.

Таков был ход событий. Что-то было написано, мы доводили это до готового к поставке состояния (это было до того, как культура DevOps стала нормой, поэтому это было гораздо более проблематично, чем я показываю), а затем мы выпускали это. Неизбежно следующие недели/месяцы будут бесконечной каруселью билетов в службу поддержки —> исправление ошибок —> выпуск —>.

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

Это было примерно в 2014 году. В то время Javascript становился большой новостью. Некоторая технология-выскочка под названием «Docker» угрожала революционизировать серверные операции и внести фундаментальные изменения в разработку. Я работал на крупном предприятии, и водопад все еще был нормой, но я видел, как дует ветер, и мог понять, почему это происходит — эти новые технологии были больше, чем просто языки, это были принципиально разные парадигмы.

Javascript, пока он широко использовался, был обратно совместим с предыдущими версиями спецификации. Соблюдение стандартов все еще было проблемой (IE6 все еще был там!), но когда вы объединили это с взглядом Docker на создание вещей, возникла закономерность. Обе эти технологии придают большое значение парадигмам, которые мы сейчас назвали бы «функциональными».

Примерно в 2015 году основными объектно-ориентированными языками в мире были Java, C++ и C#. Javascript очень быстро наверстывал упущенное. Стандарт ES6 укреплялся, и казалось (несмотря на то, что он вводил классы) Javascript становился более функциональным. Другие языки последовали их примеру. С# поддерживал лямбда-функции, начиная с .Net 3.5, Java7 представил их, и С++ также принял их. Это было фундаментальным изменением в том, как мы разрабатывали приложения, и это изменение можно объяснить в предложении «отделить данные от поведения».

Практически все ОО-языки разделяют понятие «класса», целью которого является инкапсуляция данных и поведения в единую объектную структуру. Это позволяет объекту Order иметь методы, принадлежащие Order наряду с данными. Это известно как инкапсуляция. Для бизнес-пользователя это имеет смысл, но в мире разработки программного обеспечения это компромисс, и этот компромисс — неизменность.

Представьте, что у нас есть объект Order, который принимает десятичное число, представляющее стоимость заказа при его создании. Затем у нас есть метод, который применяет скидку к заказу. Если 2 других класса имеют ссылку на один и тот же экземпляр порядка (практически все языки OO рассматривают классы как ссылочные типы как часть оптимизации, о которой я упоминал ранее). Теперь, если один из наших классов применит скидку к Заказу, данные других классов будут изменены без его ведома. Хотя эта проблема не исчезает при функциональном программировании, парадигма, согласно которой поведение и данные должны быть разделены, устраняет эффект «черного ящика».

Аналогичная ситуация возникает и с наследованием. Под прикрытием унаследованный класс в основном содержит ссылку на экземпляр своего базового класса. По той же причине, что и выше, всякий раз, когда к ссылке обращается более одного объекта, всегда есть возможность для данных внутри быть измененными без уведомления какого-либо другого класса. Такого рода проблемы трудно отлаживать и решать. Кроме того, он чаще встречается в производстве, чем в разработке, поскольку асинхронные системы (веб-серверы) чаще используют общие ссылки.

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

Это прозрение было сложно реализовать в мире объектно-ориентированного программирования, в котором я работал — C# все еще оставался классическим языком, и заставить моих товарищей по команде использовать больше функциональных возможностей C# (не говоря уже о переходе на функциональный язык) было все равно, что пытаться поймать туман! NodeJS был инструментом, который позволил мне реализовать идеи, которые, как я знал, улучшат качество нашего кода.

Наши первые несколько проектов NodeJS обернулись катастрофой. От старых привычек трудно избавиться, но, по крайней мере, мы получили от этого отличный опыт! К третьей итерации нашего проекта высокопроизводительного коммуникационного сервера вся команда перешла к функциональному кодированию. Мы создали мир, в котором (за немного больше памяти) мы могли бы иметь очень предсказуемую архитектуру приложения (в основном архитектуру каналы и фильтры), которая, несмотря на сотни зависимостей, имела несколько мест, где данные могли бы быть изменены без нашего явного знание.

Мы столкнулись с несколькими проблемами с инженерами (включая меня), пытавшимися превратить относительно простые функции в «чистые функции», поскольку «мы должны были это делать». Прагматизм необходим в разработке программного обеспечения, а в функциональном программировании есть много мест, где идеальное может стать врагом хорошего. Хотя я все еще время от времени наступаю на странную мину на этом конкретном минном поле, я искренне верю, что код, который я выпускаю сейчас, имеет более высокое качество, чем когда-либо, когда я работал чисто объектно-ориентированным способом.

А сейчас я работаю в DAZN (мы, кстати, набираем сотрудников!), и Javascript везде! Мне еще не приходилось иметь дело ни с одним непреодолимым черным ящиком в безобразном базовом классе, которого никто не понимает и все боятся. Это практически неслыханно за 10 лет разработки. Я считаю, что все это проистекает из фундаментального желания быть функциональным везде, где мы можем.

Принятие функционального мышления «делать одно дело хорошо» приводит к множеству конкретных функций, а не к какой-то одной всеобъемлющей модели God-Class, созданной 10 лет назад и используемой повсеместно. В отличие от God-Class, замена одной из этих функций оказывает настолько минимальное влияние, что часто тестирование на уровне компонентов выявляет любые проблемы. Честно говоря, я сомневаюсь, что когда-нибудь вернусь к ООП, если не найду способ продавать идеи, которые приходят из коробки, с помощью функционального программирования, и если это так, то почему бы просто не использовать функции?

Итак, у вас есть это. Вот как заядлый разработчик C# превратился в функционального новообращенного, ища решение, которое объектно-ориентированный подход, казалось, не смог предоставить. А теперь, если вы меня извините. Мне нужно написать еще один микросервис! 😄