«В Минске не пишут на Clojure». Зачем Targetprocess выбрала для нового продукта редкий язык

26 октября 2017, 09:30

Минская продуктовая компания Targetprocess занимается разработкой Fibery, новой платформы для управления работой. Бета-версия продукта, вероятно, появится весной. Исходя из требований, ядро платформы было решено создать на Clojure. О минусе этого решения сооснователь компании Михаил Дубаков недавно сообщил на своей странице в Facebook: «По какой-то мистической причине в Минске нет разработчиков, которые хотят программировать на Clojure».

Читать далее

Иллюстрация: n01se.net

«Мы не собираемся создавать огромную команду, нам нужна ещё пара человек для работы над ядром продукта. Были ожидания, что Clojure станет своеобразным фильтром при поиске людей в проект, потому что «среднему» разработчику нелегко освоить и его, и функциональное программирование на приличном уровне», — рассказывает Михаил Дубаков.

В действительности фильтр оказался слишком узким.

«Многие считают, что изучать Clojure не нужно, потому что таких вакансий просто нет. Лично мне кажется, что глубокое погружение в LISP-подобный язык отлично развивает кругозор и дает ещё одну парадигму, сквозь которую можно смотреть на возникающие перед программистом проблемы. Знание Clojure/LISP делает программиста более эффективным», — считает сооснователь Targetprocess.

Сегодня над Fibery работает команда из 4 человек, из них два бэкенд-разработчика, пишущих на Clojure. В компании продолжают поиск энтузиастов в Минске и за его пределами — однако не исключают, что придётся создавать удалённую команду.  

Участник команды Fibery, разработчик Aндрей Щёткин описал причины выбора в пользу Clojure.

Задача

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

Схема модели данных в Fibery

Гибкость модели данных достигается материализацией её описания в метамодель, с помощью которой осуществляется управление. В программе на статически типизированном языке материализация описания модели данных заключается в факторизации типа в объект, то есть тип сущности становится first-class object с возможностью его создания/изменения.

Статически типизированный язык начинает со статики (раннее связывание везде) и добавляет динамику по требованию (зоны позднего связывания через полиморфизм).

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

При разработке модели на статически типизированным (далее CТ) языке речь идёт о регионах позднего связывания. Везде, где метамодель получает обращение на чтение (например, при обработке запроса за данными) проверяется соответствие запроса схеме типов, то есть происходит разыменование строкового значения в объект — регион позднего связывания.

Имея в решении на СТ-языке сопоставимое количество мест позднего связывания не через механизмы языка (речь о виртуальных методах — проверяемое на type check time позднее связывание) и количество мест раннего связывания с плюсами type check, в Targetprocess решили не пытаться «загибать» решение в динамическом стиле на СТ-языке, а попробовать сделать его на целиком на ДТ-языке.

Другими словами, в компании исходили из того, что первична именно динамика. Обычно происходит наоборот, и в СТ-языках конструируется инфраструктура для бесшовного взаимодействия с динамическим окружающим миром (dynamic в .NET) или адаптации внешней динамики к статике (F# TypeProvider).

В пользу выбора ДТ-языка говорят сразу несколько факторов:

  • динамика как сквозное свойство целевой системы и, как следствие, закономерное свойство решения: работа с сущностями, типы которых известны на этапе выполнения;
  • низкая концептуальная размерность: решение имеет намеренно минимальное количество ответственностей — манипуляция с моделью (сущности, связи) и манипуляция данными, экземплярами типов из модели;
  • инкапуслированность динамики — функциональность «выставляется» наружу через два интерфейса: язык запросов вокруг схемы и язык команд (квази-GraphQL). Интерфейсы «торчат» наружу через API или как public interface для in-proc запросов. Решение о первичности динамики на клиента public interface не распространяется, поэтому клиент ядра может быть написан на СТ-языке, эволюционируя по своим принципам и правилам;
  • миниатюрный размер команды разработки: 2 человека.

Путь Clojure

В Targetprocess пошли характерным именно для Clojure минималистичным путём и решили выразить все понятия решения через функции и данные (рекурсивная композиция гетерогенных мап), организованные в модули.

Преимущества: Clojure обладает отличной поддержкой стандартных структур данных (в частности, команду интересовали мапы), функциями вокруг этих структур данных («из коробки» есть, например, расчёт разницы между двумя рекурсивно вложенными мапами) и keywords (first-class имена). В дополнение — functional, immutable, jvm-hosted (интероп в jvm-экосистему).

Из минусов языка в компании отмечают отметить довольно крутую кривую обучения ввиду непривычного поначалу синтаксиса (Clojure можно подавать как внятно спроектированный «javascript со скобками»).

В Clojure удобно начинать с функциональной декомпозиции вокруг мапов (гетерогенных) и при разрастании логики решения сворачивать их, например, в типы (записи, протоколы). С учётом отсутствия безумной логики (вариаций по множеству параметров), около 90 процентов решения составляет прямолинейное функциональное программирование вокруг мап.

Возникает вопрос — как с этим жить и различать без типов, что где в решении «летает»? Ответ — keywords как first-class names. Сквозь всё решение «летают» мапы, общение с которыми происходит или через явные селекторы (:type obj-link), или через интерфейс модуля (schema/get-type-name type). Конкретный выбор зависит от того, пересекает ли мап границу модуля (попадает ли в окружающий мир).

Пример запроса на СТ языке:

Пример эквивалентного запроса на Clojure:

В результате подавляющее количество понятий в решении выразили через данные (мапы). Именованные мапы «летают» сквозь всю систему, а операции вокруг мапов выражены функциями, собранными в модули. Отдельные операции реифицированы в команды, запускающиеся через механизм predicate-based диспетчеризации вызова функции (мультиметоды).

И что?

Динамический домен возможно реализовать на статически типизированном языке, подключая динамический стиль по необходимости, но в случае с Fibery с учётом специфики задачи, набора требований, размера команды, в Targetprocess решили писать на динамически типизированном языке. Причина — в желании однородной работы с понятиями в коде без умножения сущностей для снижения случайной сложности (в виде добавления динамики в СТ-язык). Плата за решение — отсутствие проверки типа на этапе компиляции.

В связи с отсутствием статических типов контракты любого элемента в решении описываются логикой (библиотеки prismatic scheme, clojure.spec) и отслеживаются только на этапе выполнения. Итоговое решение проектируется таким образом, чтобы внятно сообщить, что и где пошло не так. Имея на руках first-class контракты, их можно компоновать, транслировать в документацию, API spec (swagger) и выполнять другие операции.

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

В компании преследуют цель написания предельно меньшего количества адекватного кода, удовлетворяющего требованиям, минимальными силами. Показателен и пример Fibery: размер кодовой базы составляет около 6 тысяч строк кода без тестов, размер бэкенд-команды — всего 2 человека.

Справка dev.by

Язык программирования Clojure работает на Java Virtual Machine и, соответственно, имеет прямой доступ ко всем библиотекам и API Java. А будучи диалектом Lisp, Clojure впитывает и его возможности. Согласно опросу Clojure-разработчиков, опубликованному в феврале 2017 года, чаще вего язык используют при разработке корпоративного, финансового ПО и в ритейле. При этом второй по частоте упоминания проблемой языка разработчики называют именно недостаток проектов для его использования. Для большинства инженеров, работающих с Clojure, основным языком является Java (33,08%), за ней следуют Ruby (14,28%), Python (14,15%) и JavaScript (13,18%).

Обсуждение