Кодогенерация и метагенерация в 1С

26.08.19

Разработка - Механизмы платформы 1С

В своем докладе на конференции INFOSTART EVENT 2018 EDUCATION Дмитрий Белозеров рассказал о разработке инструмента, позволяющего программно работать с метаданными 1С и писать скрипты для выполнения тех же действий, которые выполняет разработчик в конфигураторе –  с какими сложностями и нюансами пришлось столкнуться, и что получилось в итоге.

 

 

Здравствуйте, коллеги. Меня зовут Дмитрий Белозеров. Я работаю системным архитектором в компании LM Soft. Мы занимаемся внедрением систем проектного управления, управления жизненным циклом изделий и другими сложными и интересными решениями.

Также мы являемся владельцами нескольких собственных программных продуктов на платформе 1С, в процессе разработки которых у нас появился некоторый опыт и некоторые практики. И сейчас я бы хотел с вами ими поделиться. 

 

Что такое кодогенерация?

 

 

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

 

 

Но в самой идее, которая заложена в основе, нет ничего особо нового. 

 

 

Если вы в поисковике напишите слово «кодогенерация», то можете найти на эту тему много хороших статей и видео, в том числе и на русском языке – в последнее время я видел на YouTube несколько классных докладов по кодогенерации от разработчиков из мира Java, .NET, Swift и т.д.

Но надо понимать, что наша 1С – это достаточно специфическая среда разработки, и все эти практики требуют некоторой адаптации под особенности платформы.

 

 

Давайте сначала дадим определение, что такое кодогенерация. Это – очень широкое понятие:

  • Когда вы просто пишите код в конфигураторе, вы тоже в каком-то смысле занимаетесь кодогенерацией. 

  • Или, когда, например, вы в «Конвертации данных 3.0» пишите правило, а потом на его основе формируете код на языке 1С, это тоже кодогенерация. 

  • Или, например, когда вы заходите на сайт Facebook или ВКонтакте, то в этот момент тоже выполняется кодогенерация – весь Web2.0 технически основан на кодогенерации.

  • Но в нашем случае кодогенерация – это когда одна программа на языке высокого уровня пишет другую программу на языке высокого уровня. Я буду говорить сегодня именно об этом. 

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

После такого определения, я думаю, у вас должен возникнуть вопрос – зачем такие сложности? Зачем генератор? Зачем код на исходном языке?

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

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

  • Расширяете состав определяемого типа;

  • Включаете объект в состав подписки на событие;

  • Добавляете какую-то строчку в модуль формы, еще что-то. 

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

 

 

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

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

 

 

Если выражаться на языке бизнеса, то получается, что кодогенерация – это способ:

  • Во-первых, уменьшить себестоимость разработки;

  • А во-вторых, увеличить качество продукта.

 

 

Примеры применения кодогенерации для 1С

 

 

Чтобы вы не думали, что я говорю про каких-то «сферических коней в вакууме», я сразу приведу вам реальные примеры, с которых, собственно, и началась разработка нашего инструмента. 

Первый, наиболее понятный пример – это наш программный продукт, MDM-система (от английского Master Data Management), с помощью которой мы управляем нормативно-справочной информацией. Фактически это конфигурация на платформе 1С, которая содержит большое количество справочников. Причем, все эти справочники обладают общей функциональностью – возможность поиска дублей, нормализации и т.д. По сути, они разные, но однотипные. 

 

 

Когда мы на наших проектах собирали MDM-конфигурации для конкретных клиентов, у нас всегда возникал вопрос создания вот этих справочников, поскольку их состав от случая к случаю может очень сильно отличаться. Даже была инструкция о том, как добавить новый НСИ-справочник в нашу систему. Со скриншотами она занимала около 30 страниц.

 

 

В какой-то момент мы решили, что так больше жить нельзя, и реализовали собственный инструмент, который позволил выполнять эту работу автоматически. 

Как теперь выполняется разработка нашей MDM-системы?

  • У нас есть «ядро MDM» – некий набор общих механизмов, некая базовая конфигурация, в которой нет справочников НСИ, но есть некий эталонный справочник. 

  • Дальше есть Jinnee (джинн) – мы так назвали наш кодогенератор, который:

    • Берет эталонный справочник, копирует его;

    • Изменяет его необходимые свойства – добавляет нужные реквизиты, выводит их на форму;

    • И подключает справочник ко всем нужным механизмам. В общем, генерит уже готовую конфигурацию для клиента.

Соответственно, если мы вносим изменения в ядро – мы можем перегенерировать конфигурацию еще раз, и все эти изменения туда попадут.

Таким образом, у нас, во-первых, решилась проблема скорости внесения изменений, а во-вторых, повысилось качество разработки, потому что отпали ошибки, которые могли возникнуть по причине человеческого фактора.

 

 

Но наша MDM-система – это не единственный пример того, как мы используем этот инструмент. В процессе разработки мы пользуемся библиотеками (как стандартными библиотеками 1С, так и собственными) – у нас есть некоторые подсистемы, которые «кочуют» из проекта в проект, используются более чем в одной конфигурации. Соответственно, возникает вопрос поддержки такого кода, потому что не очень интересно внести какое-то изменение в библиотеку, а потом копировать его в десять конфигураций.

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

Получается «умный Continuous Integration» – именно интеграция на уровне кода, когда машина сама знает, что, куда и как нужно внести.

 

 

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

Дело в том, что проблема 1С-ных доработок – их слабая отчуждаемость. Например, если вы доработали что-то в типовой конфигурации, вы не можете выложить свою доработку на Инфостарт просто так, потому что выкладывать в качестве публикации типовую конфигурацию запрещено. Конечно, вы можете выложить расширение, но, во-первых, механизм расширений имеет ограничения, а во-вторых, вам придется писать отдельное расширение под каждую дорабатываемую конфигурацию. 

Эту проблему можно решить с помощью скрипта кодогенерации – можно написать некое «умное расширение», которое само знает, что ему нужно сделать при внедрении в конкретную конфигурацию. Вы запускаете скрипт, а он сам проанализирует, что это – такая-то конфигурация, с такими-то объектами метаданных, значит, нужно произвести такие-то действия. В итоге получается одно расширение, которое подходит для множества различных конфигураций. 

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

 

 

Следующий возможный кейс – это то, к чему мы все придем в ближайшее время – разработка через моделирование. 

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

Как я это вижу? 

  • У нас есть некая модель. Мы реализуем ее в любом редакторе, в любой нотации, в которой хотим.

  • Следующим шагом мы выгружаем эту модель в формат XML. 

  • Полученный результат конвертируем в Jinnee-сценарий. 

  • И этот Jinnee-сценарий, полученный на основе модели, используя некие библиотеки, выдает на выходе готовую конфигурацию.

Дальше я покажу, как это у нас реализовано. Я надеюсь, что так будет понятнее. 

 

 

Основные принципы проектирования инструмента

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

 

 

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

Почему это важно? Потому что писать код, который напишет за вас код, экономически выгодно только в том случае, если его написание не занимает у нас много времени. 

Например, если вы написали скрипт на 100 строк, который вам сгенерировал 1000 строк, вы сэкономили время и исключили риски человеческого фактора – это хорошо.

Но чтобы сэкономить это время, сам псевдоязык, на котором вы пишете скрипт для генерации, должен быть для разработчика максимально простым – в случае с 1С идеально было бы использовать родной 1С-ный синтаксис. Только в этом случае такая разработка становится экономически выгодной. Иначе можно и не начинать.

 

 

Второй принцип – это правильный выбор уровня абстракции. Что это означает? Дело в том, что наш инструмент работает на уровне XML. Это означает, что:

  • У нас есть исходный набор XML-файлов (мы выгрузили конфигурацию из конфигуратора в XML);

  • Что-то там с этими XML-ками программно сделали;

  • А потом их загрузили в конфигуратор и получили готовую конфигурацию.

Так вот, вся эта программная модификация XML не должна сильно напрягать разработчика. Разработчик не должен думать, как это реализовано на уровне XML. Например, чтобы изменить имя объекта метаданных, нужно внести изменения в нескольких местах XML-файла. Зачем разработчику это знать? Он просто напишет «ИзменитьСвойствоОбъектаМетаданных(<Имя>, <Значение>)».

