Вся мощь IntelliJ IDEA на примере одного языка (в картинках)

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

    Однако, одна из проблем такого подхода заключается в том, что это значительно удорожает и замедляет сам процесс разработки. Что если всего этого нет? Тогда на помощь приходит IDE, благодаря которой можно изучить текущую логику по “голому” коду.

    Когда мы разрабатывали платформу lsFusion со встроенным языком, то у нас было несколько вариантов. Либо изобретать велосипед, и писать с нуля свою собственную IDE, как сделал в свое время 1С, или реализовать плагин к уже существующей. Мы пошли по второму пути, и в этой статье я покажу, что получилось.

    Так как сама платформа разработана на Java, то у нас были две основные опции: Eclipse или IDEA. Мы остановились на последнем варианте и не прогадали. Когда мы принимали решение, IDEA еще была недостаточно популярна, но с тех пор, они вырвались в лидеры на рынке, а Eclipse тихонько отстает.

    На разработку самого плагина ушло не очень много времени, так как удалось во многом задействовать код, используемый непосредственно при выполнении платформы. Таким образом, мы минимальными усилиями получили очень мощную IDE, во многих аспектах значительно превосходящую по функционалу IDE многих других ERP-платформ (как родных, так и построенных на Eclipse).

    Роль IDE в разработке тяжело переоценить. Несмотря на то, что многие разработчики до сих пор используют vim и считают, что так и надо. Эта позиция имеет право на жизнь, если один человек разрабатывает и в дальнейшем поддерживает этот код. Однако, в более крупных проектах, где участвует большое количество людей очень важна их взаимозаменяемость. Сотрудники болеют, уходят в отпуск, увольняются в конце концов. Кроме того, нагрузка по разным проектам неравномерна, и иногда требуется на один из них подключить большее количество людей, чтобы уложиться в короткие сроки. В такие моменты к доработкам приходится подключать новых людей, которым нужно быстро разобраться в том, как в текущий момент работает программа, и внести необходимые изменения. И тут на первый план выходит IDE.

    В первую очередь от IDE нам требовалось следующее:

    • Поддержка синтаксиса. Подсветка ключевых слов, автоподстановка, подсветка ошибок.
    • Навигация. Переход к объявлению, поиск использований, поиск по текстовой строке, файлу или названию и т.д.
    • Анализ. Иерархия классов и вызовов, а также свойства и действия класса.
    • Рефакторинг. Переименование классов, свойств и действий.
    • Визуализация форм. Отображению разработчику текущего дизайна определенной формы.
    • Метапрограммирование. Возможность на лету генерировать код на основе метакодов.
    • Отладчик. Возможность ставить breakpoint’ы (в том числе и с условиями), отлаживать императивную логику, смотреть watches.
    • Language Injection. Навигация, рефакторинг, автоподстановка и подсветка синтаксиса lsFusion при использовании в других языках — Java и JasperReports XML.

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

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

    Поддержка синтаксиса


    Плагин умеет подставлять допустимые ключевые слова, возможные свойства, автоматически обнаруживать различные ошибки:

    image

    Навигация


    Возьмем логику из примера Управление материальными потоками. Предположим, нам нужно посмотреть, где объявляется свойство Цена. Для этого нужно под пользователем с правами администратора навести указатель мыши на заголовок нужной нам колонки:

    image

    В появившемся окне сразу видно, в каком модуле это свойство создано (Shipment), какой номер строки в нем (37), таблица в которой хранится (_auto_Shipment_ShipmentDetail), и ряд другой информации.

    Чтобы перейти непосредственно к объявлению свойства, нужно начать поиск файла и в появившемся диалоге ввести Shipment:

    image

    image

    Затем при помощи Navigate — Line/Column переходим к 37й строке, где видим объявление свойства:

    image

    Нажав CTRL+ALT+F7, стоя курсором на нужном свойстве, можно быстро найти все его использования по всем проектам:

    image

    В данном случае, первое использование цены идет в расчете суммы по строке. Два последних — это добавление на соответствующие формы.

    При необходимости можно включить поиск только по записи в данное свойство, если убрать соответствующую опцию:

    image

    Тогда в списке останется только запись в это свойство. Чтобы узнать, какое конкретно значение в него записывается нужно встать курсором на salePrice и нажать Go To Declaration or Usages. Дальше вернутся через Navigation — Back и перейти к объявлению свойства item:

    image

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

    Рефакторинг


    Часто бывает ситуации, когда требуется изменить название свойства, класса, формы или любого другого элемента в системе. Для осуществления такого действия нужно стать на этот элемент и нажать Refactor — Rename:

    image

    Переименование элемента автоматически изменяет исходный код во всех местах его использования. Кроме того, если создан файл migration.script, туда будут добавлены соответствующие записи. Серверу необходимо знать изменения имен, чтобы, например, автоматически мигрировать данные из одной колонки в другую. Иначе, невозможно отличить переименование от создания нового свойства с другим именем.

    Анализ


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

    Для этого IDEA, практически из коробки, позволяет просмотреть структуру выбранного класса (доступные для этого класса свойства и действия):



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

    • наследований выбранного класса
    • использований выбранного элемента (например, свойства или формы)



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

    Визуализация форм


    В lsFusion структура и дизайн форм задается в том же коде, что и доменная логика, при помощи специальных конструкций. При этом разные части формы могут быть объявлены в разных модулях, а при запуске сервера будут “сливаться” воедино в зависимости от подключенных модулей.

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

    • Показывать текущий дизайн и иерархическую структуры формы в специальном окне
    • Находить элементы в структуре формы
    • Выделять выбранный элемент формы в дизайне

    Вот как это выглядит в IDE:

    image

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

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

    Метапрограммирование


    Иногда возникает потребность создавать однотипный код для различных задач. В lsFusion есть механизм метакодов, который позволяет генерировать код на основе некоторого шаблона. При этом, в случае изменения метакода, будет происходить автоматическая обновление кода. По сути, это автоматический copy/paste с возможностью замены определенных идентификаторов на заданные значения.

    Для включения этого механизма нужно сначала активировать его в меню. После этого IDE будет сама автоматически изменять соответствующий код.

    image

    При запуске сервера будет использоваться только сгенерированный код. Сами шаблоны META при запуске сервера учитываться не будут.

    Кстати, реализация возможности метапрограммирования заставила нас внести еще один вклад в open-source (в данном случае в лице Intellij IDEA). Дело в том, что в ERP метакоды используются достаточно активно, и, соответственно, часто возникает необходимость сгенерировать код / удалить сгенерированный код. Это приводит к большому количеству асинхронных изменений файлов, что, в свою очередь, приводило к весьма своеобразному багу. Проблема в том, что в самом JetBrains его не могли воспроизвести, поэтому все свелось к тому, что нам самим пришлось написать неработающий юнит-тест. Это конечно заняло несколько дней, зато косвенно помогло нам при реализации следующих двух возможностей.

    Отладчик


    Когда по коду совершенно непонятно что происходит, то приходится обращаться к отладчику. На любой строке императивной логики (действия, события, ограничения) можно поставить breakpoint. Как только выполнение сервера достигнет этой точки, оно будет остановлено и управление перейдет в отладчик. В этот момент можно смотреть watches, а также продолжать выполнение построчно. Слева будет показан stack trace, по которому можно перемещаться как при отладке обычного Java приложения.

    image

    При просмотре текущих значений можно обращаться как текущим объектам (например, Shipment s), так и к любым другим объектам из базы (например, Item i). Однако, разработчик сам несет ответственность за добавление в watches данных, считывание которых займет много времени или памяти, и приведет к падению производительности.

    Также можно ставить breakpoint'ы на конкретное свойство. Выполнение остановится в любом месте, когда в него будет производится запись:

    image

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

    Для реализации отладчика мы на самом деле использовали существующий в IDEA Java Debugger. То есть отлаживается платформа как обычное Java приложение, но для lsFusion действий мы создаем proxy java методы и подменяем их отображение на свой код (я так понимаю в IDEA это сделано для поддержки Scala и других оберток над Java). И вот тут был забавный момент. В какой-то момент разработчики IDEA сделали конструктор своего Java Debugger закрытым (private). И если ситуацию с вызовом private методов еще можно обойти через Reflection, то как унаследоваться от класса с private конструктором — непонятно. Но как раз в то время шли разборки с багом из верхнего раздела, и мы «по бартеру» решили попросить людей из JetBrains сделать этот конструктор обратно protected, на что они отреагировали очень оперативно (за что им конечно большое спасибо).

    Language Injection


    Одна из самых необычных возможностей IDEA — возможность обеспечивать поддержку своего языка в строковых константах других языков. Для этого достаточно сказать IDEA, какие именно строковые константы относятся к твоему языку, дальше IDEA сама автоматически:

    • генерирует виртуальный файл (или несколько файлов) с заданными префиксами для каждой строковой константы
    • создает в редакторе исходного файла для всех констант своего рода «окна» в этот виртуальный файл
    • обеспечивает в этом виртуальном файле поддержку всех возможностей «внедряемого» языка, таких как подсветку ошибок, переход к объявлению, автодополнение, поиск использований и, что особенно важно, рефакторинг. То есть, при переименовании любого элемента на одном языке, он автоматически переименовывается и во всех строковых константах, ссылающихся на этот элемент в других языках. Таким образом вы автоматически защищены от «битых» ссылок.



    Здесь в IDEA тоже был (и сохраняется) небольшой баг. Когда виртуальный файл большой, то если при переходе к использованию IDEA должна перейти на начало «окна внедрения», она на самом деле переходит на конец предыдущего «окна внедрения» (то есть например на предыдущее использование свойства в Java-файле). Существует конечно, простой обход этого бага — создавать по отдельному виртуальному файлу на каждый строковый литерал. Но такой подход тормозит когда использований больше 30, поэтому в этом случае все равно приходится использовать один большой виртуальный файл (с другой стороны, когда использований много, найти правильное, то есть следующее, использование не так уж и сложно). Мы просили исправить этот баг опять-таки в рамках «обмена услугами», и разработчики JetBrains его вроде как пофиксили, но, как позже выяснилось, как-то не так (это еще по коммиту было видно, но мы подумали, что просто не до конца его поняли). Впрочем, у нас к этому багу все уже давно привыкли, благо ситуация с использованием больше 30 элементов в одном файле встречается достаточно редко.

    Заключение


    В статье описаны только основные варианты использования. В ней также присутствуют возможности по поиску реализаций абстрактных свойств и классов, визуализации зависимостей между модулями и свойствами, автоматической генерации форм на основе xml/json и многое другое. И, конечно же, есть встроенная интеграция с основными системами контроля версий Git и Subversion, а также поддержка Maven и Ant.

    Пойдя по пути разработки плагина к IDEA, мы небольшими усилиями получили очень мощную бесплатную интегрированную среду разработки, превосходящую по многим параметрам IDE конкурентов.
    lsFusion
    205,08
    Не очередной язык программирования
    Поделиться публикацией

    Похожие публикации

    Комментарии 10

      +1

      Да, IDE семейства IDEA шикарны, говорю как бывший vim'ер, умеющий из vim-а сделать все что угодно. (IdeaVIM плагин, конечно, мне все равно строго необходим :-)


      Может, напишете статью о разработке плагина? Было бы очень интересно!

        0
        Да, напишем, я думаю. Мы на самом деле реализовывали плагин не на голой IDEA, а с использованием их же GrammarKit и JFlex. Это как я понял считается стандартом для custom made языков у них (хотя точно не уверен), поэтому в статье и говорили что практически из коробки. И если с GrammarKit и JFlex набить руку, поддержку любого относительно несложного языка можно сделать за пару месяцев. Хотя конечно тот же GrammarKit нам пришлось немного подпиливать в части автоподстановки.
        –1

        deleted

          –2

          надо сделать просмотр кто и когда ставит минусы, ошибся топиком с комментарием, удалить его нельзя, отредактировал, уже влепили минус, круто работает система кармы на данном ресурсе

          0

          Но вообще похоже что ваш язык можно, было бы заменить DSL-ем на Kotlin.

            0
            А можно пример, как это выглядит на примере языка со сложной грамматикой, и как это хотя бы приблизительно сделать?
            0
            как сделал в свое время 1С

            1С уже тоже уже отказывается от велосипеда. Сейчас уже есть IDE для 1С на базе Eclipse.
              0
              Спасибо за цикл статей, очень познавательно. А вы могли бы в одной из своих будущих статей затронуть проблемы, которые могут встретиться при разработке на вашей платформе, ограничения, подводные камни? Что можно решить, обойти, а с чем придётся смириться? Мы же тут все понимаем что за всё приходится в какой-то момент платить — пишем на низком уровне, большие объёмы кода, который невозможно поддерживать, пишем на высоком уровне — ограничения платформы — и вам как никому должны быть известны все ваши слабые стороны.
                +2
                Да, была мысль, после статей Почему не…, написать Почему не lsFusion.

                Хотя если вкратце, есть три основных нюанса:
                1. Из-за высокой декларативности и сложных оптимизаторов, есть вопросы к Hot Deploy и холодному старту. Для Hot Deploy при небольшом изменении логики по сути необходимо сбрасывать абсолютное большинство кэшей, которые есть в системе, плюс обновлять некоторые данные, которые технически кэшами не являются, но зависят от логики в целом (скажем изменение графа классов, потребует обновление практически половины данных сервера приложений). Плюс для ускорения первоначального старта внутри построена архитектура на многопоточный запуск сервера и это тоже определенным образом усложняет реализацию Hot Deploy. Что важно, фича Hot Deploy важна не столько для продакшна, а сколько для разработки (чтобы быстро можно было посмотреть результат). Изначально предполагалось что разработчики будут стартовать только разрабатываемый модуль, но тут проблема, что при переключении модуля платформа считает что все существующие элементы в базе удалились и чистит данные. Поэтому по факту разработчики все равно стартуют всю логику (что может занимать до минуты). В будущем возможно удастся решить эту проблему (или заставить платформу не удалять данные, или действительно дореализовать Hot Deploy, сбросом кэшей / псевдокэшей), но пока проблема существует. Плюс с холодным стартом — так как невозможно предугадать действия пользователя, по сути программа дописывается на лету, а значит многие сложные оптимизаторы работают уже на рабочей базе. Как правило пропорция где-то такая: первый проход по ветке раз в 7 медленнее второго. На 3-м где-то все становится уже хорошо.
                2. Из-за оптимизаторов и кэшей достаточно большой memory footprint. То есть грубо говоря ERP сервер приложений может зажрать сразу 8-10 гб, и это будет независимо от числа пользователей. То есть даже для 10. Конечно на средних / больших проектах это не критично, на как микросервис lsFusion пока плохо подходит.
                3. «Большая сила — большая ответственность». Никто не мешает построить свойство из 1000 операторов (например прибыль всего предприятия), а потом материализовать (не материализуя ни одно из промежуточных свойств). Сложность инкрементального обновления при этом будет мягко говоря большая. Там внутри сотни оптимизаторов, которые будут бороться за жизнь до последнего и возможно справятся, но при нормальном уровне изоляции база все равно будет в вечном update conflict'е.
                Ну и document(DocumentDetail d) < — NULL; никто не мешает написать. Правда в таком случае, такое действие будет выполняться очень долго, с огромной вероятностью приведет к нарушению каких-либо ограничений, так что к разрушительным последствиям очень вряд ли приведет (в нашей практике были локальные только инциденты, после чего мы сделали максимальные подсветки в IDE которые только можно + разнообразные защиты). Та же ситуация может быть когда администратор включит логирование каких-то очень больших данных (против этого тоже есть ряд защит). Ну и так далее в таком духе.
                  0
                  Из-за оптимизаторов и кэшей достаточно большой memory footprint. То есть грубо говоря ERP сервер приложений может зажрать сразу 8-10 гб, и это будет независимо от числа пользователей. То есть даже для 10. Конечно на средних / больших проектах это не критично, на как микросервис lsFusion пока плохо подходит.


                  Микросервисы — это «побитые на маленькие по функционалу компоненты».

                  Количество оперативки — ничуть не является фактором, что ограниничивает применимость в микросервисах. Если использование локального диска еще является ограничивающим фактором (горизонтальное масштабирование не работает), то память нет. Упомянутые цифры для современности не являются проблемой.

                  «Микро» в названии микросервисов — не значит мало ресурсов.
                  «Микро» в названии микросервисов — значит малый функционал компонента (сервиса).

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое