Общие модули 1С: синтаксис вызова, флаги и ошибки

· 9 мин

Содержание

Чтобы вызвать процедуру общего модуля, пишут ИмяМодуля.ИмяПроцедуры(Параметры), а саму процедуру в модуле помечают ключевым словом Экспорт. Если модуль объявлен глобальным, имя модуля не указывают — обращаются прямо по имени процедуры. Это весь базовый синтаксис; всё остальное — нюансы контекста (клиент или сервер) и настройки флагов самого модуля.

Общий модуль — это общий склад переиспользуемого кода: процедуры и функции, которые нужны в разных местах конфигурации. Положили расчёт один раз, дёргаете из документов, обработок и форм. Разница между «работает» и «метод не обнаружен» почти всегда в двух вещах: забыли Экспорт или вызвали серверный код оттуда, где он не компилируется.

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

Где лежит общий модуль и как его создать

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

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

  • 📦 суффикс Сервер — для серверных модулей (РаботаСФайламиСервер);
  • 📦 суффикс Клиент — для клиентских (РаботаСФайламиКлиент);
  • 📦 суффикс КлиентСервер — для модулей с кодом, который работает и там, и там;
  • 📦 суффикс ВызовСервера — для серверных модулей, которые дёргают с клиента.

Суффиксы — не требование платформы, а соглашение, но оно сильно упрощает жизнь: по имени модуля сразу видно, откуда его можно вызывать. Код методов пишут в модуле объекта — открывается двойным щелчком по модулю или из контекстного меню.

Не сваливайте всё в один гигантский модуль. Разбивайте по смыслу: отдельно работа с файлами, отдельно расчёты, отдельно обмен. Так проще искать и меньше конфликтов при командной разработке.

Синтаксис обращения к процедуре и функции

Обращение строится через точку: имя общего модуля, затем имя метода.

// Вызов процедуры серверного общего модуля "РаботаСДокументами"
РаботаСДокументами.ПровестиДокумент(СсылкаНаДокумент);

// Вызов функции с получением результата
Итог = РасчётыОбщие.ВычислитьСумму(ТаблицаСтрок);

Имя модуля — это имя объекта метаданных, как оно задано в дереве конфигурации (ветка Общие → Общие модули), а не синоним и не заголовок. Регистр не важен, но лучше писать как в метаданных.

Процедура (Процедура … КонецПроцедуры) значения не возвращает — её вызывают отдельным оператором. Функция (Функция … КонецФункции) возвращает результат через Возврат, поэтому её вызов ставят в правой части присваивания или внутри выражения.

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

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

Процедура ПровестиДокумент(Ссылка) Экспорт
    // тело процедуры
КонецПроцедуры

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

Флаги общего модуля: где выполняется код

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

ФлагЧто включает
Клиент (управляемое приложение)Код компилируется и исполняется на клиенте
СерверКод исполняется на сервере
Внешнее соединениеДоступен при работе через внешнее соединение (COM)
Вызов сервераРазрешает обращаться к серверному модулю из клиентского кода
ПривилегированныйКод работает без проверки прав доступа
ГлобальныйМетоды доступны без указания имени модуля

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

Практическое правило: серверную логику (запросы, проведение, работу с базой) кладут в модуль с флагом «Сервер», а мелкие клиентские помощники (предупреждения, вопросы пользователю) — в модуль «Клиент (управляемое приложение)».

Вызов сервера: клиент-серверное взаимодействие

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

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

&НаКлиенте
Процедура ОбновитьКурсы(Команда)
    // ВалютыСервер — серверный модуль с флагом "Вызов сервера"
    ВалютыСервер.ЗагрузитьКурсыНаСегодня();
КонецПроцедуры

Есть ограничение, о которое спотыкаются чаще всего: через серверный вызов нельзя передавать и возвращать мутабельные прикладные объекты, которые не поддерживают передачу между клиентом и сервером. Примитивы (число, строка, дата, булево), ссылки, структуры и таблицы значений ходят свободно; а, например, объект документа (ДокументОбъект) в клиентский контекст так не вернуть.

Каждый серверный вызов — это обращение по сети. Десять вызовов в цикле — это десять раундтрипов. Собирайте данные в одну структуру и передавайте одним вызовом.

Сравните два подхода. Плохой — дёргать сервер в цикле:

// Антипример: N серверных вызовов
Для Каждого Строка Из Список Цикл
    ОстаткиСервер.ОбновитьОстаток(Строка.Товар); // вызов за итерацию
КонецЦикла;

Хороший — собрать параметры и сходить на сервер один раз:

// Один серверный вызов на весь список
Товары = Новый Массив;
Для Каждого Строка Из Список Цикл
    Товары.Добавить(Строка.Товар);
КонецЦикла;
ОстаткиСервер.ОбновитьОстатки(Товары);

На коротком списке разница незаметна, на сотнях строк — это секунды против долей секунды.

Ещё одна частая путаница — директивы компиляции. Разработчик пробует поставить &НаСервере или &НаКлиенте над процедурой общего модуля, как в модуле формы. В общих модулях директивы компиляции не используются — контекст полностью определяют флаги модуля.

Директивы (&НаКлиенте, &НаСервере, &НаСервереБезКонтекста) ставят в модулях форм, команд и в модуле управляемого приложения, где в одном файле уживается и клиентский, и серверный код. В общем модуле весь код работает в том контексте, который задан галочками, — отдельная пометка процедурам не нужна.

Отсюда простое следствие: если нужен метод и на клиенте, и на сервере, делают модуль с обоими флагами (Клиент + Сервер). Платформа скомпилирует две копии, и каждая сторона вызовет свою.

Передача параметров и возврат значений

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

// Таблица передаётся копией — исходная не изменится
Функция ВычислитьСумму(Знач Таблица) Экспорт
    Итог = 0;
    Для Каждого Строка Из Таблица Цикл
        Итог = Итог + Строка.Сумма;
    КонецЦикла;
    Возврат Итог;
КонецФункции

Функция обязана вернуть значение через Возврат. Если выполнение дойдёт до КонецФункции без Возврат, функция вернёт Неопределено — это законно, но часто это и есть скрытая причина ошибки «значение не определено» дальше по коду.

Вернуть можно что угодно: число, строку, структуру, массив, таблицу значений. Когда результатов несколько, удобно собрать их в структуру:

Функция РассчитатьИтоги(Знач ТаблицаСтрок) Экспорт
    Результат = Новый Структура("Сумма, Количество", 0, 0);
    Для Каждого Строка Из ТаблицаСтрок Цикл
        Результат.Сумма = Результат.Сумма + Строка.Сумма;
        Результат.Количество = Результат.Количество + 1;
    КонецЦикла;
    Возврат Результат;
КонецФункции

Выходные параметры тоже работают: передаёте переменную без Знач, процедура её заполняет, вызывающий код читает результат после вызова.

// Процедура возвращает данные через выходной параметр "Ошибки"
Процедура ПроверитьЗаполнение(Знач Объект, Ошибки) Экспорт
    Ошибки = Новый Массив;
    Если Не ЗначениеЗаполнено(Объект.Контрагент) Тогда
        Ошибки.Добавить("Не заполнен контрагент");
    КонецЕсли;
КонецПроцедуры

// Вызов
СписокОшибок = Неопределено;
ПроверкиСервер.ПроверитьЗаполнение(Документ, СписокОшибок);

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

Привилегированный режим и повторное использование

Два флага модуля прямо влияют на производительность и безопасность.

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

Повторное использование возвращаемых значений — кэш результатов функций. У свойства три значения:

  • 🔁 Не использовать — функция считается заново при каждом вызове;
  • 🔁 На время вызова — результат кэшируется в пределах одного серверного вызова;
  • 🔁 На время сеанса — результат живёт весь сеанс пользователя.

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

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

Глобальный общий модуль: вызов без имени

Если у модуля стоит флаг «Глобальный», его экспортные методы доступны во всём прикладном решении напрямую — имя модуля писать не нужно.

// В глобальном модуле объявлена процедура ЗаписатьСобытие()
ЗаписатьСобытие("Начало обработки");

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

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

Типичные ошибки вызова и как их устранить

Почти все ошибки обращения к общему модулю сводятся к нескольким причинам. Сопоставьте текст ошибки с таблицей.

Диагностика: выберите симптом

Если вы только проектируете модуль и не уверены, какие флаги ставить, оттолкнитесь от того, откуда будете вызывать метод.

Откуда вызываете метод общего модуля?

Частые вопросы

Чем вызов процедуры отличается от вызова функции?
Процедуру вызывают отдельным оператором — она ничего не возвращает. Функцию ставят в выражение или в правую часть присваивания, потому что она возвращает результат через Возврат.
Нужно ли писать имя модуля при вызове?
Для обычного модуля — да: ИмяМодуля.Метод(). Для глобального модуля имя не пишут, метод вызывают напрямую. Внутри самого модуля к своим методам тоже обращаются без имени.
Почему метод виден в конфигураторе, но не вызывается из другого модуля?
Скорее всего, у него нет ключевого слова Экспорт. Без него метод доступен только внутри своего модуля.
Можно ли вызвать серверный модуль прямо из формы на клиенте?
Да, если у модуля включён флаг «Вызов сервера». Тогда обращение выполнится как серверный вызов. Без этого флага платформа выдаст ошибку о необнаруженном методе.
Что делает «Повторное использование возвращаемых значений»?
Кэширует результат функции для конкретного набора параметров — на время вызова или на время сеанса. Полезно для редко меняющихся настроек; для изменяющихся данных вернёт устаревший результат.