Получается, что в нашем случае разработчик может мыслить именно в терминах объектов метаданных. Он вообще не знает, как это устроено с точки зрения XML, он делает то, что делал бы сам в конфигураторе руками. 

В этом есть и другие плюсы – если XML-формат выгруженных файлов изменится (а такое периодически бывало и с форматом конфигуратора, и с форматом EDT), нам не придется переписывать наши уже написанные скрипты.

 

 

Третий архитектурный паттерн, который мы применили, я назвал «Сыктывкар». Объясню почему. 

Пять лет назад я ездил в командировку в этот северный город, и тот район, где нас разместили, был очень печальный. И когда поздно ночью один мой коллега сказал: «У меня сигареты закончились, давайте сходим, купим», другой, более опытный коллега, который уже пожил в Сыктывкаре и знал, насколько этот район криминально опасный, ответил ему: «Тут за сигаретами нужно ходить с сигаретами». 

Перефразируя эти слова применительно к нашему случаю – разрабатывая инструментарий 1С, нужно использовать инструментарий 1С.

Примеров такого подхода тоже много – это Конвертация данных, АПК, Инструменты разработчика. Разработчики, используя платформу 1С, уже существенно облегчили себе жизнь.

 

 

Работа с инструментом на практике

 

 

Перейдем к самому инструменту – рассмотрим, как он выглядит. 

На слайде показан пример реального сценария генерации – в табличке перечислен список действий, каждое из которых выполняется по определенному правилу. Причем, действие – это просто текстовая строка, понятная для человека (чтобы было понятно, что тут делается). А вся магия кодогенерации выполняется в правиле.

 

 

Перейдем к правилу, посмотрим, как оно устроено. 

Принцип реализации правила независимо от используемой платформы всегда примерно один и тот же:

  • берется некий шаблон;

  • неким образом модифицируется;

  • и получается итоговый результат. 

Вопрос только в том, что в случае 1С считать шаблоном? Мы посчитали, что шаблоном могут быть какие-то объекты метаданных исходной конфигурации. Соответственно, у нас здесь есть визуальный интерфейс, где мы в дереве метаданных можем выбрать, какие объекты исходной конфигурации нам нужно переместить в нашу результирующую конфигурацию. Этот состав объектов мы можем буквально «накликать» мышкой.

 

 

В результате, при переходе на соседнюю вкладку у нас получится вот такой сценарий. Причем, обратите внимание – в сценарии есть один и тот же объект (имя объекта метаданных одинаковое), но команды разные. Это означает, что мы можем переносить как объекты метаданных в отдельности, так и подсистему целиком.

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

 

 

Возможность переносить объекты – это, конечно, хорошо. Но с ними, как правило, еще и нужно что-то сделать. И тут на сцену у нас выходят обработчики. Как раз все волшебство выполняется в обработчиках.

Как я уже говорил, мы старались максимально упростить работу с инструментом, чтобы разработчик мог там что-то описать, просто мысля в терминах метаданных, – изменить какие-то объекты, добавить реквизит, добавить табличную часть и т.д. 

На текущий момент у нас написано 84 правила (84 скрипта). Это уже достаточно большая кодовая база.

 

 

Здесь на слайде приведен пример функций программного интерфейса, которыми пользуется разработчик:

  • ИзменитьСвойствоОбъектаМетаданных; 

  • ПолучитьСвойствоОбъектаМетаданных;

  • ДобавитьЭлементФормы;

  • УдалитьЭлементФормы. 

Принцип, я думаю, понятен.

 

 

Разберем одну из этих функций. 

Как на практике происходит программная модификация объекта метаданных? 

  • Мы пишем «ИзменитьСвойствоОбъектаМетаданных()»;

  • В качестве первого параметра передаем полное имя изменяемого объекта – это у нас определяемый тип ВладелецФайлов;

  • Далее указываем, какое свойство этого объекта мы хотим изменить, и какое значение ему присвоить. 

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

 

 

Расскажу про возможность параметризации правила. Что это значит? 

Я уже рассказывал про пример нашей MDM-системы, где мы генерировали справочники, у каждого из которых свой реквизитный состав. Для генерации каждого такого справочника использовались:

  • Одно и то же правило;

  • Один и тот же алгоритм, по которому все должно задаваться;

  • Но в этот алгоритм нужно было как-то передать данные – с каким именем, с каким атрибутным составом, с какими свойствами мы должны создать справочник. 

Соответственно, у правила есть возможность задать некие параметры, которые потом при настройке сценария можно будет заполнить, и дальше, при выполнении сборки они будут передаваться на вход правила. 

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

 

 

Версионирование правил при помощи Git

 

 

Расскажу про версионирование. Почему у нас вообще возникла необходимость версионирования?

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

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

  • У нас есть некая серверная часть – база, где хранятся общие данные, 

  • И есть клиентская часть – именно клиент является средой разработки правила. 

  • Но само правило в базе вообще не хранится, оно хранится в XML-файле под версионированием Git. Самая ближайшая аналогия – это EDT. 

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

 

 

Проблемы и их решения

 

 

Чтобы начать этим пользоваться, нужно было все это пережить и разрулить проблемы. У нас это получилось не сразу. Расскажу про самые интересные проблемы, с которыми мы столкнулись. 

Первое – это проблема ссылочной целостности. Когда вы забираете в вашу конфигурацию какую-то подсистему из БСП, вы не задумываетесь о том, что ваши объекты метаданных имеют ссылки друг на друга, потому что, когда вы их переносите, конфигуратор эти проблемы автоматически решает – он удаляет ссылки на несуществующие объекты, если такие есть. Но в случае, если мы сами манипулируем XML-файлом, никто за нас этого не сделает. Поэтому, когда мы пытались загрузить нашу конфигурацию из XML-файлов, то получали вот такую простыню ошибок. 

Мы решили эту проблему, просто добавив в программный интерфейс специальные функции для очистки ссылок.

 

 

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

Для этого мы в нашем инструменте реализовали механизм, который сохраняет уже сгенерированные идентификаторы. Это решило проблему.

 

 

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

Для этой проблемы нет общего решения, но можно попробовать разные способы. Например, в случае с нашей MDM-системой у нас возникал вопрос, как кастомизировать формы – мы решили его с помощью программной модификации форм. Кроме этого, можно было воспользоваться расширениями и т.д. То есть, эти вопросы решаемы. 

 

 

Роль кодогенератора в решении архитектурных проблем

 

 

Тут стоит задуматься вот о чем. Если вы решили пойти по пути модульной разработки, когда у вас есть подсистемы, которые собираются и изменяются с помощью какого-то инструмента, вы должны решить ряд архитектурных проблем, ответить себе на вопрос: «Как мне проектировать мои механизмы, чтобы они были отчуждаемые, встраиваемые и т.д.?»

И сейчас на следующем слайде мы увидим ответ, как наш кодогенератор решает эти архитектурные проблемы.

 

 

Никак он их не решает. Что вы хотели? Но это, наверное, и хорошо. Иначе, зачем мы были бы нужны?

Этим я просто хочу подчеркнуть, что необходимость грамотного проектирования никуда не делась. Скорее, наоборот, она встает еще более остро.

 

 

Заключение

 

 

И еще один архитектурный антипаттерн, который тоже нужно учитывать – называется «Золотой молоток». Это – когда разработчик освоил какую-то крутую технологию, и она ему настолько сильно понравилась, что он начинает ее использовать везде. Даже там, где это нецелесообразно. Поэтому помните, что любая технология имеет границы применимости и подходить к её использованию нужно с позиции здравого смысла и экономической целесообразности.

Два слова о том, как мы видим дальнейшее развитие инструмента. Во-первых, это реализовать поддержку формата EDT. Интересно было бы научит Jinnee парсить код на языке 1С и формировать AST-tree (хотя у нас большой потребности в этом не было, но это могло бы открыть новые интересные возможности). Есть много идей по развитию внутреннего инструментария, а также хочется попробовать реализовать конвертацию моделей в jinnee-сценарии, т.е. приблизится к разработке через моделирование.

 

 

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

 

 

 

