Как мы выбираем языки программирования в Typeable
Неоднократно меня спрашивали, почему я предпочитаю использовать такие языки программирования как Haskell и Rust, т.к. они не являются самыми широко используемыми и популярными инструментами. Этот пост написан с целью демистифицировать то, что происходит у меня в голове, когда я думаю о выборе технологии.
Разработка программного обеспечения с требованиями долгосрочной эксплуатации и определенного уровня надежности в некотором смысле сродни шахматной игре. Варианты развития событий и в том, и в другом достаточно тяжело осознать человеческим мозгом, роль опыта играет большое значение и каждый/ход выбор, может иметь критическое значение. Сходство продолжается и в том смысле, что, как и шахматы, разработка очень «позиционная» — целая серия ходов может быть направлена на подготовку к маневру, который приведет к выигрышу пешки. Может показаться, что это всего лишь одна пешка, но для серьезной игры это может быть весомым преимуществом. Подобно позиционной игре за шахматной доской, во время разработки и развития больших проектов мы постоянно принимаем решения, которые направлены на решение более масштабных задач или выполнение требований проекта. Эффект от каждого, даже маленького, решения имеет свойство накапливаться к эндгейму, или к тому моменту как программный продукт находится в эксплуатации. Отличием же, усугубляющим ситуацию, является то, что разработка программного обеспечения, в отличие от шахмат, не решается на компьютере, и нельзя просто взять и найти лучшие ходы, запустив компьютерный движок. Поэтому необходимо принимать множество решений, инкрементально приводящих нас к этой цели, и все средства, которые улучшают нашу стратегическую «позицию», хороши.
Решения можно упрощенно разделить на несколько категорий: архитектурные, методические и инструментальные. Архитектурные говорят о том, как мы структурируем проект. Методами определяется то, как мы организуем процесс работы, убеждаемся в качестве и корректности реализации. Инструменты же определяют, с чем команде разработки нужно работать, чтобы получить результат. Для полного цикла разработки сегодня используется большое количество инструментов: нужно формализовать требования, процесс разработки, нужно написать программный код и протестировать его, собрать релиз и т. д. Несмотря на такое обилие задач, самое большое значение может сыграть выбор языка программирования, ведь он определяет совокупность следующих параметров:
- Baseline по скорости работы ПО.
- Особенности распространения и эксплуатации программы, например требование интерпретатора или возможность статической линковки.
- Экосистема библиотек и компонентов, которые можно переиспользовать. Отмечу что важно не только количество библиотек, но и качество релевантных для вас.
- Возможности параллельного/конкурентного/асинхронного исполнения программ, что может быть важным для многих систем.
- Сложность обучения людей выбранной технологии, что значительно влияет и на сообщество языка, и на перепрофилирование разработчиков.
- Выразительность языка.
Кроме того, выбор языка программирования может значительно повлиять ещё и на методические вопросы, например, инструменты экосистемы языка могут определять, как и в каком объеме пишутся юнит-тесты. Хорошая инфраструктура для property тестов может дать толчок в этом направлении, а отсутствие хорошей инфраструктуры для юнит-тестов, может осложнить их написание и поддержку.
Также инструменты влияют и на вопросы архитектурного толка — переиспользование модулей в системе привязано к тому, как удобно концептуально делить блоки и структурировать код. Например, явная работа с системами эффектов позволяет обобщать код сильнее и убеждаться, что модуль программного кода не осуществляет операций ввода-вывода, таких, как работа с сетью и диском, что позволяет рассуждать о безопасности и архитектуре.
Исходя из этого, следует отдавать себе отчет в том, что правильный выбор языка программирования для проекта и команды может иметь далеко идущие последствия. Памятуя про шахматную аналогию, держим в уме, что каждый маленький плюс пойдет в копилку языка и может сыграть значительную роль в большой разработке. Стоит также оговориться, что речь идёт о выборе средства разработки для ситуаций, где нет жестких ограничений по выбору технологий в связи, например, с большой экосистемой, уже на чем-то написанной. В Typeable мы руководствуемся следующими соображений для языков общего назначения:
- Статическая типизация должна поддерживаться языком программирования. Это позволяет сократить длительность для каждой итерации цикла изменения и валидации кода для разработчика. Также это позволяет существенно снизить количество багов, как со стороны функциональных требований, так и безопасности ПО.
- Алгебраические типы данных — очень сложно переоценить влияние этой фичи, после того, как начинаешь ей пользоваться. Очень доступная и абсолютно необходимая при моделировании инвариантов вещь. Те же типы-суммы настолько незаменимы, что выбрать язык, в котором их нужно моделировать через другие конструкции, это ставить себе преграды уже на первом шаге.
- Гибкая возможности для поддержки и исполнения многопоточных программ. Языки с GIL (Global Interpreter Lock) сразу же не удовлетворяют этому требованию. Хочется иметь возможность хорошо утилизировать возможности железа и иметь достаточно высокоуровневые абстракции для работы.
- Достаточная экосистема библиотек, оцениваем субъективно их качество в том числе. Не считаем необходимым все подключать в виде библиотек, но наиболее базовые вещи, вроде биндингов к популярным базам данных, должны быть в наличии.
- Светлые головы в коммьюнити разработчиков на этом языке программирования. Профиль разработчика, которого бы мы хотели видеть своим сотрудником это заинтересованный в CS и разработке человек. В противопоставлении этому можно поставить статус «легких в освоении» технологий, которые привлекают людей в IT ради легкой наживы, что сильно размывает рынок спецов.
- В нашем распоряжении должны быть языки программирования, которые позволяют реализовывать ПО с жесткими требованиями ко времени обработки и потребления памяти.
В результате всего вышесказанного в нашем ящичке с инструментами есть достаточно блоков, которые позволяют нам занять уверенную позицию во многих проектах. Возвращаясь к шахматной аналогии, это наши принципы, которые позволяют вести позиционную игру. Позиционная игра — игра направленная на создание долгосрочной позиции, открывающей игроку возможности и минимизирующей слабости. Она противопоставляется игре атакующей, «острой», где есть большая доля риска, а атакующий игрок пытается эту игру завершить до того, как его противник сможет занять хорошую оборонительную позицию. «Острая» разработка это олимпиадное программирование, MVP для маркетинговых экспериментов, многие задачи в data science, да и зачастую создание ПО, сопровождающее Computer Science публикации. Их объединяет то, что долгой поддержки они зачастую не требуют, им только нужно отработать в определенный момент времени. Позиционная же игра это игра вдолгую, где ключевыми показателями являются поддерживаемость и обновляемость. Именно этим мы и занимаемся, и нам нужен хороший фундамент, для того чтобы быть уверенными в долгосрочной работе ПО, которое мы пишем и обновляем. Такие проекты тоже могут начинаться с MVP, но они делаются с совсем другими предпосылками.
Почему же список соображений для выбора технологий именно такой? Причин тут несколько. Во-первых, желательно исключить вопросы модности и трендовости технологий, для того чтобы увеличить предсказуемость на долгом промежутке времени. Компилятор с долгой историей и активным сообществом это пусть и консервативный, но надежный выбор, в противопоставление новым сверкающим вещам, появляющимся из года в года. Наверняка часть из них перейдет из последней категории в первую, но мы об этом узнаем позже, вероятно, через годы. Вместо трендов мы пытаемся использовать фундаментальный Computer Science и большое количество исследований по этой теме, которые нашли применение в используемых нами языках программирования. Например: теория типов это смежная математике и CS дисциплина, которая занимается фундаментальными вопросами формализации требований. Это ровно то, что нам нужно для написания ПО. К тому же это совокупный опыт других людей, занимающихся точными науками, и как-то глупо, на мой взгляд, этим опытом пренебрегать. Лучше взять за основу такую дисциплину, чем не взять ничего, либо же взять субъективное мнение, основанное на жизненном опыте одного отдельно взятого человека.
Во-вторых, мы ищем реализацию наибольшего количества взятых нами принципов в языках программирования и компиляторах. По этой причине вдобавок к нашему любимому Haskell, в нашем ящичке инструментов стал появляться Rust. С real-time требованиями и жесткими ограничениями на использование памяти нам нужно что-то достаточно низкоуровневое. Строгость типизации в C все же оставляет желать лучшего, поэтому если есть возможность использовать Rust для такой задачи, мы предпочтем это сделать.
Третья причина: мы делаем ПО в первую очередь для наших клиентов, и мы бы хотели, чтобы они были защищены от наших предрассудков. Поэтому при выборе инструмента риск не может превышать некий уровень, который должен быть оговорен с клиентом. Но даже с такими условиями у нас появились довольно маргинальные технологии вроде GHCJS, т.к. при совокупном разборе сильных и слабых сторон картинка все равно была привлекательной для нас и наших клиентов. Про то, как мы пришли к этому решению, мы уже писали: Elm vs Reflex.
При работе с большими кодобазами и сложным ПО все средства и теоретические обоснования хороши, ведь нужно как-то уметь держать эту сложность в узде. Наша идея правильного подхода — защищать каждую пешку, улучшать свою позицию плавно и аккуратно, чтобы проект смог в хорошем состоянии дожить до того момента, когда он сможет сыграть решающую роль для бизнеса наших клиентов. Чего и вам желаем.