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).
Мы объявляем клиентскую часть — API для нашего сервера.
После этого мы определяем серверную часть — то, что будет происходить на сервере.
Мы готовы к использованию.
Соответственно, мы создаем этот поток, получаем его 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.
Отличия в коде минимальные, и не всегда понятно, на чем написан конкретный код.
Код 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-функционал), поэтому намного более проработанный. И эта технология будет развиваться.
Выводы
Elixir — это не Ruby.
В Elixir другая композиция кода.
Phoenix Live View — killer feature!
Beam торчит стектрейсами наружу (в 22-ой версии он стал user-friendly, то есть developer-friendly.
Elixir влияет на развитие Erlang и Beam, то есть не работает в пустоте и влияет на всю экосистему.
Elixir (язык и платформа) развивается целенаправленно и методично, и это дает уверенность, что развитие продолжится.
Elixir можно использовать как замену Go для распила монолитов.