Данная статья написана по итогам доклада, прочитанного на конференции INFOSTART EVENT 2018 EDUCATION.

См. также

Механизмы платформы 1С Программист Стажер Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Эта небольшая статья - некоторого рода шпаргалка по файловым потокам: как и зачем с ними работать, какие преимущества это дает.

23.06.2024    7498    bayselonarrend    20    

154

Механизмы платформы 1С Программист Стажер Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Пример использования «Сервисов интеграции» без подключения к Шине и без обменов.

13.03.2024    5962    dsdred    16    

80

Механизмы платформы 1С Программист Стажер Платформа 1С v8.3 Бесплатно (free)

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

24.01.2024    17782    YA_418728146    26    

71

Механизмы платформы 1С Программист Бесплатно (free)

Язык программирования 1С содержит много нюансов и особенностей, которые могут приводить к неожиданным для разработчика результатам. Сталкиваясь с ними, программист начинает лучше понимать логику платформы, а значит, быстрее выявлять ошибки и видеть потенциальные узкие места своего кода там, где позже можно было бы ещё долго медитировать с отладчиком в поисках источника проблемы. Мы рассмотрим разные примеры поведения кода 1С. Разберём результаты выполнения и ответим на вопросы «Почему?», «Как же так?» и «Зачем нам это знать?». 

06.10.2023    23782    SeiOkami    48    

135

WEB-интеграция Универсальные функции Механизмы платформы 1С Программист Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

При работе с интеграциями рано или поздно придется столкнуться с получением JSON файлов. И, конечно же, жизнь заставит проверять файлы перед тем, как записывать данные в БД.

28.08.2023    14772    YA_418728146    7    

166
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. user-z99999 71 26.08.19 17:22 Сейчас в теме
Jinnee - в тексте повторяется 6 раз.
Как скачать?
Примеры как использовать?

Платно/бесплатно ?
2. VmvLer 26.08.19 17:57 Сейчас в теме
(1) Львиная доля статей-докладов не более чем маркетинг а ля
"конкретно мы вам ничего не скажем, просто покажем вам картинки чтобы вы поняли какие мы крутые и купили у нас услуги, товары и т.п."
daMaster; wowik; sm.artem; Anchoret; for_sale; +5 Ответить
6. kirovsbis 151 27.08.19 10:05 Сейчас в теме
(2)

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


Хотелось бы больше конкретики в претензиях на отсутствие конкретики :) Статья всё-таки написана по итогам 30-минутного доклада, в котором полноценно раскрыть тему невозможно при всём желании. Целью было донести до слушателей саму идею, плюсы-минусы, реальные кейсы применения технологии, рассказать кратко про сам инструмент и возможные проблемы использования. Если тематика интересна - могу более подробно рассказать в следующих статьях о том как оно работает, как организован сам процесс разработки, каких архитектурных принципов стараемся придерживаться при разработке, как реализуем модульность на практике.
8. VmvLer 27.08.19 10:16 Сейчас в теме
(6) Моя реплика - это общий взгляд на современные презентации.
3. for_sale 976 26.08.19 18:02 Сейчас в теме
(1)
Очередная статья из серии "Смотрите, как мы умеем!"
daMaster; wowik; +2 Ответить
5. kirovsbis 151 27.08.19 09:48 Сейчас в теме
(1)
Jinnee - в тексте повторяется 6 раз.


Не считал, но верю Вам на слово :)


Как скачать?
Примеры как использовать?

Платно/бесплатно ?


Инструмент нигде не публиковали пока. Изначально его разрабатывали для решения вполне конкретных собственных задач (в первоначальной постановке он назывался "механизм клонирования справочников"). Когда получили что-то более-менее работоспособное возникла мысль рассказать об этом сообществу с целью понять будет ли тема интересна коллегам (на мой взгляд технология имеет достаточно широкий потенциал для применения). Я планирую подготовить первый полноценный релиз с документацией и примерами до конца этого года. Буду признателен за конструктивные комментарии, предложения и идеи. Готов ответить на возникающие вопросы.
drmaxart; +1 Ответить
7. user-z99999 71 27.08.19 10:11 Сейчас в теме
(5)
Выложите инструмент, дайте попробовать.
Научите в нём работать. Простые примеры, создание справочников.

Если вы продаете, так и пишите - распространение на платной основе.
4. nytlenc 27.08.19 06:44 Сейчас в теме
Все мне пора в отпуск. Прочитал как "Кодогенерация и матогенерация в 1С"...
9. kirovsbis 151 27.08.19 10:23 Сейчас в теме
Выложите инструмент, дайте попробовать.
Научите в нём работать. Простые примеры, создание справочников.


Выложу, как только подготовлю версию, которую не стыдно выложить :) Про планируемые сроки уже написал.

Если вы продаете, так и пишите - распространение на платной основе.


С моделью распространения ещё не определились, поэтому пока не знаю что ответить на этот вопрос.
10. Darklight 33 27.08.19 11:29 Сейчас в теме
Великолепное решение! Великолепная статья! Браво! За кодогенерацией будущее! Ближайшее будущее! Очень ждём таких решений для платформы 1С Предприятие. "Подписываюсь" практически под каждым тезисом из статьи, ну разве что смутила фраза


Как на практике происходит программная модификация объекта метаданных?

Мы пишем «ИзменитьСвойствоОбъектаМетаданных()»;

В качестве первого параметра передаем полное имя изменяемого объекта – это у нас определяемый тип ВладелецФайлов;

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

Очень принципиально смутило название функции "ИзменитьСвойствоОбъектаМетаданных"
Хотя по тексту и не совсем понятно, как она работает: предположил - что глагол "Изменить" означает что функция заменяет одно значение на другое - это конечно может быть иногда полезно, но чаще - это не лучшее решение (тем более в качестве демонстрационного примера). В кодогенерации нужно стараться чаще оперировать не термином "изменить", а термином "Расширить" (ну и ещё - "Исключить" в широком смысле понимания; а так же - "Сдвинуть"). То есть алгоритм кодогенерации должен стараться оперировать не абсолютными величинами, а относительными - воспринимая исходный объект как "черный ящик" (но ожидаемо удовлетворяющий некоторым правилам - и включать инструменты проверки на соответствие этим правилам - формату/интерфейсу), а команды скрипта должны это "черный ящик" видоизменять на заданную величину требуемого изменения, не меняя ничего, что не входит в это изменение.

Другими словами, для данного примера правильно было бы выполнить команду "ДобавитьВЗначениеСвойстваОбъектаМетаданного" - которая в данном случае в существующие ОписаниеТипов внесла бы новый тип (в случае его отсутствия), не меняя существующие.
Хотя, может так функция "ИзменитьСвойствоОбъектаМетаданных" и работает - но название тогда не очень корректное.


Интересно было бы научит Jinnee парсить код на языке 1С и формировать AST-tree


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

Ну и как развитие данной тем - нужно присваивать исходным метаданным маркеры, на которые можно было бы опереться при кодогенерации - и дальше, например, решать включать или не включать это метаданное (или его часть) в итоговое решение, ну или как-то иначе модифицировать - т.е. оперировать уже не конкретными элементами метаданных - а некими общими признаками. Например, присвоив маркер "ЕстьОрганизация" как справочнику "Организация" (условно), так и измерениям регистров, и реквизитам документов "Организация", а также блокам программного кода, обращающимся к этим метаданным - то в зависимости от настройки - ведения учета по разным организациям - можно выкинуть все эти элементы из итоговой конфигурации, тем самым снизив её сложность - а значит повысив эффективность её исполнения платформой и СУБД.

Так что желаю авторам всяческих успехов в развитии данного проекта. Давно сам грёзю о таком - но мне одному его не поднять проект. А вот данное решение с удовольствием бы начал применять в повседневной практике!

