3 попытки и 8 лет перехода с Ruby на Elixir

—

от автора

Привет, я Дмитрий Клейменов и я начал писать код в 2011 году на C++/Qt. В 2012 году я пересел на Ruby, поскольку работать удаленно на C++/Qt в то время было невозможно. На Ruby я делал платежные системы, доставки, REST-интеграции, интеграции через iFrame и ImageMagick и прошел путь от джуниора до тимлида .

Первый раз я попытался пересесть на Elixir в 2014 году и даже сделал несколько toy-проектов. Однако на тот момент я не чувствовал себя достаточно уверенно в качестве программиста и не был готов сменить работу.

Через пять лет я понял, что уперся в потолок и в 2019 году сделал вторую попытку. За эти пять лет Elixir повзрослел, потому что мейнтейнеры его планомерно и последовательно разрабатывали и улучшали. На Elixir было написано достаточное количество больших проектов, поэтому разработчики определили best practices для типичных задач. Однако и в этот раз мне не удалось сменить работу.

Третью и уже успешную попытку я сделал в 2021 году. Все концепции были уже знакомы и в какой-то момент я понял, что готов к production-коду. Поэтому с 2021 года я являюсь full-time Elixir-разработчиком в компании Evrone.

Чем зацепил Elixir?

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

Почему мне нравится Elixir?

Моделирование реального мира

Еще будучи Ruby-разработчиком я прочитал отличную книгу Practical Object-Oriented Design in Ruby (POODR) Sandi Metz, которая написана для Ruby и описывает объектно-ориентированное программирование, что не совсем вяжется с функциональным программированием в Elixir. Однако эта книга пересекается с Actor Model в Elixir, который позволяет писать отличные программы.

Разделение больших систем на мелкие

Actor Model сам по себе стимулирует изоляцию, но в Elixir есть достаточное количество необходимых для этого специальных средств. Например, нет разделения памяти между различными процессами, и используется свой GC для каждой корутины, соответственно, нам не нужны ни семафоры, не мьютексы.

Кроме того, нет необходимости в синхронизации, потому что нет двух процессов, использующих одно и то же. Например:

Чтобы на Elixir создать асинхронное хранилище данных ключ/значения, необходимо просто объявить модуль и унаследовать поведение GenServer (стандартное поведение Elixir, унаследованное от Erlang).

  • Кроме того, нет необходимости в синхронизации, потому что нет двух процессов, использующих одно и то же.

    Например:

    Чтобы на Elixir создать асинхронное хранилище данных ключ/значения, необходимо просто объявить модуль и унаследовать поведение GenServer (стандартное поведение Elixir, унаследованное от Erlang).

Мы объявляем клиентскую часть — API для нашего сервера.

  • Кроме того, нет необходимости в синхронизации, потому что нет двух процессов, использующих одно и то же.

    Например:

    Чтобы на Elixir создать асинхронное хранилище данных ключ/значения, необходимо просто объявить модуль и унаследовать поведение GenServer (стандартное поведение Elixir, унаследованное от Erlang).

После этого мы определяем серверную часть — то, что будет происходить на сервере.

Мы готовы к использованию.

Соответственно, мы создаем этот поток, получаем его process_id и используем этот process_id для хранения и получения данных, то есть мы получаем готовое асинхронное хранилище данных.

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

Свой GC у каждой корутины (Beam Process, где Beam — виртуальная машина Erlang, которую унаследовал Elixir). Beam-процессов может быть очень много, они взаимосвязаны, но друг друга не ломают. Однако у такого подхода есть и минусы: GC и процессы потребляют больше памяти, поскольку необходимо дублировать все данные, используемые в двух разных процессах.

Одновременность как в реальном мире

  • Одновременность у Elixir, как в реальном мире. На Beam могут работать миллионы различных легковесных процессов (отличающихся от процессов в операционной системе) на всех ядрах CPU.

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

  • Встроенные механизмы отказоустойчивости (let it crash), когда мы позволяем какой-то части системы ломаться и восстанавливаем ее из заведомо правильного состояния.

  • Стандартные механизмы обработки ожидаемых ошибок, как в Ruby и многих других языках.

