Не создавайте собственный ЯП (DSL) для расширения функционала приложения
Когда вы хотите дать пользователю возможность писать плагины для своего приложения, вы встаете перед выбором того, как предоставлять API. Под катом я покажу, почему худшим решением для этого будет изобретение собственного языка программирования и парсинг исходников, а также причем здесь мебель.
ЯП — это не основная функция приложения
Представим, что мы открыли производство модульной мебели. Есть некие базовые элементы: столешницы, подставки, тумбочки и т.п. Есть линия производства, связанная с обработкой дерева: станки, пилы, лаки, все по последнему слову техники. Но все это надо как-то скреплять. Мы знаем, что есть 100500 компаний, которые специализируются на производстве скобяных изделий и болтов, есть некоторые стандарты для мебельного крепежа, которые были придуманы сообществом профессионалов для облегчения жизни их клиентам. Насколько же дальновидным решением будет развернуть дополнительную линию по производству собственных болтов, гаек и уголков?
Что мы можем выиграть?
- Можем таким образом брендировать наш товар, чтобы хомячок чувствовал свою «илитарность», и нес больше денег.
- Может это позволит нам не платить копирайт за какой-то из болтов, или решить проблему логистики.
- Можем войти в новый для себя рынок болтов и гаек, установив свой стандарт, который быстрее, лучшее и выше.
Но давайте на чистоту.
- «Илитарность» — это работа продажников. Продажник сделает или не сделает бренд Элитой как с новой линией производства, так и без.
- Копирайт, как правило (не всегда, конечно), дешевле, чем разработка с нуля. А решая проблему доставки одного элемента развертыванием новой линии производства, вы только усугубляете ее.
- Если мы хотим попробовать для себя новую сферу деятельности — не надо связывать ее с тем, в чем мы и так профи. Может показаться, что с мебелью болты толкнуть легче, но если болты не взлетят — они и мебель утащат с собой. По крайней мере, ту, которая уже стоит у клиентов.
Возвращаясь к нашим баранам: если вы делаете экосистему для расширений вашего приложения, значит у вас есть приложение. Оно делает что-то хорошо, что-то, в чем хороши вы.
KSP — Kontakt Script Processor, или линия производства болтов в мире digital audio
Расскажу одну историю про такой язык:
Kontakt — это ромплер (сэмплер) от австрийской компании Native Instruments. В настоящее время очень сложно найти проект с использованием виртуальных инструментов, в котором он не употребляется. За последние 10 лет Kontakt занял большую часть рынка сэмплированных инструментов. Секрет прост: в свое время, Kontakt предложил две инновации, которые перевернули подход к разработке сэмплированных виртуальных инструментов.
Первая инновация была прямо связана с его основной функцией: он очень бережно обходился с памятью (а сэмплы в wav — это тот еще отжиратель, как HDD так и RAM). NI сделали формат lossless компрессии с быстрым декодингом и написали революционную для своего времени систему буферизации аудио.
Второй инновацией стал KSP
До контакта было два способа функционально организовать записанные сэмплы в инструмент, управляемый посредством MIDI:
- Писать собственный движок с нуля на С++, или другом языке, способном использовать VST SDK от Steinberg (а ведь еще существуют и другие форматы плагинов, допустим AAX).
- Использовать готовый сэмплер, сделанный для музыкантов, не знакомых с программированием, но имеющим звуки, которые должны быть организованы в какую-то систему. Допустим, Giga Studio. Но такие ромплеры, как правило, были либо закрытые, либо для допиливания под свои нужды требовали не меньшего штата, чем голая разработка под VST SDK.
Контакт же угодил и тем, и этим: для быстрого прототипирования есть удобный GUI, понятный любому музыканту, прочитавшему мануал, а для дальнейшей доводки есть ни много ни мало язык программирования, с условиями, функциями (с версии 4) и стандартной библиотекой, представляющей API к большей части реализуемого через GUI функционала, а также к параметрам воспроизведения сэмплов напрямую. Кроме прочего, с версии 2 появилась возможность кастомизации интерфейса всякими свистелками и перделками, что позволило проявлять свою уникальность в почти что безграничных масштабах. А код разработчиков скрыт от глаз дважды: обфускацией и защитой от изменения инструментов.
Учитывая нарастающую популярность движка, а также внушительный срок активной разработки ромплера, на сегодняшний день Kontakt представляет из себя что-то вроде автомата Калашникова в мире Digital Audio. Прост в изучении, надежен как танк, имеет возможность допилки в разумных пределах под себя любимого, и держит под собой огромный рынок довольных юзеров.
Не все так радужно
Случилось неизбежное: инновация в виде KSP стала бичом. Пытаясь сделать синтаксис доступным для чайников, которыми являются музыканты, вместо решения реализации API на человеческом ЯП, Нативы написали собственный интерпретатор собственного языка, архитектура которого изначально не предполагала такого бурного полета фантазии разработчиков инструментов, который мы наблюдаем сейчас. Уже к версии 3 Нативы потеряли надежду угнаться за аппетитами пользователей, и просто стали клепать новые функции стандартной библиотеки, позволяя юзерам разбираться со средой разработки кода самостоятельно.
Тем более, что уже тогда появился Nils Lieberg KScriptEditor, форкнутый со Scintilla, долгое время служивший основной IDE для KSP. Смешно сказать, но когда Нативы поняли, что контакт не справляется с размерами скармливаемых исходников, они ввели в язык функции, даже не озаботившись передачей в них аргументов. И через месяц в KScriptEditor появились taskfunc
, передающие аргументы в функции, не принимающие аргументов.
Спустя время, Нилс понял, что наступает на грабли Нативов: собственную IDE разрабатывать смысла нет. Он перенес компилятор и реализованный функционал IDE на SublimeText2, и помахал ручкой. В настоящий момент бразды правления SublimeKSP несет разработчик, кажется, из Fluffy Audio.
Ну вы поняли)
И снова, уже кодогенератор, представляющий собой ни много ни мало язык, с системой импортов, парсером, компилятором, синтаксисом, отличным от KSP, но-таки поддерживающим обратную с ним совместимость, по неизвестной науке причине оказывается страшной горой из костылей, которые невозможно выбросить по причине обратной совместимости проектов разработчиков библиотек, которые разрабатывают свои KSP-движки годами.
Допустим, система импортов работает глобально по отношению к файлу, из которого запускается компиляция, поэтому для того, чтобы скомпилировать один модуль, находящийся во вложенной папке, надо полностью менять ему пути в импортах, в соответствии с его положением в структуре проекта. И Парень, поддерживающий его рад бы это поменять, но тогда он сломает проекты тех же Spitfire Audio далеко и надолго. А уже один этот факт усложняет модульное (уж промолчим про юнит) тестирование до чертиков.
Казалось бы, решение проблемы — в использовании симлинков, но что-то где-то там работает не так, как предполагается, и симлинки работают только частично. Такого рода проблем не одна штука. Кроме всего прочего, после Нилса, разработка велась не модификацией самого компилятора, получающего уже распарсенный код. А, опять же, из соображений обратной совместимости, добавлением отключаемых плагинов расширенного синтаксиса, каждый из которых получает первоначально порезанные на строки исходники, парсят их самостоятельно, и проводят модификации.
Учитывая то, что большая часть логики препроцессора держится на макросах и inline-функциях, которые разворачивают код в огромное полотно, хранящее в себе 80% всегда истинных или всегда ложных условий (за счет подставления констант на вход условия), которые сворачиваются обратно уже на этапе разбора AST, время компиляции «правильных» исходников сопоставимо с С проектами, это в интерпретируемом-то языке для чайников.
Сказать, что для разработчиков KSP стал болью — ничего не сказать.
Не контактом единым.
Я не могу привести примеры из других областей, но вот из сферы DigitalAudio:
- Lemur — приложение для лопат с десктопным редактором, позволяющая быстро делать красивые интерфейсы для общения лопаты по протоколу OSC. Имеет свой ЯП, который можно использовать в особых script-объектах, разбросанных по всему дереву проекта. Способа сделать для него компилятор типа того, что сделан для KSP — нет.
- Reaper — DAW с развитой экосистемой разработки расширений. В итоге там, где можно, продублировала свой ЯП JSFX (ReaScript) в виде API для C++, lua и Python.
- HISE — молодой комбайн для написания и сборки VST\VSTi, который рано или поздно убьет Kontakt от Шведского разработчика Christoph Haart. Внутри собственно редактора позволяет писать на модифицированном JavaScript, который парсится и компилируется в бинарник уже объектами C++. Идея с собственным парсером для введения дополнительных сущностей (допустим, регистровых переменных, если я правильно перевел) работала до тех пор, пока пользователи не перенесли свой код из редактора HISE в любимые IDE с подсветкой синтаксиса, статическим анализом и инструментами форматирования по JsPrettier. Сейчас Кристоф набросал пару заголовочных файлов для компиляции статических библиотек на C++, которые потом могут быть использованы как модули в редакторе. Параллельно он продолжает дополнять уже HISEScript (потому что назвать его JavaScript теперь нельзя) новыми функциями, но мы то знаем…
Пишите ваше собственное приложение, посвящая себя его основному функционалу, не разбазаривайте время на парсер, семантику и синтаксис. Это интересно, пока вы начинаете, но с большой вероятностью приведет в тупик. Язык программирования не может быть частью приложения: это своего рода — отдельная линия производства, требующая огромного времени на обслуживание, модификацию и поддержку комьюнити. В свою очередь, если вы надеетесь, что понизите порог входа для чайников — бросьте это дело. Настоящий чайник, как правило, боится вообще что-либо напечатать, и не будет себя утруждать и вашим простым синтаксисом.
В то время, как начинающим разработчикам плагинов для вашей программы можно просто сделать небольшой QuickStartGuide, знакомящий их с основными концепциями выбранного вами ЯП для расширения функционала и потихоньку скармливать ему ваш API, являющийся частью экосистемы этого языка.
P.S. Нет, писать свой собственный парсер для готового ЯП — тоже плохая идея.
Буду рад любой критике к статье, первый блин и все дела.