P.S.
Да и ещё - вот тут недавно всплыл новый 1С-подобный язык программирования - Перфолента- построенный на расширенном синтаксисе 1С (для платформы MS .NET Framework).
Отличным решением было бы скооперироваться с автором Перфоленты - и внедрить поддержку данного языка (а парсер и разбор в AST-tree уже есть у автора) в качестве основы как для написания встроенных скриптов, так и для исходного языка источника для кодогенерации (изменения) алгоритмов - это вообще была бы бомба в программировании на 1С. Позволившая как писать очень продвинутые скрипты, так и писать продвинутые алгоритмы для конфигурации 1С (например, с поддержкой ООП), которые после кодогенерации - превращались бы в понятные платформе 1С Предприятие инструкции (не факт что 100% на языке 1С, скорее всего тут некоторые вещи имеет смысл сразу компилировать в байткод (OpCode) стековой машины 1С, ну или сначала на промежуточный язык 1с-ассемблера (тоже недавно тут представленный).
Вот это вообще была бы сногсшибательная революция в программировании для 1С Предприятие 8
Perfolenta; dabu-dabu; kirovsbis; +3 Ответить
12. kirovsbis 151 27.08.19 12:33 Сейчас в теме
(10)

Спасибо за содержательный комментарий!

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

Пример:
Есть объект метаданных, например определяемый тип. Если мы зайдем в конфигуратор и в дереве метаданных выберем наш определяемый тип, а затем откроем окно свойств (Alt + Enter), то увидим существующие свойства объекта. Некоторые будут "простыми" - например "Имя" или "Синоним", другие "сложными" (или составными) - например "Тип", но всё равно это СВОЙСТВА объекта метаданных. Поэтому независимо от того меняем ли мы синоним или добавляем новый тип в состав определяемого типа (или наоборот удаляем тип из состава) мы фактически изменяем одно из свойств объекта метаданных.
Полное описание функции "ИзменитьСвойствоОбъектаМетаданных" выглядит так:
ИзменитьСвойствоОбъектаМетаданных(<Полное имя объекта метаданных>, <Имя свойства>, <Добавляемое значение>, <Удаляемое значение>)
Разработчик подает на вход функции имя объекта метаданных и имя свойства, а инструмент соответственно на основе этих данных понимает как правильно интерпретировать третий или четвертый параметр.

Примеры использования функции

Сделаем справочник иерархическим:

ИзменитьСвойствоОбъектаМетаданных("Catalog.ПримерСправочника","Hierarchical","true");


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

ИзменитьСвойствоОбъектаМетаданных("DefinedType.ВладелецФайлов", "Type", "CatalogRef.ПримерСправочника");


А можем и сразу несколько типов добавить:

МассивТипов = Новый Массив;
МассивТипов.Добавить("CatalogRef.ПримерСправочника1");
МассивТипов.Добавить("CatalogRef.ПримерСправочника2");
ИзменитьСвойствоОбъектаМетаданных("DefinedType.ВладелецФайлов", "Type", МассивТипов);
14. Darklight 33 27.08.19 13:30 Сейчас в теме
(12)Спасибо за пояснения. Теперь механика стала понятнее . Хотя, если ориентироваться на новичков - мне кажется несколько отдельных функций с именами "ДобавитьВЗначениеСвойстваОбъектаМетаданного", "ИсключитьИзЗначенияСвойстваОбъектаМетаданного" было бы более понятным, но это не принципиально Хотя я бы вообще делал API боле универсально - как-то так:

МетаСвойство = НекаяКонфигурация.ПолучитьМетаданное(Путь, Свойство, ШаблонОписанияВСлучаеОтсутвия); //Сразу получили описание доступа к свойству конкретного метаданного
МетаСвойство.Обновить(КомандыИзмененияМетаданных.Расширить, Значение); //Изменяем свойство по заданному правилу команды и значению ("Обнвоить" - это "Update" -  но по-русски, конечно, привычнее звучит "Изменить")
МетаСвойство.Применить();  //Применяем изменение


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

Фильтр = СоздатьФильтрМетаданых();
Условие1 = Фильтр.Условия.Добавить(СоздатьГруппуУсловий("Поля")); //Группы условий ниже обрабатываются в цикле
Условие1.Отбор.Добавить(ПолучитьТип("УниверсальныйМаркер"), ИмяОтбора1, КомандыОтбора.Равно, СоздатьМаркерМетаданных("ЕстьОрганизация")); 
Условие1.Отбор.Добавить(ПолучитьТип("ВидМетаданных"), ИмяОтбора2, КомандыОтбора.Наследование, НекаяКонфигурация.ПолучитьВидМетаданных("ПолеДанных")); //ПолеДанных - это универсальный вид метаданных - от него наследованы, например: "Измерение, Ресурс", "Реквизит")

Условие2 = Фильтр.Условия.Добавить(СоздатьГруппуУсловий("ТипыЗначений");
Условие2.Отбор.Добавить(ПолучитьТип("ВидМетаданных"), ИмяОтбора3, КомандыОтбора.Наследование, НекаяКонфигурация.ПолучитьВидМетаданных("ПолеДанных"));
МетаданоеСпрОрганизация = НекаяКонфигурация.ПолучитьМетаданное("Справочник.Организация"); 
Условие2.Отбор.Добавить(ПолучитьТип("ТипЗначения"), КомандыОтбора.Содержит, МетаданоеСпрОрганизация); //Проверка, что тип значения поля содержит заданный тип(в любом виде: Ссылка, Объект)

Выборка = НекаяКонфигурация.ВыбратьМетаданные(Фильтр);
Пока Выборка.Следующий() Цикл
    МетаСвойство = Выборка.ЗначениеКак("Метасвойство");
    Если МетаСвойство = неопределено Тогда продолжить; КонецЕсли;
    Если Выборка.ЭтоГруппаУсоловий("Поля") Тогда
         МетаСвойство.Обновить(?(НекаяКонфигурация.ПолучитьФунициональнуюОпцию("ВедениеПоНесколькимОрганизациям"),КомандыИзмененияМетаданных.Требуется, КомандыИзмененияМетаданных.Необязательное)); //Данные команды, в зависимости от значения опции проекта, помечают свойство либо как требуемое - либо как необязательное  - обслуживается отдельно ниже (может быть в любом другом месте скрипта)
    ИначеЕсли Выборка.ЭтоГруппаУсоловий("ТипыЗначения") Тогда
         МетаСвойство = МетаСвойство.ПолучитьСвойство("ТипЗначения"); //Мы выбирали поля - нужно получить вложенное свойство
         МетаСвойство.Обновить(?(НекаяКонфигурация.ПолучитьФунициональнуюОпцию("ВедениеПоНесколькимОрганизациям"),КомандыИзмененияМетаданных.Требуется, КомандыИзмененияМетаданных.Необязательное), МетаданоеСпрОрганизация);  //У типа значения поля тип (Ссылка и Объект Справочник.Организация) будет помечен соотвесвтующим маркером и будет добавлен или исключен из типа при финализации
    КонецЕсли;
    МетаСвойство.Применить();
КонецЦикла;

//Где-то в другом месте (возможно после выполнения целого вороха других скриптов)
НекаяКонфигурация.Выполнить(ГлобальныеКомманды.ОтложенныеИзменения); //Финализация: все свойства, имеющие только пометку "Необязательное" будут удалены из конфигурации
Показать


Где "НекаяКонфигурация" - инициализированный объект проекта изменения метаданных конфигурации - где загружены все настройки!

Сложно? Да ненамного - зато универсальность и уровень абстракции - на голову выше простых функций, предложенных Вами.

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


P.S.
К своему предыдущему комментарию ещё хотел бы добавить пожелание - хорошо бы в движке кодогенерации метаданных реализовать поддержку:

1. Что-то типа наследования метаданных - когда, скажем, один справочник, в описании правил наследуется от другого - и в результате получается просто объединённый справочник. Это, конечно не наследование, а скорее - совмещение (объединение) - особенно когда есть несколько исходных справочников и новый создаётся путём как-бы множественного наследования/объединения - а все пересечения решаются за счёт заданных правил проведения такой операции (ну например, пересекающиеся реквизиты объединяются по типам, а пересекающиеся ТЧ объединяются по реквизитам - но это поведение должно быть настраиваемым)

2. Аналогично - но для управляемых форм - когда результирующая форма создаётся из отдельных фреймов (тоже форм) путём совмещения интерфейсных элементов (по заданным правилам) и переноса кода в модуль формы. Естественно сами формы тоже должны быть спроектированы по некоторым правилам, допускающим такое совмещение

3. Кроме форм неплохо бы совмещать макеты - но это уже высший пилотаж!
dabu-dabu; +1 Ответить
16. Darklight 33 28.08.19 00:41 Сейчас в теме
(14)А вот так я бы организовал обработку текстов алгоритмов (есть небольшие отличия от предыдущего примера т.к. я ещё немного подумал над архитектурой)
Фильтр1 = СоздатьФильтрМетаданых(); //Для фильрации текстов  подойдёт тот же объет что и длля фильтрации метаданных – я так считаю
Условие1 = Фильтр1.Условия.Добавить(); //Тут по группам не делим
Условие1.Отбор.Добавить(ПолучитьТип("УниверсальныйМаркер"), ИмяОтбора1, КомандыОтбора.СодержитВнутри, СоздатьМаркерМетаданных("ПрименениеОрганизации")); //Вид отбора – «СодержитВнутри» просит заглянуть внутрь анализируемых элементов (что типа «В ИЕРАРХИИ»)
Условие1.Отбор.Добавить(ПолучитьТип("ВидМетаданных"), ИмяОтбора2, КомандыОтбора.Наследование, НекаяКонфигурация.ПолучитьВидМетаданных("МодульМетод")); //По данному виду  «МодульМетод» отбираем все производные виды – Процедуры и Функции всех элементов, которые могут содержаться в любьых программных модулях (считаю что модуль не должен быть конечным объектом металднных модели, несморря на то, что в конфигурации он конечный и не имеет вложенных элементов метаданных) – это даёт бОльшую гибкость при анализе кода

//Будет использован ниже – хотя, в принципе, думаю, эти условия можно было вставить вместо Условия1 – и всё тоже работало (
Фильтр2 = СоздатьФильтрМетаданых(); 
Условие2 = Фильтр2.Условия.Добавить();
Условие2.Отбор.Добавить(ПолучитьТип("УниверсальныйМаркер"), ИмяОтбора1, КомандыОтбора.Содержит, СоздатьМаркерМетаданных("ПрименениеОрганизации")); //Здесь уже отбор «Содержит», т.к. маркеры, согласно соглашения и кодировании, должны быть заданны на блоки кода (о них ниже)
//Условие2.Отбор.Добавить(ПолучитьТип("ВидМетаданных"), ИмяОтбора2, КомандыОтбора.Равно, НекаяКонфигурация.ПолучитьВидМетаданных("БлокАлгоритма")); //Эта строка закоментирована – она будет нужна если ей заменить второй отбор сверху

Фильтр3 = СоздатьФильтрМетаданых(); 
Условие3 = Фильтр2.Условия.Добавить(СоздатьГруппуУсловий("ПрименениеОрганизации"));
Условие3.Отбор.Добавить(ПолучитьТип("УниверсальныйМаркер"), ИмяОтбора1, КомандыОтбора.Содержит, СоздатьМаркерМетаданных("ПрименениеОрганизации")); 
Условие4 = Фильтр2.Условия.Добавить(СоздатьГруппуУсловий("Иначе"));
Условие4.ИспользоватьИначе = Истина; //Эта группа условий будет применена как выборка «Иначе» для элементов, не удовлетворяющих другим условиям

ПараметрыВыборкиБлоковКода = СоздатьПараметрыВыборки();
ПараметрыВыборкиБлоковКода.Иерархия = ВидИерахии.Линейная; //Несмотря на то, что блоки могут быть вложенными, они выбираются линейным списком – т.к. в данном скрипте их вложенность никак не обрабатывается – и проще из перебирать линейным перебором
ПараметрыВыборкиБлоковКода.ОбработкаВложений = МетодОбработкиВложений.Исключить; //Если один блок вложен в другой и оба подходят по условию выборки – то риз родительского блока будет исключено содержимое вложенного блока – чтобы не обрабатывать их повторно (сначала в родителе, затем во вложении)

ВыборкаФункции = НекаяКонфигурация.Алгоритмы.ВыбратьМетаданные(Фильтр); //отделил выборку метаданных от выборки текстов – но это не обязательно, хотя физически в xml файлах они разнесены в разные структуры и процессоры их обработки тоже будут различны
Пока ВыборкаФункции.Следующий() Цикл
     МетаОписаниеПроцедуры = ВыборкаФункции.ЗначениеКак("МодульМетод");
     Если МетаОписаниеПроцедуры = неопределено Тогда продолжить; КонецЕсли;
     ВыборкаБлокиКода = МетаОписаниеПроцедуры.Тело.БолокиАлгоритмов.Выбрать(Фильтр2, ПараметрыВыборкиБлоковКода); //Как написал выше – можно было бы сразу отбирать блоки кода, а не процедуры/функции  тогда эта выборка тут была бы не нужна – привёл её просто, чтобы показать что та тоже было бы можно; БолокиАлгоритмов – это встроенное представление (зарегистрированное для объектов – «ТекстАлгоритма»), которое разбивает исходное содержимое (тело алгоритма функции) на составные блоки кода по заданным в него семан6тическим правилам (ну например – отделяя  регионы и тексты запросов, хотя можно выделять и циклы и условия ветвления как отдельные блоки, или ещё как-то отельно их обозночать – для метаанализа алгоритмов наличие правильно выделенных блоков очень важно – к блокам могут быть привязаны параметры и маркеры, используемые для метаобработки – как в данном случае – маркер «ПрименениеОрганизации» - тут отбираются только те блоки кода, где к ним такой маркер привязан в качестве аннотации)

     Пока ВыборкаФункции.Следующий() Цикл //Лийнейный перебор найденных блоков, согласно параметрам выборки, без повторов вложений
         БлокКода = ВыборкаФункции.Значение; //Тут всегда блоки алгоритмов – согласно выборки из предствления «БолокиАлгоритмов»
         //Дальше возможно несколько разных путей обработки блоков программного кода
         //1. Применить подстановку значений в макрошаблон 
         Шаблон = БлокКода.ПолучитьШаблон(); //Применяется шаблонизатор по-умолчанию, номожно было бы использовать внешний объект-шаблонизатор, связав его с данным блоком кода
         Шаблон.Параметры.Вставить(“НекийПараметрШаблона”, НекоеЗначение);
         Шаблон.Параметры.Вставить(“УчитыватьОрганизацию”, НекаяКонфигурация.ФункциональныеОпции.ВедениеПоНесколькимОрганизациям.ПолучитьЗначение());
         Шаблон.Применить(); //Шаблон будет готов использованию в блоке алгоритма (все параметры будут проинициализированы), но ещё но текст алгоритма ещё не будет заменён
         БлокКода.Обновить(КомандыИзмененияМетаданных.ПрименитьШаблон, Шаблон); //Шаблонные параметры будут проинцииализированы заданными значениями в тексте алгоритма блока кода

         //2. Применить обработку текста каким-либо Препроцессором (по заданным в тексте препроцессорным инструкциям)
         Препроцессор = НекаяКонфигурация.Препроцесоры.Найти(“ОсновнойПрепроцессор”); //Поиск нужного препроцессора в проекте, по имени – можно и так: НекаяКонфигурация.Препроцесоры.ОсновнойПрепроцессор; 
         НовыйБлокКода = Препроцессор.Выполнить(БлокКода); //Полученный из проекта «НекаяКонфигурация» препроцессор будет уже инициализирован необходимыми настройками и будет иметь доступ к функциональным опциям – поэтому его достаточно просто применить к блоку адгоритмов – он проверит свои инструкции препроцессора и вычистить тест от лишнего кода (и возможно сгенерирует новый)
         БлокКода.Обновить(КомандыИзмененияМетаданных.Заменить, НовыйБлокКода); //Замена содержимого блока кода на новое значение

         //3. Выполнить детальную обработку разобранного синтаксического дерева алгоритма

         //Тут есть две отдельные схемы (и блоки кода желательно сразу отфильтровывать отдельно для каждой схемы – конечно выбрать нужные части блока кода можно и после открытия выборки – тем более что до этого этапа не производился синтаксический разбор – а тут уже потребуется лексер)
         Лексер = НекаяКонфигурация.Лексеры.ПолучитьОсновной(); //Получаем лексер проекта, назначенный в нём основным – он уже будет инициализирован параметрами проекта
         ASTДерево = Лексер.Выполнить(БлокКода); //Синтаксический разбор, будет исполиьзован синтаксический контекст, связанный с БлокКода (так что могут быть задействованы другие алгоритмы, вне данного блока – конечно такой разбор лучше делать для всех ммодулей алгоритма сразу – а уже потом их анализировать)
         ПотокИнструкций = ASTДерево.АлгоритмИнструкции.Выбрать(Фильтр3); //Выбираем только ветви ASTДерева вида «АлгоритмИнструкции», в которых есть нужный маркер
         НовыйПотокИнструкций = ASTДерево.АлгоритмИнструкции.СоздатьПоток(); //Новый пустой поток для преобразованных инструкций (в рамках текущего контекста лексического разбора)
         Пока ПотокИнструкций.Следующий() Цикл
             //самостоятельно перебираем и обрабатываем иерархию ASTДерева
             Если ПотокИнструкций.ЭтоГруппаУсоловий("ПрименениеОрганизации") Тогда
                  //Что делаем с данной инструкцией
                  //надо только учесть что внутри инструкций могут быть выражения – их можно пропускать – если они обрабатываются отдельно

             Иначе // ЭтоГруппаУсоловий("Иначе")
                  //Эта инструкция не содержит искомого маркера
             КонецЕсли;
             НовыйПотокИнструкций.Добавить(вносим обработанную или пропущенную инструкцию в новый поток)
         КонецЦикла;
         Генератор = СоздатьГенераторКода(“ГенераторИнструкций1С”); //Создаём базовый генератор кода языка 1С (можно было бы выбрать и из проекта: НекаяКонфигурация.ГенераторыКода.ПолучитьОсновной(“ГенераторИнструкций”) – тогда не пришлось бы его инициализировать
         Генератор.УстановитьКонтекстНастроек(НекаяКонфигурация);
         Генератор.Инициализировать();
         Генератор.УстановитьКонтекстВыполнения(БлокКода); //Контекст сразу инициализируется (прекомпилируется)
         Генератор.УстановитьИсходныйПоток(НовыйПотокИнструкций);
         НовыйБлокКода = Генератор.Выполнить(ПолучитьТип(“БлокКода”));
         БлокКода.Обновить(КомандыИзмененияМетаданных.Заменить, НовыйБлокКода);

         //Для выражений всё аналогично, но можно не не генерировать весь поток инструкций заново а заменять выражения на обработанные 

ПотокВыражений = ASTДерево.АлгоритмВыражения.Выбрать(Фильтр3); //Выбираем только ветви ASTДерева вида «АлгоритмВыражения», в которых есть нужный маркер
         НовыйПотокИнструкций = ASTДерево.АлгоритмВыражения.СоздатьПоток(); //Новый пустой поток для преобразованных выражений (в рамках текущего контекста лексического разбора)
         Пока ПотокВыражений.Следующий() Цикл //Поток выражений по умолчанию линеен
             Если ПотокВыражений.ЭтоГруппаУсоловий("ПрименениеОрганизации") Тогда
                  ТекущееВыражение = ПотокВыражений.Значение.ДляИзменения(); //Создаём копию выражения для изменения
                  //Что делаем с данным выражением 
                  ASTДерево.АлгоритмВыражения.Заменить(ПотокВыражений.Значение, ТекущееВыражение); //Все выражения в ASTДереве уникальны (имеют внутренний идентификатор) и лего совпоствляются и заменяются
             Иначе // ЭтоГруппаУсоловий("Иначе")
                   //Пропускаем это выражение без изменений
             КонецЕсли;
         КонецЦикла;
         Генератор = НекаяКонфигурация.ГенераторыКода.ПолучитьОсновной(“ГенераторИнструкций”); //Для выражений всё равно нужно будет генерировать весь блок инструкций их, использующий
         Генератор.УстановитьКонтекстВыполнения(БлокКода); //Контекст сразу инициализируется (прекомпилируется)
         Генератор.УстановитьASTДерево(ASTДерево); 
         НовыйБлокКода = Генератор.Выполнить(ПолучитьТип(“БлокКода”));
         БлокКода.Обновить(КомандыИзмененияМетаданных.Заменить, НовыйБлокКода);

     КонецЦикла;

     //На самом деле тут выше допущен ряд логических ошибок – так отдельно генерировать инструкции и выражения нельзя – они пере затрут друг друга – но оставил пример так для наглядности

         //4. Можно выполнить просто текстовый перебор по ключевым словам
     Отбор = СоздатьТекстовыйФильтр();
     Отбор.Слово.Установить(“ПрименениеОрганизации”);
     ПозицияСлова = БлокКода.ТекстовыйПроцессор.НачатьПоиск(Отбор);
     НовыйПотокСлов = СоздатьПотокСлов();
     Пока ПозицияСлова.Следующий() Цикл //Нашли вхождение в текст «ПрименениеОрганизации»
         Предыдиущие5Слов = ПозицияСлова.ВзятьСлова(НаправлениеПоиска.Назад, 5);
         СледующиеСловаДоКонцаИнструкции = ПозицияСлова.ВзятьСлова(НаправлениеПоиска.Вперед,, Истина);

         //Ну и как-то их обрабатывать

         НовыйПотокСлов.Вставить(…);
     КонецЦикла;

     НовыйБлокКода = СоздатьБлокКода(НовыйПотокСлов);
     БлокКода.Обновить(КомандыИзмененияМетаданных.Заменить, НовыйБлокКода);

         //5. Ну или просто получить блок кода как текст и обрабатывать его как обычную строку - по старинке
     СтрокаАлгоритма = БлокКода.ПолучитьТекст();
     НоваяСтрокаАлгоритма = СтрЗаменить(СтрокаАлгоритма,…,…);
     НовыйБлокКода = СоздатьБлокКода(НоваяСтрокаАлгоритма);
     БлокКода.Обновить(КомандыИзмененияМетаданных.Заменить, НовыйБлокКода);

     БлокКода.Применить(); //Фиксируем изменения
     ///БлокКода.Применить(ВидФиксации.Отложенно); //Или откладываем на потом
КонецЦикла;

НекаяКонфигурация.Алгоритмы.Изменения.Применить(ГлобальныеКоманда.ВыполнитьОтложенныеИзменения); //Применяем все актуальные отложенные изменения алгоритмов

//Отложенные изменения физически не применяются к метаданным до их финального выполнения. Это позволяет другим алгоритмам-скриптам видеть, как исходные метаданные, так и с потенциальными изменениями – и в случае необходимости корректировать эти потенциальные изменения или отменять их
Показать

Может получилось несколько сложновато, зато какой глубокий потенциал заложен в таком подходе. Тут главное – это правильная декомпозиция, правильные фильтры и универсальный алгоритм обработки либо текстов либо инструкций/выражений
21. kirovsbis 151 28.08.19 10:31 Сейчас в теме
(16)

Спасибо как минимум за то что не поленились продумать и написать такой кусок кода :) Вечером я помедитирую над ним )
25. Darklight 33 28.08.19 12:18 Сейчас в теме
(21)Просто я тоже давно грёзю кодогенерацией в 1С и программными скриптами изменения метаданных - и много думал на эту тему. Так что подобные выкладки кода - это просто продолжение моих размышлений - и как стать, может когда-нибудь это всё станет не только моей фантазией....
27. Darklight 33 28.08.19 13:51 Сейчас в теме
(16)В листинге у меня небольшая опечатка: "КонецЦикла" перед. п.4. рано закрыл :-( - это же был цикл перебора блоков кода - его нужно закрыть в самом конце - перед закрытием последнего цикла перебора процедур/функций, чтобы контексты п.4 и п.5. были внутри этого цикла.

Ещё добавка - алгоритмы п.3 (анализирующие инструкции и выражения; да, впрочем, это важно для всех вариантов пунктов) в приведённом листинге не имеют проверки на вид блока кода - а это может быть очень важно для его разбора - например блок кода с текстом запроса скорее всего нужно обрабатывать иначе, чем просто инструкции языка 1С, уж как минимум Лексер в ASTДерево и Генератор кода для него должны быть свои (поэтому для п.3. это очень важно, для других случаев - это не обязательно, но всё-равно лучше всегда знать с каким видом блока кода имеешь дело).
18. kirovsbis 151 28.08.19 09:29 Сейчас в теме
(10)
Ну и как развитие данной тем - нужно присваивать исходным метаданным маркеры, на которые можно было бы опереться при кодогенерации - и дальше, например, решать включать или не включать это метаданное (или его часть) в итоговое решение, ну или как-то иначе модифицировать - т.е. оперировать уже не конкретными элементами метаданных - а некими общими признаками. Например, присвоив маркер "ЕстьОрганизация" как справочнику "Организация" (условно), так и измерениям регистров, и реквизитам документов "Организация", а также блокам программного кода, обращающимся к этим метаданным - то в зависимости от настройки - ведения учета по разным организациям - можно выкинуть все эти элементы из итоговой конфигурации, тем самым снизив её сложность - а значит повысив эффективность её исполнения платформой и СУБД.


Покажу на примере как мы решаем этот вопрос.
Допустим у нас есть некая "Подсистема1", которая содержит "Документ1" (причем реквизита "Организация" у этого документа изначально нет).
Мы написали "Правило1", которое переносит "Подсистему1" из исходной конфигурации в результирующую.

А ещё есть "Подсистема2", которая содержит справочник "Организации".
Мы написали "Правило2", которое переносит "Подсистему2" и добавляет в "Документ1" реквизит "Организация".

Если мы создадим сценарий генерации состоящий из двух шагов, например:

Шаг 1 по "Правилу1"
Шаг 2 по "Правилу2"

то в результате получим конфигурацию содержащую "Документ1" с реквизитом "Организация".
Но если "Шаг 2" отключить то в результате получим "Документ1" без реквизита "Организация" т.е. этот реквизит как бы "Удалится" (но правильнее сказать "не добавится"). Поэтому проблема быстрого исключения из конфигурации ненужной функциональности легко решается если изначально нужным образом спроектировать сценарий генерации.
24. Darklight 33 28.08.19 12:17 Сейчас в теме
(18)Немного разные сценарии - которые оба имеют веское право на применение в реальных проектах.
В моём случае, как я считаю, что это эффективнее для большинства случаев, но не для абсолютного большинства - проще создавать одну общую конфигурацию, в которой присутствует весь необходимый функционал (я тут лукавлю - ниже поясню почему). И результирующую конфигурацию получать путём отключения и исключения лишнего функционала. Почему именно так? Да потому что обычно (но далеко не всегда) проще разрабатывать и отлаживать одну единую инфраструктуру, где все её элементы размещены на своих местах как их задумал архитектор. А когда что-то не нужно - то удалять всегда проще, чем добавлять (при условии, что архитектура изначально спроектирована в расчёте на возможность исключения из неё некоторых составных частей).
Этот подход хорош и для случая - когда решение поставляется комплексно (как сейчас в типовых конфигурациюях 1С, например), а ненужный функционал уже отключается на месте в процессе первоначального развёртывания (или позже).

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

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

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

Я не зря привёл пример с организацией - это достаточно общий реквизит, который присущ большому числу метаданных. Аналогично я бы мог привести пример, например со складами. И эксплуатации ИБ не всегда есть потребность в ведение учета по разным организациям или складам. Нет смысла выносить эти части в отдельный модуль, и совмещать его резултьтирующей конфигурацией - проще сначала собрать её из более общих модулей (которые были оплачены) - и в таком виде её поставить заказчику, а уже при развёртывании у заказчика отключить ненужный функционал.

Я не зря в алгоритме сценария сослался на понятие "функциональная опция" - т.к. на мой взгляд в 1С её наличие - архиважное дело - но вот реализация - архи-неправильная и архи-ограниченная! Я же получаю значение функциональной опции не для ветвления алгоритмов в runtime, а ещё на стадии сборки конфигурации - по сути - это этап препроцессинга продуктивной конфигурации из подгтовленного релиза в репозитории ((собранного из девелоперских частей - отдельных бранчей).

Так что оба подхода важны и имеет право на практическое применение! Простоя привёл пример с другой стороны - для расширения кругозора так сказать ;-)

А скрипт, проводящий совмещение нескольких конфигураций в одну по сути может выглядеть схожим образом
Формально это даже может быть что-то уж совсем простое

ПараметрыВыбораМетаданных = СоздатьПараметрыВыборки();
ПараметрыВыбораМетаданных.Иерархия = ВидИерахии.ВерхийУровеньЭлементов; //Метаданные выбираются только линейно и только верхний уровень элементов, без захода во вложения;

Фильтр = ПолчитьФильтр(); //Здесь можно получить фильтр для фильтрации подметаданных при совмещении метаданных верхнего уровня - если нужно

Модули = НекаяКонфигурация.Модули.Выбрать(); //Выбрали все импортированные в проект модули конфигураций (ну если надо - наложилb какие-то фильтры; если надо - произвели надл этими модулями какие-то предварительные изменени, причём даже без необходимости сохранять их - оставив только изменённую модель в памяти - ну или выгрузив её в temp-хранилище)
Пока Модули.Следующий() Цикл
      Модуль  = Модули.Значение;
      Выборка = Модуль.Метаданные.Выбрать(,ПараметрыВыбораМетаданных); //Выбрали все метаданные модуля (ну или наложли какие-то фильтры)
      Пока Выборка.Следующий() Цикл
           Метаданное = Выборка.Значение;
           РезМетаданное = НекаяКонфигурация.Метаданные.Найти(Метаданное); //Ищем такое же наметанное в результирующей конфигурации
           Если РезМетаданне = неопределено Тогда
                  Результат = НекаяКонфигурация.Метаданные.Обновить(КомандыИзмененияМетаданных.Добавить, Метаданное, Фильтр, Модуль.ПравилаИнтеграции); //Это в простейшем виде, тут ещё могут быть другие параметры
                  Результат.Применить(); //Сразу применяем
           Иначе
                  РезМетаданное.Обновить(КомандыИзмененияМетаданных.Расширить, Метаданное, Фильтр, Модуль.ПравилаИнтеграции); //Вся логика объединения скрыта внутри движка и правилах интеграции: внутри которых будут и режимы объединения, и способы обработки коллизий, и вызовы обработчиков "для ручного управления процессом" (куда будет переда обрабатывамый контекст проекта и метаданных); причём правила интеграции тоже будут использованы совмещённые - переданные из модуля и общие настроенные внутри проекта "НекаяКонфигурация.ПравилаИнтеграцации.Основное()"; 
                   //При желании - можно даже менять внутренний процессор обработки команды "КомандыИзмененияМетаданных.Расширить" на свой - где описывать произвольную логику совмещения метаданных, если встроенный процессор не годится
                  //НекаяКонфигурация.ПроцессорыКоманд.Добавить(КомандыИзмененияМетаданных.Расширить, ИмяПроцессора, ПолучитьНекийСвойПроцессор(), УстановитьОсновным); 
                  РезМетаданное.Применить();
           КонецЕсли;
      КонецЦикла;
КонецЦикла;
Показать


Всё очень компактно - т.к. основная логика скрыта в процессоре обработки команды "КомандыИзмененияМетаданных.Расширить" и настроенных правилах интеграции. Ну и модули уже импортированы в проект и к ним привязаны нужные настройки.
Здесь, в большинстве случаев, всё можно выполнять достаточно универсально - без необходимости глубоко вмешиваться процесс интеграции - декларативных правил и, на худой конец, обработчиков вполне хватит.

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

При желании обрабатывать ещё и раздельно по подсистемам - да пожалуйста - добавим в выборки выборку метаданных с группировкой по подсистемам - и вуаля - будут отдельно выделены подсистемы (тут, правда, надо чтобы метаданные, относящиеся к сразу к нескольким подсистемам, не пересекались, ну подсистемы нужно привязывать не только к метаданным верхнего уровня, но и к нижестоящим - хоть к тем же реквизитам, а платформа 1С этого не умеет).


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

Но нужно будет ещё и встроить Организацию во многие уже присутствующие, в исходной конфигурации, метаданные: и в справочники, и в документы и в регистры. Задача очень не тривиальная (поэтому этот сценарий не лучший выбор - но порой иначе никак). Тогда нужно делать общий обход метаданных и решать - куда нужно встроить Реквизит/Измерение/Ресурс/Тип данных/Код обработки/Элемент на форму - Организация, а куда нет.
Решения тут могут быть разные - я не буду сейчас на этом останавливаться.
Я просто указал на наличие и такого сценария - когда при интеграции объединением обрабатываются метаданные исходной конфигурации и расширяются только программно и динамически, без присутствия в объединяемом модуле.
28. Darklight 33 28.08.19 13:57 Сейчас в теме
Аналогично объединению метаданных можно реализовать и алгоритм ввода нового метаданного на основании другого метаданного

Фильтр = ПолучитьФильтр(); //Какой-то фильтр, определяющий что нужно переносить из Родителя в Потомок (можно без него - команда сама знает как провести наследование)
ПравилаНаследование = ПолучитьПравилаНаследования(); //Если наследование должно идти по не стандартным правилам их можно тут задать - особенно будет полезно для множественного наследования
Родитель1 = НекаяКонфигурация.Метаданные.Найти(ПолныйПутьРодителя); //Ищем метаданное источник
Родитель2 = НекаяКонфигурация.Метаданные.Найти(ПолныйПутьРодителя); //Ищем метаданное источник
Потомок = НекаяКонфигурация.Метаданные.Найти(ПолныйПутьПотомк); //Ищем метаданное потомок, но без части, наследуемой у родителя

//Обновляем потомка - хотя можем сделать и совсем новое метаданное - особенно когда Потомок просто импортируется из какого-то вспомогательного модуля
Потомок.Обновить(КомандыИзмененияМетаданных.Наследовать, Родитель1, Фильтр, ПравилаНаследования);
Потомок.Обновить(КомандыИзмененияМетаданных.Наследовать, Родитель2, Фильтр, ПравилаНаследования); //Тут вполне допустимо множественное наследование - коллизии могут быть разрулены в ПравилаНаследования
Потомок.Применить();
Показать



Тут вся фишка во встроенной Команде "КомандыИзмененияМетаданных.Наследовать" в которой (в Процессоре обработки команды, который тоже можно задать свой) прописан универсальный алгоритм того, как совмещаются два объекта метаданных при операции наследования (ну и, он использует переданные "ПравилаНаследования" - если они не стандартные, которые тоже могут быть изменены, и берутся отсюда: НекаяКонфигурация.ПравилаНаследования.Основное()).
По умолчанию - она (команда) переносит из Родителя в Потомок всё то, чего там нет (согласно фильтру - если будет задан) - а где есть пересечения - объединяет их свойства с приоритетом потомка. Ну перенос текстовых алгоритмов тут более сложный - но в целом он должен работать как классическом ООП (специальные аннотации в методах модулей будут в помощь). Ну и совмещение форм и макетов - это тоже не простые задачи - но это всё внутренние универсальные задачи движка
11. sikuda 677 27.08.19 12:31 Сейчас в теме
А я то и не знал что пишу кодогенератор php1c.ru ;)
13. kirovsbis 151 27.08.19 12:34 Сейчас в теме
(11)

