Для 1С-разработчика: как по тексту файла модуля построить карту вызовов, найти процедуры с наибольшим числом обращений, самую длинную цепочку вызовов и процедуры без вызовов. Скрипт на Python 3 разбирает выгрузку конфигурации в файлы, прогон на учебном модуле показан целиком.
- Разбор текста - скрипт читает файл `.bsl`, платформа для этого не нужна
- Два прохода - объявления собираются до поиска вызовов, иначе вызов процедуры, объявленной ниже по тексту, считается внешним обращением
- Четыре чтения графа - узлы без входящих связей, горячие узлы, длинные цепочки, порядок рефакторинга
- Слепые зоны разбора - вызовы через строку, связи между модулями, обработчики по началу имени
- Прогон с числами - 17 объявлений, 19 связей внутри модуля, пять вызовов проверки заполнения, цепочка из пяти переходов
Текстовый разбор модуля даёт карту вызовов без платформы: видно, какие процедуры вызываются чаще всех, где цепочка тянется на пять переходов и какие процедуры не вызывает никто.
Для кого: 1С-разработчики, которым достался модуль объекта или общего модуля на сотни строк и нужно понять его устройство до первой правки.
Модуль объекта, который годами правили всей командой, читается тяжело по простой причине: у него нет карты. Строки есть, а структуры не видно. Вызовы разбросаны по сотням строк, одна и та же проверка дёргается из пяти мест, часть процедур не вызывается вовсе, и выясняется это только последовательным чтением сверху вниз.
Карту можно собрать из самого текста модуля. Достаточно найти все вызовы процедур и функций этого же модуля, посчитать входящие связи и посмотреть на результат: кто вызывается чаще всех, где самая длинная цепочка, какие процедуры висят без вызовов.
📦 Где взять текст модулей
Разбору нужен файл с текстом модуля; открытая форма конфигуратора для этого не годится. Текст модуля лежит в выгрузке конфигурации в файлы:
- в конфигураторе: «Конфигурация - Выгрузить конфигурацию в файлы», каталог выбирается в диалоге;
- из командной строки тем же действием:
DESIGNER /DumpConfigToFiles <каталог выгрузки>; - из 1C:EDT: экспорт проекта или выгрузка в файлы для сравнения с базой.
В выгрузке каждому модулю соответствует отдельный текстовый файл с расширением .bsl, и имя файла говорит о роли модуля: модуль управляемой формы - Module.bsl, модуль объекта - ObjectModule.bsl, модуль команды - CommandModule.bsl, модуль менеджера - ManagerModule.bsl. Модуль объекта лежит внутри каталога своего объекта, модуль общего модуля - внутри каталога общего модуля. Кодировка файлов - UTF-8.
Скрипт умеет обойти всю выгрузку целиком: если вместо файла передать ему каталог, он найдёт все .bsl внутри рекурсивно. Для первого раза удобнее один модуль: отчёт по объекту на несколько тысяч строк сам превращается в свалку, и читать его приходится с фильтром.
🧱 Как устроен разбор
Скрипт call_graph.py из вложения делает четыре шага.
- Убирает из текста то, что мешает искать скобки. Строковые литералы и комментарии вырезаются, но переводы строк сохраняются, поэтому номера строк остаются на месте. Скобка внутри текста запроса или в комментарии не создаёт ложную связь.
- Собирает объявления. Строка
Процедура Имя(...)илиФункция Имя(...)даёт имя и признакЭкспорт, если он есть в объявлении. - Ищет вызовы. Вызовом считается имя, после которого идёт открывающая скобка. Служебные слова языка отбрасываются, обращения через точку тоже:
Запрос.Выполнить()иТовары.Индекс()адресуют метод объекта и связи между процедурами модуля не создают. - Достраивает связи. Связь появляется только между двумя процедурами одного модуля. Всё остальное - обращения к платформе и другим модулям - идёт в отдельный счётчик.
Ядро разбора - четыре строки: два шаблона регулярных выражений и два списка слов. Объявление ловится шаблоном ^\s*(?:Процедура|Функция)\s+([A-Za-zА-Яа-яЁё_]\w*)\s*\(([^)]*), вызов - шаблоном (?<![.\w])([A-Za-zА-Яа-яЁё_]\w*)\s*\(. Запрет на точку и букву перед именем вызова отсекает обращения к методам объектов: Запрос.Выполнить() в список не попадает. Служебные слова языка - если, иначеесли, пока, для, возврат, новый, не, и, или, ждать - тоже отбрасываются, иначе каждое Если стало бы вызовом.
Разбор идёт в два прохода, и это принципиально. Сначала по всему файлу собираются объявления, потом по телам процедур ищутся вызовы. Однопроходный вариант, который я написала первым, ломается на первом же модуле: вызов процедуры, объявленной ниже по тексту, уходит в счётчик внешних обращений, и модуль показывает три внутренние связи там, где их девятнадцать. Во втором проходе имя вызова ищется среди собранных объявлений; если нашлось и это не самообъявление, в список связей добавляется пара «откуда - куда»; если не нашлось, растёт счётчик внешних обращений, и имя попадает в топ частых.
Обработчики событий платформы разбор помечает по началу имени: При, Обработка, Перед, После. Это список догадок; таблицы обработчиков платформы в разборе нет, и ниже видно, чем эта догадка плоха.
🧪 Прогон на учебном модуле
Проверяла скрипт на учебном модуле primer-modulya.bsl из вложения. Модуль объекта, 17 процедур и функций, логика намеренно запутана: проверка заполнения вызывается из пяти мест, цепочка сверки количества тянется на пять переходов, одна процедура не вызывается ниоткуда.
python call_graph.py primer-modulya.bsl --dot primer-graf.dot
Файл: primer-modulya.bsl Объявлено процедур и функций: 17 (из них с Экспорт: 1) Связей внутри модуля: 19 Вызовов внутри модуля с учётом повторов: 19 Обращений к платформе и другим модулям (в граф не входят): 19 Точки входа - обработчики событий (3): ОбработкаПроведения, ПриЗаписи, ПриОткрытии Точки входа - экспортные методы (1): ПроверитьЗаполнение Без входящих связей и без Экспорт (1): НайтиСтрокуПоКоду Вызываются из 3 и более мест: ПроверитьЗаполнение (5) Самая длинная цепочка (5 переходов): ОбработкаПроведения -> СформироватьДвижения -> ПроверитьЗаполнение -> ПроверитьТабличнуюЧасть -> ПроверитьСтрокуТЧ -> СверитьКоличество Самые частые внешние обращения (топ-10): Сообщить: 6 ЗначениеЗаполнено: 4 СтрШаблон: 4 МоментВремени: 1 ЭтоНовый: 1 Структура: 1 ЗаписьЖурналаРегистрации: 1 Формат: 1
Что из этого отчёта видно сразу:
- Одна внешняя точка. Из 17 процедур экспортная одна -
ПроверитьЗаполнение. Остальное внутренняя кухня, и это уже ограничивает радиус правки: снаружи модуль отдаёт одну функцию. - Внутренних связей и внешних обращений поровну, по 19. Модуль плотно работает с платформой:
Сообщить,ЗначениеЗаполнено,СтрШаблон, запрос остатков. По одному этому числу оценивают, насколько модуль привязан к контексту, но не время работы. - Четыре точки входа, из них три - обработчики.
ОбработкаПроведения,ПриЗаписи,ПриОткрытиивызываются платформой; вызовов в коде модуля у них нет, и в анализе их считают корнями. ПроверитьЗаполнениевызывается из пяти мест. Порог в три входящих связи она перешла одна. Правка в ней отзовётся в пяти сценариях.НайтиСтрокуПоКодуне вызывается ниоткуда и не экспортная. Либо мёртвая, либо вызывается извне модуля.- На конце самой длинной цепочки стоит запрос к виртуальной таблице остатков. Цепочка из пяти переходов ведёт к обращению к базе, и это первое место, которое стоит смотреть при разборе производительности.
💻 Граф в Graphviz
По ключу --dot скрипт пишет файл в формате Graphviz: 40 строк, из них 17 узлов и 19 связей. Устройство файла простое. Открывает его строка digraph module {, за ней rankdir=LR задаёт направление слева направо. Дальше идут строки узлов вида "ПроверитьЗаполнение" [fillcolor="#FDE68A", style="filled"]; и строки связей вида "ОбработкаПроведения" -> "ПроверитьЗаполнение";. Заливка узла зависит от числа входящих связей: три и больше - жёлтая, ни одной - серая, остальные белые. По этой заливке карту и читают: серые узлы слева - точки входа, жёлтый узел в середине - место, которое меняют осторожно, длинный хвост связей справа - цепочка проверок.
Картинка рисуется одной командой, сам Graphviz ставится отдельно.
dot -Tsvg primer-graf.dot -o primer-graf.svg
Если отдельный пакет ставить не хочется, тот же граф описывается текстом Mermaid и рисуется прямо в документации или в wiki проекта.
|
ОбработкаПроведения
|
|
СформироватьДвижения
|
|
ПроверитьЗаполнение
|
|
ПроверитьТабличнуюЧасть
|
|
ПроверитьСтрокуТЧ
|
|
СверитьКоличество
|
Модуль занимает 191 строку, картинка по нему - 17 узлов и 19 связей. Читается она быстрее текста: хвост цепочки проверок виден сразу, без вычитывания сотни строк подряд.
🧭 Как читать граф
Читаю карту в четыре прохода, от дешёвого к дорогому.
Узлы без входящих связей. Серая заливка означает, что изнутри модуля процедуру никто не зовёт. Дальше два варианта: обработчик события или экспортный метод - это нормальная точка входа, и трогать её не надо. Всё остальное - кандидат в мёртвый код, но перед удалением имя ищут по всей выгрузке: процедура может вызываться из другого модуля, из строки Выполнить или из расширения. В учебном модуле такой узел один, НайтиСтрокуПоКоду.
Горячие узлы. Три и больше входящих связей - место, куда сходятся сценарии. Смотреть надо на то, что внутри: если в такой процедуре цикл по табличной части или запрос в цикле, правку начинают с неё. Если внутри две строки проверки реквизита, ничего страшного в пяти вызовах нет. Второй счётчик в отчёте помогает отличить одно от другого: у процедуры с запросом на конце цепочки число обращений к платформе будет выше, чем у чистой проверки.
Длинные цепочки. Цепочка на пять-шесть переходов означает пять-шесть уровней, которые надо держать в голове при отладке. Иногда это осмысленная иерархия, иногда следы правок: промежуточная функция появилась, чтобы обойти один вызов, и осталась. Склеивать уровни стоит тогда, когда промежуточная функция ничего не добавляет к смыслу.
Порядок рефакторинга. Начинаю с мёртвых узлов: удаление того, что никто не вызывает, поведение не меняет. Потом разбираю горячий узел: разбиваю длинную процедуру на части по смыслу проверок. Потом цепочку. После каждого шага прогоняю сценарии, которые затрагивает изменённая ветка: граф показывает структуру, а проверку поведения он не заменяет.
🚧 Чего статический разбор не видит
Текстовый разбор дёшев ровно потому, что не разбирает синтаксис. Плата за это - слепые зоны.
- Вызовы через строку.
Выполнить("Проверить" + ИмяЧасти),Вычислить, вызов метода по имени из реквизита - для скрипта это текст в кавычках, и он вырезан вместе с литералами. Такие места ищутся глазами по выгрузке. - Связи между модулями. Граф строится по одному файлу. Обращения к общим модулям попадают в счётчик внешних обращений, и по нему видно только их количество, но не адресат.
- Обработчики определяются по началу имени.
При,Обработка,Перед,После- догадка скрипта. ПроцедураПривестиКНормальномуВидупопадёт в точки входа, хотя обработчиком не является. Список приходится просматривать глазами. - Расширения и заимствование. В расширении модуль-заимствование и директивы
&Вместо,&После,&Передсвязывают код расширения с основным модулем. Разбор одного файла эту связь не покажет: нужны обе стороны. - Время работы. Ни одна цифра отчёта не говорит о производительности.
Сообщить: 6означает шесть обращений к платформе в тексте; миллисекунд за этой цифрой нет.
Из этого списка следуют две привычки. Перед удалением «мёртвой» процедуры имя ищут по всей выгрузке, включая расширения. Перед выводами о скорости запускают замер: карта показывает, где смотреть, и не отвечает на вопрос, сколько это стоит в секундах.
📚 Что уже есть по теме
Три инструмента закрывают ту же задачу с другой стороны, и стоит знать, чем они отличаются от скрипта.
1C:EDT, панель «Иерархия вызовов». Встроенная панель показывает, кто вызывает метод и что вызывает он, по конкретному методу проекта. Работает разбором кода: понимает обращения через точку и типы. Карту по всему модулю разом она не рисует, зато не ошибается на вызовах через строку там, где их видно статически.
Отладчик конфигуратора и «Стек вызовов». Точка останова на входе в подозрительную процедуру даёт цепочку вызовов живого сценария. Точность здесь максимальная: за цепочкой стоит факт выполнения. Цена - покрытие: чтобы обойти модуль с десятками ветвлений, нужно прогнать столько сценариев, сколько ветвей, а для разбора производительности ещё и замерить время на каждом уровне.
Обработка «Карта вызовов для проектов 1С» на Infostart (публикация 78976, автор stal76). Строит карту вызовов по объектам, модулям и методам конфигурации и выгружает результат для отрисовки в Graphviz. В описании заявлены платформы 8.1 и 8.2, обработка платная; для 8.3 её нужно проверять отдельно.
Отдельно отмечу опубликованную статью 2798216 про граф знаний конфигурации: там связи между объектами конфигурации - справочниками, документами, регистрами. Здесь уровень ниже: процедуры внутри одного модуля. Для первого знакомства с чужим модулем второй разрез полезнее, потому что показывает, с какой процедуры начинать чтение.
📎 Файл к статье
Во вложении три файла и описание к ним.
call_graph.py- скрипт разбора. Нужен Python 3.8 и старше, сторонних библиотек нет, платформа не нужна.primer-modulya.bsl- учебный модуль, на котором проверялся скрипт: 17 процедур, пять вызовов проверки заполнения, цепочка на пять переходов, одна процедура без вызовов.primer-graf.dot- граф учебного модуля, построенный скриптом. Открывается просмотрщиком DOT или командойdot -Tsvg.README.md- что проверено и что осталось непроверенным. Отдельно: учебный модуль не компилировался на платформе, для запуска нужна база с регистром накопления и документом с табличной частьюТовары.
🏁 Итог
Порядок работы с незнакомым модулем укладывается в три шага. Выгружаю конфигурацию в файлы и забираю нужный .bsl. Прогоняю по нему скрипт и читаю отчёт: точки входа, горячие узлы, цепочки, узлы без связей. Открываю граф и смотрю, откуда начинать чтение.
Дальше начинается работа, которую скрипт за разработчика не сделает. Мёртвый узел перед удалением проверяется поиском по выгрузке. Кандидаты на оптимизацию проверяются замером; количество входящих связей для этого не годится. Разбиение длинной процедуры заканчивается прогоном сценариев. Карта показывает, где смотреть, и экономит на этом часы чтения; выводы о поведении кода она не выдаёт.
Вступайте в нашу телеграмм-группу Инфостарт