Код 1С от нейросети: как найти выдуманные методы и поля до первого запуска
Иван Недомолков · Опубликовано:
Модель, которая пишет код 1С, почти никогда не ошибается в грамматике. Ошибается она в именах: берёт их по аналогии, и аналогия звучит убедительно. ОбщегоНазначения.ПолучитьЗначениеРеквизитаОбъекта похоже на БСП ровно настолько, чтобы его не перепроверяли, но в типовой УТ 11.5 такой функции нет. Там есть ЗначениеРеквизитаОбъекта и ЗначениеРеквизитаОбъектов.
Обычно про выдуманное имя узнают в момент, когда выполнение до него дошло. Если повезло, на копии. Эта статья про то, как найти такие места раньше: какая проверка какой класс ошибок вообще способна заметить и чем закрыть те, что не видит никто.
Карта: класс ошибки и проверка, которая его видит
Кусок кода из чата ещё не лежит в модуле конфигурации, поэтому контроль модулей Конфигуратора и EDT до него не дотягивается. Остаются приёмы, доступные из консоли кода. Мы прогнали их на типовой УТ 11.5 по каждому классу ошибок.
| Что выдумано | Выполнить с обёрткой Если Ложь | Что закрывает дыру |
|---|---|---|
синтаксис: лишний знак, цикл без КонецЦикла | ловит, со строкой и колонкой | - |
| глобальная функция, которой нет | ловит | - |
| общий модуль, которого нет | ловит как “Переменная не определена” | - |
| справочник, документ, регистр | молчит | коллекции Метаданные |
| реквизит объекта | молчит | Метаданные, включая стандартные реквизиты |
| метод общего модуля | молчит | вызов со ста аргументами |
| метод менеджера или объекта | молчит | тот же вызов, для объекта на экземпляре в памяти |
| поле в тексте запроса | молчит, текст запроса для него просто строка | НайтиПараметры() в цикле |
| поле переменной неизвестного типа | молчит | ничем, типа из текста не видно |
Обёртка работает так: фрагмент кладётся внутрь Если Ложь Тогда ... КонецЕсли и отдаётся Выполнить. Платформа компилирует его целиком, а ветка никогда не исполняется.
Код =
"Если Ложь Тогда
| Спр = Справочники.ВыдуманныйСправочник;
| Имя = ОбщегоНазначения.НесуществующийМетод(1);
|КонецЕсли;";
Выполнить(Код); // исключения не будет
Почему компилятор пропускает половину
Если посмотреть на правую половину таблицы, видно общее правило. Компилятор разбирает имя слева до первой точки: глобальная функция, общий модуль, менеджер Справочники. Всё, что идёт после точки, он оставляет на время выполнения. Модуль ОбщегоНазначения есть в каждой конфигурации на БСП, поэтому левая часть выдуманного вызова проверку проходит, а правую никто не смотрит.
Отсюда и способ лечения: вторую половину имени надо проверять отдельно, своим средством для каждого вида объекта.
Справочники, документы, реквизиты: хватает метаданных
С объектами метаданных просто, их состав платформа отдаёт целиком. Набросок проверки для справочника и его реквизита:
// Есть ли справочник и реквизит у него. Стандартные реквизиты
// (Код, Наименование, Родитель) лежат в отдельной коллекции.
Функция ЕстьРеквизитСправочника(ИмяСправочника, ИмяРеквизита)
ОбъектМД = Метаданные.Справочники.Найти(ИмяСправочника);
Если ОбъектМД = Неопределено Тогда
Возврат Ложь;
КонецЕсли;
Если ОбъектМД.Реквизиты.Найти(ИмяРеквизита) <> Неопределено Тогда
Возврат Истина;
КонецЕсли;
Для Каждого Станд Из ОбъектМД.СтандартныеРеквизиты Цикл
Если Станд.Имя = ИмяРеквизита Тогда
Возврат Истина;
КонецЕсли;
КонецЦикла;
Возврат Ложь;
КонецФункции
Для документов, регистров и перечислений то же самое по соседним коллекциям. Табличные части и их реквизиты ищутся через ТабличныеЧасти.
Метод общего модуля: спросить платформу, не входя в тело
Тут метаданные не помогают. Модуль в Метаданные.ОбщиеМодули найдётся, а перечня его экспортных методов там нет, и прочитать текст модуля из пользовательского сеанса нельзя. Вызвать метод по-честному тоже нельзя, он что-то сделает.
Выручает порядок, в котором платформа обрабатывает вызов. Сначала она сравнивает число переданных аргументов с объявлением и только потом входит в метод. Сто аргументов не принимает ни один обычный метод, поэтому ответов бывает два:
- “Слишком много фактических параметров” значит, что метод в модуле есть, а тело не запускалось;
- “Метод объекта не обнаружен” значит, что метода нет.
// Есть ли экспортный метод в общем модуле. Тело метода не выполняется:
// при ста аргументах платформа отказывает до входа в него.
// Истина - метод есть, Ложь - выдуман, Неопределено - проверить не удалось.
Функция ЕстьМетодОбщегоМодуля(ИмяМодуля, ИмяМетода) Экспорт
Если Метаданные.ОбщиеМодули.Найти(ИмяМодуля) = Неопределено Тогда
Возврат Ложь; // модуля нет вовсе
КонецЕсли;
Аргументы = Новый Массив;
Для Н = 1 По 100 Цикл
Аргументы.Добавить(Формат(Н, "ЧГ=0"));
КонецЦикла;
Попытка
Выполнить(ИмяМодуля + "." + ИмяМетода + "(" + СтрСоединить(Аргументы, ",") + ");");
Исключение
Текст = НРег(ОписаниеОшибки());
Если СтрНайти(Текст, "слишком много фактических параметров") > 0
Или СтрНайти(Текст, "too many actual parameters") > 0 Тогда
Возврат Истина;
ИначеЕсли СтрНайти(Текст, "метод объекта не обнаружен") > 0
Или СтрНайти(Текст, "object method not found") > 0 Тогда
Возврат Ложь;
КонецЕсли;
Возврат Неопределено; // другая ошибка: модуль недоступен в этом контексте и т.п.
КонецПопытки;
// Сюда дойдёт только метод, у которого сто параметров и больше:
// он выполнился по-настоящему.
Возврат Истина;
КонецФункции
На УТ 11.5 и платформе 8.3.27 функция отдаёт Ложь для ПолучитьЗначениеРеквизитаОбъекта и Истину для ЗначениеРеквизитаОбъекта. То, что тело метода при этом не запускается, мы проверили прогоном.
Где у приёма края:
- проверяется только существование метода, число и порядок параметров остаются за кадром;
- метод со ста параметрами и больше выполнился бы на самом деле, в типовых таких нет;
- звать функцию надо в том контексте, где модуль доступен: клиентский модуль на сервере даст “Переменная не определена”, и ответ будет Неопределено;
Выполнитьисполнит всё, что подставлено вИмяМетода, поэтому туда должно попадать имя, разобранное из текста, а не произвольная строка.
Методы менеджеров и объектов проверяются тем же вызовом. Для объекта нужен экземпляр, его можно создать в памяти через СоздатьЭлемент() и не записывать. Так на Справочники.Контрагенты.НайтиПоИНН в типовой УТ 11.5 получается ответ, что такого метода нет ни у платформы, ни в модуле менеджера. А если метода нет и в общем модуле, имеет смысл поискать его в соседних модулях и в вариантах имени: без приставки “Получить”, с “Объектов” вместо “Объекта”. Так рядом с выдуманным именем появляются два настоящих.
Запрос: собрать все ошибки за один проход
У запросов обратная ситуация. Неверное поле платформа замечает и без выполнения: Новый Запрос(Текст).НайтиПараметры() компилирует текст и бросает исключение с позицией. Неудобство в том, что исключение одно на компиляцию.
Пример с нашего стенда, задание “остатки по складам”, промпт без описания метаданных:
ВЫБРАТЬ
ОстаткиТоваровНаСкладах.Склад КАК Склад,
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ОстаткиТоваровНаСкладах.Номенклатура) КАК КоличествоПозиций,
СУММА(ОстаткиТоваровНаСкладах.КоличествоОстаток) КАК ВНаличии
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&КонецПериода, ) КАК ОстаткиТоваровНаСкладах
ГДЕ
ОстаткиТоваровНаСкладах.КоличествоОстаток <> 0
СГРУППИРОВАТЬ ПО
ОстаткиТоваровНаСкладах.Склад
Ресурс регистра ТоварыНаСкладах называется ВНаличии, в таблице остатков он превращается в ВНаличииОстаток. КоличествоОстаток модель взяла у соседнего регистра ТоварыОрганизаций, где ресурс действительно Количество, и сделала это в пяти заданиях из тридцати. Ошибок в тексте две, в строках 4 и 8, а платформа называет одну, причём нижнюю:
{(8, 26)}: Поле не найдено "ОстаткиТоваровНаСкладах.КоличествоОстаток"
Сами по себе такие ошибки безопасны, их видно сразу. Раздражает другое: каждая следующая всплывает только после исправления предыдущей, и на длинном запросе выходит несколько кругов “отправил модели, получил, проверил”. Настоящего имени поля платформа не подскажет ни на одном круге.
Круги можно прогнать программно. Нашли ошибку, заменили проблемное место заглушкой, скомпилировали снова. Поле заменяется на NULL, выдуманная таблица на ближайшую настоящую, ЗНАЧЕНИЕ(...) тоже на NULL. Скелет цикла:
Для Проход = 1 По 40 Цикл
Попытка
Запрос = Новый Запрос(СтрСоединить(Строки, Символы.ПС));
Запрос.НайтиПараметры(); // компиляция без выполнения
Прервать; // ошибок больше нет
Исключение
Ош = РазобратьОшибку(ОписаниеОшибки()); // строка, колонка, имя
КонецПопытки;
Ошибки.Добавить(Ош);
Если Ош.Вид = "Синтаксис" Тогда
Прервать; // дальше платформа текст не читает
КонецЕсли;
// ошибочное поле подменяем на NULL и компилируем снова
Строки[Ош.Строка - 1] = ЗаменитьВСтроке(Строки[Ош.Строка - 1], Ош.Колонка, Ош.Имя, "NULL");
КонецЦикла;
Подсказку с настоящими именами даёт СхемаЗапроса: у каждого источника по его псевдониму есть список доступных полей, в том числе полей виртуальных таблиц с суффиксами. Для КоличествоОстаток из этого списка выходят ВНаличииОстаток и КОтгрузкеОстаток.
На синтаксической ошибке цикл обрывается, потому что дальше неё компилятор текст не разбирает. Одну такую ошибку стоит знать в лицо, потому что ответ платформы её не объясняет:
ВЫБРАТЬ ПЕРВЫЕ 10
В.Код,
В.Наименование,
В.НаименованиеПолное
ИЗ
Справочник.Валюты КАК В
Платформа пишет “Ожидается имя” и указывает на В в последней строке. Причина в том, что В занято языком запросов (В (...), В ИЕРАРХИИ) и псевдонимом быть не может. Таблицу по первой букве называют машинально и люди, и модели.
Чего так не найти
- Поля переменной, тип которой не виден из текста.
Выборка.Поле, параметр процедуры, результат своей функции: вывода типов нет. Тип определяется только там, где он очевиден из кода, например послеСоздатьЭлемент(),НайтиПоКоду(),ТЧ.Добавить()или вДля Каждого Стр Из Объект.Товары. - Запроса с настоящими именами и неверным смыслом. Он выполнится и вернёт правдоподобное число. Его ловит только сверка результата с эталоном, это отдельная методика, она разобрана в статье про проверку запроса 1С от нейросети.
- Окружения вставки. Код, вырезанный из модуля, ссылается на переменные модуля и соседние процедуры. Это предупреждения, а не ошибки, и в счёт ложных срабатываний ниже они не входят.
Самый дешёвый способ сократить число выдуманных имён ещё до проверки - дать модели структуру базы в промпте. Как её выгрузить и сколько её влезает, разобрано в статье про метаданные для нейросети. Если правка касается формы, у неё свои ловушки: имена обработчиков и видимость полей из файлов формы не угадать, об этом отдельный разбор.
На чём проверяли
УТ 11.5.22.67, платформа 8.3.27. Набор из 28 кейсов: 19 с одной ошибкой (7 запросов и 12 фрагментов кода) и 9 без ошибок. Все 28 разобраны верно.
Ложные срабатывания мерили на коде самой типовой: он компилируется и работает, значит, любая найденная в нём ошибка ложная. Взяли три случайные выборки по 60 процедур. Первая дала 10 ложных ошибок, вторая 3, после исправлений все три выборки прошли чисто, 60 из 60. Третью выборку сняли уже после всех исправлений, на настройке она не использовалась.
Готовая обработка
Все приёмы выше вместе со сверкой имён по метаданным собраны в обработку проверки кода и запросов нейросети. В неё вставляется текст из чата, по кнопке выходит таблица: строка, что не так и как это называется в вашей базе. Список ошибок одной кнопкой превращается в текст для обратной вставки в чат. Запрос можно прогнать на 10 строк, а кнопка “Пример с ошибками” соберёт демонстрационный запрос и код из метаданных вашей же базы. В базу обработка не пишет и к нейросети не обращается.
Для ошибок смысла, когда запрос выполнился и вернул не те строки, нужен другой инструмент: стенд проверки запросов нейросети с тридцатью эталонами к УТ 11.5.