Наверное правильнее это транслятором назвать
15. PerlAmutor 155 27.08.19 19:09 Сейчас в теме
Вероятно в будущем конфигурации 1С начнут продавать исходя из пожеланий заказчика, который "накидал в корзину" прямо с сайта магазина перечень необходимых блоков и получил в итоге не ERP, а лишь то, что ему из неё реально необходимо: "бюджетирование, производство, кадры, складской учет, регламентированный учет"... "документооборот"...
17. Darklight 33 28.08.19 00:47 Сейчас в теме
(15)Абсолютно верно - я уже тоже давно пришёл к тому, что в будущем такой подход продажи будет очень оправдан - платишь ровно за то, что тебе нужно - и не получаешь ничего лишнего в нагрузку - соответственно конфигурация легче - понятнее и быстрее!
Ну а нужно потом что-то ещё - так докупаешь и быстренько интегрируешь в свою систему!
Но это всё-таки более далёкая перспектива, нежели покупка комплексных многофункциональных решений (как сейчас), и их самостоятельная настройка уже "на месте" с генерированием итоговых метаданных, в которых содержится скомпонованный только нужный пакет метаданных. Тут и в расширениях в итоге потребность должна отпасть - обновляться будут исходные конфигурации - а потом результирующая просто будет пересобираться из исходных (включая части с доработками).
19. muskul 28.08.19 09:33 Сейчас в теме
Я считаю что будущее это когда в конфигураторе можно будет звездочками отметить избранные (те объекты которые тебе нужны в данный момент) а не вот это вся матогенерация. Клонирование справочников(чем копировать вставить не то)? С данынми? Можно более конкретно в каким местах это может помочь на каких реальных задачах.
20. kirovsbis 151 28.08.19 10:20 Сейчас в теме
(19)
Клонирование справочников(чем копировать вставить не то)? С данынми? Можно более конкретно в каким местах это может помочь на каких реальных задачах.


