Вызов процедуры из другой процедуры: синтаксис и примеры
Содержание
Чтобы вызвать одну процедуру из другой, достаточно написать её имя со скобками и аргументами прямо в теле вызывающей процедуры: СформироватьДвижения(Отказ);. Если нужная процедура лежит в другом модуле, у неё должен стоять признак Экспорт, а вызов пишется через имя модуля: РаботаСДокументами.ЗаполнитьШапку(ЭтотОбъект);.
Вызвать процедуру из процедуры в 1С — базовая операция встроенного языка 8.3, но у неё есть нюансы: как передаются параметры, откуда видна вызываемая процедура, можно ли вызвать её саму из себя и что делать с серверным и клиентским контекстом. Разберём по порядку, с примерами кода, которые решают задачу целиком.
Типичная ситуация: вы пишете обработку проведения документа, часть логики повторяется в нескольких местах, и её хочется вынести в отдельную процедуру, чтобы не копировать код. Дальше — как это сделать правильно.
Как устроен вызов: процедура, функция, экспорт
Вызов процедуры — это её имя, круглые скобки и перечень аргументов через запятую. Точка с запятой в конце:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
СформироватьДвижения(Отказ);
ПроверитьОстатки(Отказ);
КонецПроцедуры
Процедура СформироватьДвижения(Отказ)
// логика формирования движений
КонецПроцедуры
Процедура не возвращает значение — её вызывают отдельным оператором. Функция возвращает результат через Возврат, поэтому её вызывают внутри выражения:
Итого = ПосчитатьСумму(ТаблицаТоваров);
Функция ПосчитатьСумму(Таблица)
Сумма = 0;
Для Каждого Строка Из Таблица Цикл
Сумма = Сумма + Строка.Сумма;
КонецЦикла;
Возврат Сумма;
КонецФункции
Пока обе подпрограммы лежат в одном модуле, вызов работает без лишних слов. Чтобы обратиться к процедуре из другого модуля (общего модуля, модуля объекта, модуля формы), у неё должен стоять Экспорт в строке объявления:
// В общем модуле РаботаСДокументами
Процедура ЗаполнитьШапку(Документ) Экспорт
Документ.Дата = ТекущаяДата();
КонецПроцедуры
Вызов из модуля объекта документа — через имя общего модуля:
РаботаСДокументами.ЗаполнитьШапку(ЭтотОбъект);
Если процедура нужна только внутри своего модуля, Экспорт не ставьте: чем меньше публичных методов, тем проще сопровождать код.
Порядок объявления внутри одного модуля значения не имеет. Платформа компилирует модуль целиком, поэтому процедуру можно вызвать выше по тексту, чем она описана, — переносить объявления вверх, как в некоторых языках, не нужно. А вот между модулями видимость определяется уже не порядком, а признаком Экспорт и флагами общего модуля.
Передача параметров: по значению и по ссылке
По умолчанию параметры в 1С передаются по ссылке. Это значит, что если внутри вызванной процедуры присвоить параметру новое значение, оно вернётся в вызывающий код:
Процедура Пример()
Количество = 10;
Увеличить(Количество);
Сообщить(Строка(Количество)); // выведет 11
КонецПроцедуры
Процедура Увеличить(Параметр)
Параметр = Параметр + 1;
КонецПроцедуры
Чтобы защитить исходную переменную от изменений, перед параметром ставят ключевое слово Знач — передача по значению. Внутри процедуры создаётся локальная копия, и присваивание наружу не возвращается:
Процедура Увеличить(Знач Параметр)
Параметр = Параметр + 1; // на вызывающий код не влияет
КонецПроцедуры
Здесь важный подвох, на котором спотыкаются даже опытные разработчики. Знач копирует только ссылку, а не сам объект. Для ссылочных типов — Массив, Структура, ТаблицаЗначений, Соответствие — обе переменные указывают на один и тот же объект в памяти. Поэтому изменение содержимого видно снаружи, несмотря на Знач:
Процедура ДобавитьЭлемент(Знач Список)
Список.Добавить("Новый"); // исходный массив тоже изменится
КонецПроцедуры
Знач защищает только от переприсваивания самой переменной (Список = Новый Массив), но не от вызова её методов. Если нужна настоящая независимая копия таблицы или структуры, делайте её явно — например, через Скопировать() у таблицы значений.
Параметрам можно задать значение по умолчанию — тогда при вызове их разрешается не указывать:
Процедура Записать(Текст, ВЖурнал = Ложь)
// если второй аргумент не передан, ВЖурнал = Ложь
КонецПроцедуры
Записать("Готово"); // ВЖурнал = Ложь
Записать("Ошибка", Истина); // ВЖурнал = Истина
Значением по умолчанию может быть только литерал (число, строка, Истина, Ложь, Неопределено), но не выражение или вызов функции.
Необязательные параметры в конце списка при вызове можно не указывать. А вот чтобы пропустить параметр в середине и задать тот, что идёт после него, оставьте пустое место между запятыми — платформа подставит значение по умолчанию:
// Выбрать(<Родитель>, <Владелец>, <Отбор>, <Порядок>)
Выборка = Справочники.Номенклатура.Выбрать(Родитель, , Отбор);
Здесь владелец пропущен (между запятыми пусто), а отбор задан. Порядок аргументов строго позиционный, именованной передачи во встроенном языке нет — ещё один довод в пользу Структуры, когда параметров много.
Область видимости и контекст клиент–сервер
Откуда видна вызываемая процедура, зависит от того, где она объявлена:
- 🔹 Локальные переменные процедуры живут только внутри неё. Снаружи их нет.
- 🔹 Переменные модуля (
Перемв начале модуля) доступны всем процедурам этого модуля. - 🔹 Экспортные процедуры общего модуля доступны отовсюду, где этот модуль виден по его флагам (Клиент / Сервер / Вызов сервера).
Вторая ось — контекст исполнения. В управляемом приложении код работает либо на клиенте, либо на сервере, и это задаётся директивами компиляции перед процедурой:
| Директива | Где исполняется |
|---|---|
&НаКлиенте | на клиенте, есть доступ к форме и диалогам |
&НаСервере | на сервере, с контекстом формы |
&НаСервереБезКонтекста | на сервере, без данных формы (быстрее) |
&НаКлиентеНаСервереБезКонтекста | доступна и там, и там |
Правило вызовов между контекстами:
- Клиентская процедура может вызвать серверную (
&НаСервереили&НаСервереБезКонтекста) — это серверный вызов. - Серверная процедура не может напрямую вызвать клиентскую: с сервера нет доступа к форме и интерфейсу.
Серверный вызов — это обращение к серверу по сети. Не вызывайте серверные процедуры в цикле по одной записи: соберите данные и передайте их одним вызовом, иначе форма будет заметно тормозить.
Рекурсия: когда процедура вызывает сама себя
Процедура или функция может вызвать сама себя — это рекурсия. Она удобна для обхода древовидных данных: групп справочника, подчинённых строк, структуры каталога.
Классический пример — факториал:
Функция Факториал(Н)
Если Н <= 1 Тогда
Возврат 1;
КонецЕсли;
Возврат Н * Факториал(Н - 1);
КонецФункции
Факториал(3) развернётся в 3 * 2 * 1 = 6. Главное в любой рекурсии — условие выхода. Здесь это Если Н <= 1. Без него вызовы не остановятся.
На практике рекурсией удобно обходить дерево групп справочника: для каждой группы обрабатываем её элементы, а для вложенных групп вызываем ту же процедуру ещё раз. Схематично это выглядит так:
Процедура ОбойтиГруппу(Родитель)
Выборка = Справочники.Номенклатура.Выбрать(Родитель);
Пока Выборка.Следующий() Цикл
Если Выборка.ЭтоГруппа Тогда
ОбойтиГруппу(Выборка.Ссылка); // рекурсия: спускаемся на уровень ниже
Иначе
// обрабатываем элемент
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Процедура вызывает сама себя для каждой вложенной группы, пока не дойдёт до элементов. Условие выхода здесь неявное: когда у очередной группы нет потомков, цикл Пока просто не сделает ни одного шага, и вложенный вызов завершится сам.
Глубина вложенных вызовов ограничена размером стека. Бесконечная или слишком глубокая рекурсия приводит к ошибке переполнения стека и аварийному завершению. Всегда закладывайте условие выхода и, если дерево может быть очень глубоким, подумайте о замене рекурсии обычным циклом с собственным списком-очередью.
Если рекурсия в вашей задаче рискует уйти слишком глубоко, передавайте в процедуру счётчик уровня и прерывайте обход по разумному пределу — это безопаснее, чем ловить аварийное завершение.
Асинхронные вызовы и диалоги
На тонком и веб-клиенте 8.3 модальные окна (Вопрос, Предупреждение, обычный ввод) отключены. Поэтому вызов, после которого нужно дождаться ответа пользователя, строят асинхронно: вы инициируете диалог, а продолжение выносят в отдельную процедуру-обработчик.
Первый способ — через ОписаниеОповещения. Указываете имя процедуры, которую платформа вызовет после закрытия диалога:
&НаКлиенте
Процедура УдалитьСтроку(Команда)
Оповещение = Новый ОписаниеОповещения("ПослеОтветаНаВопрос", ЭтотОбъект);
ПоказатьВопрос(Оповещение, "Удалить выбранную строку?", РежимДиалогаВопрос.ДаНет);
КонецПроцедуры
&НаКлиенте
Процедура ПослеОтветаНаВопрос(Результат, ДополнительныеПараметры) Экспорт
Если Результат = КодВозвратаДиалога.Да Тогда
// удаляем строку
КонецЕсли;
КонецПроцедуры
Процедуру-обработчик, на которую ссылается ОписаниеОповещения, обязательно объявляют с Экспорт.
Второй способ короче и доступен в современных релизах платформы (появились в версии 8.3.18) — директива Асинх и оператор Ждать. Асинхронный вызов пишется почти как обычный, без разрыва на две процедуры:
&НаКлиенте
Асинх Процедура УдалитьСтрокуАсинх(Команда)
Ответ = Ждать ВопросАсинх("Удалить строку?", РежимДиалогаВопрос.ДаНет);
Если Ответ = КодВозвратаДиалога.Да Тогда
// удаляем строку
КонецЕсли;
КонецПроцедуры
У асинхронных методов суффикс «Асинх» (ВопросАсинх, ПредупреждениеАсинх), Ждать можно писать только внутри процедуры или функции с директивой Асинх. Диалоги — функция интерфейса, поэтому такие вызовы живут только на клиенте.
Частые ошибки при вызове процедур
Самые распространённые сообщения платформы и их причины:
- 🔸 «Процедура не обнаружена (ИмяПроцедуры)» — у процедуры в другом модуле нет
Экспорт, либо вызов идёт не из того модуля, либо опечатка в имени. Проверьте признак экспорта и имя модуля. - 🔸 «Обращение к процедуре как к функции» — вы пытаетесь присвоить результат процедуре, которая ничего не возвращает. Нужна функция с
Возврат. - 🔸 «Недостаточно фактических параметров» — вызвали процедуру без обязательного аргумента. Либо передайте его, либо задайте параметру значение по умолчанию.
- 🔸 «Слишком много фактических параметров» — в вызове больше аргументов, чем объявлено. Сверьтесь со строкой объявления.
- 🔸 Прямой вызов серверной процедуры из клиентского кода без серверного метода формы — платформа не скомпилирует такой переход.
Когда параметров становится больше пяти-шести, передавайте их одной Структурой. Код вызова станет читаемее, а добавить новый параметр можно будет, не трогая все места вызова.
Отладка: как увидеть, что и откуда вызывается
Когда непонятно, почему вызов не срабатывает или приходит не то значение, помогает отладчик конфигуратора. Сначала разрешите отладку: в конфигураторе Сервис → Параметры → Запуск 1С:Предприятия, вкладка «Дополнительные», установите режим разрешения отладки.
Дальше — стандартный набор:
- Поставьте точку останова на строке вызова — клавиша
F9. - Запустите в режиме отладки. Когда выполнение остановится, идите по коду:
F11— шаг внутрь процедуры (зайти в вызываемую),F10— шаг через (не заходя внутрь),Shift+F11— выйти из текущей процедуры,F5— продолжить. - Откройте стек вызовов (
Ctrl+Alt+C) — он покажет всю цепочку: кто кого вызвал и на каком уровне вы сейчас находитесь. - Значения переменных смотрите в табло или наведением мыши; разовое выражение считайте через «Вычислить выражение» (
Shift+F9).
Стек вызовов особенно полезен при рекурсии: по нему сразу видно глубину вложенности и значения параметров на каждом уровне.
Частые вопросы
Чем процедура отличается от функции при вызове?
Возврат и вызывается внутри выражения (Х = Функция();). Процедура значение не возвращает, её вызывают отдельным оператором (Процедура();).Можно ли вызвать процедуру формы из общего модуля?
Сколько параметров можно передать в процедуру?
Структуру. Так и вызов читается лучше, и добавлять параметры проще.Почему пишет «Процедура не обнаружена», хотя она есть в коде?
Экспорт; вызов идёт через неверное имя модуля; клиентский код обращается к серверной процедуре напрямую. Проверьте эти три пункта по очереди.