Low-level programming

Что отличает Elixir от Ruby — структуры данных более низкого уровня, чем в Ruby. Низкоуровневые структуры обеспечивают и гибкость, и одновременно удобство, то есть больший баланс между производительностью и удобством.

Elixir-ный способ написания производительного кода. Elixir способствует правильному написанию кода. Например, в Ruby есть только массивы, а в Elixir — разделение на списки и на собственно классические массивы (таплы), которые хранятся в памяти как в ячейках (одна за другой). Linked list можно итерировать, но при этом доступ к списку через индекс отсутствует. Напротив, таплы не предоставляют возможность итерации, то есть к ним нельзя обращаться по очереди, но обеспечивают удобный доступ по индексу. 

Лучшее, что есть в Ruby

Хосе Валим, создатель c Elixir, вышел из сообщества Ruby и забрал с собой все самое лучшее, что есть в Ruby.

  • Похожий синтаксис — одно из основных преимуществ Ruby.

Слева код на Elixir, справа — на Ruby
Слева код на Elixir, справа — на Ruby

Отличия в коде минимальные, и не всегда понятно, на чем написан конкретный код.

  • Код Phoenix похож на код Rails.

Такой код может прочитать любой, кто знаком с Ruby.

  • Hotwire, недавнее дополнение в Rails, вдохновлялось Phoenix LiveView.

  • Привычный для Ruby-разработчиков инструментарий, например, консоль или аналог Rake (Mix в Elixir).

Elixir после Ruby

Хосе Валим взял из Rails и из Ruby лучшее. Поэтому переход на Elixir после Ruby как-будто позволяет сбросить тяжесть с плеч. Все, что мне нужно было в Ruby, есть в Elixir. А все остальное, например, функциональное программирование, pattern matching, оказалось настолько близким и понятным, что программировать на Elixir очень приятно. Хотя многие разработчики на форумах пишут, что продолжают писать объектно-ориентированный Ruby-код на Elixir, от чего качество кода страдает.

Приходится привыкать к модулям вместо классов и объектам и процессам вместо объектов, но это не сложно.

Кроме того, PLUG-модель Phoenix отличается от Rails Way. Это функциональное программирование, когда данные проходят через функции, модифицируя и, в случае с Phoenix, добавляя, например, response, хедеры и т. д. (таким образом в Phoenix строится HTML или JSON).

LifeView сильно отличается от Rails View, однако благодаря Hotwire и Standard View в Elixir, как и в Rails, с течением времени разница становится меньше.

Проблемой перехода на Elixir после Ruby может стать отсутствие единого мнения о работе с ошибками (let it crash). Однако все эти проблемы решаемы.

Впечатления после года работы на Elixir

Впечатления, разумеется, очень субъективны. Но мои три попытки перехода на Elixir в течение восьми лет однозначно того стоили. 

  • Мне очень нравится функциональная модель Erlang + Elixir.

  • Elixir дает Erlang лаконичность и приятный синтаксис, как и синтаксис Ruby.

  • Очень высокая скорость работы и простота после привычных Ruby-монолитов.

  • Phoenix LifeView, по сути, Hotwire, который задумывался изначально (core-функционал), поэтому намного более проработанный. И эта технология будет развиваться.

Выводы

  1. Elixir — это не Ruby.

  2. В Elixir другая композиция кода.

  3. Phoenix Live View — killer feature!

  4. Beam торчит стектрейсами наружу (в 22-ой версии он стал user-friendly, то есть developer-friendly.

  5. Elixir влияет на развитие Erlang и Beam, то есть не работает в пустоте и влияет на всю экосистему.

  6. Elixir (язык и платформа) развивается целенаправленно и методично, и это дает уверенность, что развитие продолжится.

  7. Elixir можно использовать как замену Go для распила монолитов.


ссылка на оригинал статьи https://habr.com/ru/company/evrone/blog/707588/