Попробую более детально объяснить.
В нашей конфигурации MDM есть некий эталонный справочник, он уже содержит некую общую для всех справочников такого назначения функциональность. У него содержится код в модуле объекта, модуле менеджера, есть форма элемента и форма списка. Уже неплохо :)
У нас возникает потребность в создании вполне конкретного справочника НСИ, например справочника "Активы". Мы заходим в конфигуратор, копируем эталонный справочник. Соответственно в полученной копии будет такой же модуль объекта, модуль менеджера, формы и т.д. Ура!
Решён ли вопрос создания справочника "Активы"? Неа.
Нам как минимум ещё нужно:

1. Изменить значения некоторых свойств этого справочника (Например Имя, Синоним)
2. Включить созданный справочник в состав нужным нам определяемых типов, планов обмена и т.д.
3. Назначить права на этот справочник для одной или нескольких существующих ролей.
4. Добавить для этого справочника нужные реквизиты.
5. Вывести эти добавленные реквизиты на уже существующую форму элемента.
6. Модифицировать запрос динамического списка в форме списка справочника.
7. Возможно ещё ряд некоторых действий.

В статье об этом написано в целом, для создания справочника "руками" была даже написана специальная инструкция, просто скопировать объект метаданных недостаточно.
Но автоматизация создания справочников это в общем то не самое главное преимущество технологии. Как ни крути, справочник то мы создаем 1 раз, и в принципе можно и потерпеть и таки произвести все манипуляции "вручную" без "всей этой матогенерации".
На практике гораздо важнее стоит вопрос сопровождения этого кода. Допустим мы нашли ошибку в одном из модулей нашего справочника НСИ. Поскольку у нас все справочники НСИ создаются копированием эталонного, то значит ошибку надо исправлять в модуле КАЖДОГО справочника НСИ, а у нас их например штук 40. Заходить в каждый и вносить изменения удовольствие ниже среднего. В нашем случае ошибку достаточно исправить в модуле эталонного справочника, выполнить пересборку и нужные изменения будут внесены ВЕЗДЕ. Т.е. кодогенерация это по-сути способ масштабировать собственную работу.
22. ambrozii 28.08.19 10:43 Сейчас в теме
(20) >> Заходить в каждый и вносить изменения удовольствие ниже среднего

Будь мужиком! Исправь все руками!
23. kirovsbis 151 28.08.19 11:39 Сейчас в теме
(22)

)))

Ну да, настоящие мужики терпят!!!
26. Darklight 33 28.08.19 12:25 Сейчас в теме
(19)Смотрите мои посты (14)(16)(24) - я там как раз говорю об этом
Оставьте свое сообщение