Вызов процедуры из другой процедуры: синтаксис и примеры

· 9 мин

Содержание

Чтобы вызвать одну процедуру из другой, достаточно написать её имя со скобками и аргументами прямо в теле вызывающей процедуры: СформироватьДвижения(Отказ);. Если нужная процедура лежит в другом модуле, у неё должен стоять признак Экспорт, а вызов пишется через имя модуля: РаботаСДокументами.ЗаполнитьШапку(ЭтотОбъект);.

Вызвать процедуру из процедуры в 1С — базовая операция встроенного языка 8.3, но у неё есть нюансы: как передаются параметры, откуда видна вызываемая процедура, можно ли вызвать её саму из себя и что делать с серверным и клиентским контекстом. Разберём по порядку, с примерами кода, которые решают задачу целиком.

Типичная ситуация: вы пишете обработку проведения документа, часть логики повторяется в нескольких местах, и её хочется вынести в отдельную процедуру, чтобы не копировать код. Дальше — как это сделать правильно.

Как устроен вызов: процедура, функция, экспорт

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

Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    СформироватьДвижения(Отказ);
    ПроверитьОстатки(Отказ);
КонецПроцедуры

Процедура СформироватьДвижения(Отказ)
    // логика формирования движений
КонецПроцедуры

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

Итого = ПосчитатьСумму(ТаблицаТоваров);

Функция ПосчитатьСумму(Таблица)
    Сумма = 0;
    Для Каждого Строка Из Таблица Цикл
        Сумма = Сумма + Строка.Сумма;
    КонецЦикла;
    Возврат Сумма;
КонецФункции

Пока обе подпрограммы лежат в одном модуле, вызов работает без лишних слов. Чтобы обратиться к процедуре из другого модуля (общего модуля, модуля объекта, модуля формы), у неё должен стоять Экспорт в строке объявления:

// В общем модуле РаботаСДокументами
Процедура ЗаполнитьШапку(Документ) Экспорт
    Документ.Дата = ТекущаяДата();
КонецПроцедуры

Вызов из модуля объекта документа — через имя общего модуля:

РаботаСДокументами.ЗаполнитьШапку(ЭтотОбъект);

Если процедура нужна только внутри своего модуля, Экспорт не ставьте: чем меньше публичных методов, тем проще сопровождать код.

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

Передача параметров: по значению и по ссылке

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

Процедура Пример()
    Количество = 10;
    Увеличить(Количество);
    Сообщить(Строка(Количество)); // выведет 11
КонецПроцедуры

Процедура Увеличить(Параметр)
    Параметр = Параметр + 1;
КонецПроцедуры

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

Процедура Увеличить(Знач Параметр)
    Параметр = Параметр + 1; // на вызывающий код не влияет
КонецПроцедуры

Здесь важный подвох, на котором спотыкаются даже опытные разработчики. Знач копирует только ссылку, а не сам объект. Для ссылочных типов — Массив, Структура, ТаблицаЗначений, Соответствие — обе переменные указывают на один и тот же объект в памяти. Поэтому изменение содержимого видно снаружи, несмотря на Знач:

Процедура ДобавитьЭлемент(Знач Список)
    Список.Добавить("Новый"); // исходный массив тоже изменится
КонецПроцедуры

Знач защищает только от переприсваивания самой переменной (Список = Новый Массив), но не от вызова её методов. Если нужна настоящая независимая копия таблицы или структуры, делайте её явно — например, через Скопировать() у таблицы значений.

Параметрам можно задать значение по умолчанию — тогда при вызове их разрешается не указывать:

Процедура Записать(Текст, ВЖурнал = Ложь)
    // если второй аргумент не передан, ВЖурнал = Ложь
КонецПроцедуры

Записать("Готово");          // ВЖурнал = Ложь
Записать("Ошибка", Истина);  // ВЖурнал = Истина

Значением по умолчанию может быть только литерал (число, строка, Истина, Ложь, Неопределено), но не выражение или вызов функции.

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

// Выбрать(<Родитель>, <Владелец>, <Отбор>, <Порядок>)
Выборка = Справочники.Номенклатура.Выбрать(Родитель, , Отбор);

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

Область видимости и контекст клиент–сервер

Откуда видна вызываемая процедура, зависит от того, где она объявлена:

  • 🔹 Локальные переменные процедуры живут только внутри неё. Снаружи их нет.
  • 🔹 Переменные модуля (Перем в начале модуля) доступны всем процедурам этого модуля.
  • 🔹 Экспортные процедуры общего модуля доступны отовсюду, где этот модуль виден по его флагам (Клиент / Сервер / Вызов сервера).

Вторая ось — контекст исполнения. В управляемом приложении код работает либо на клиенте, либо на сервере, и это задаётся директивами компиляции перед процедурой:

ДирективаГде исполняется
&НаКлиентена клиенте, есть доступ к форме и диалогам
&НаСерверена сервере, с контекстом формы
&НаСервереБезКонтекстана сервере, без данных формы (быстрее)
&НаКлиентеНаСервереБезКонтекстадоступна и там, и там

Правило вызовов между контекстами:

  • Клиентская процедура может вызвать серверную (&НаСервере или &НаСервереБезКонтекста) — это серверный вызов.
  • Серверная процедура не может напрямую вызвать клиентскую: с сервера нет доступа к форме и интерфейсу.

Серверный вызов — это обращение к серверу по сети. Не вызывайте серверные процедуры в цикле по одной записи: соберите данные и передайте их одним вызовом, иначе форма будет заметно тормозить.

Рекурсия: когда процедура вызывает сама себя

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

Классический пример — факториал:

Функция Факториал(Н)
    Если Н <= 1 Тогда
        Возврат 1;
    КонецЕсли;
    Возврат Н * Факториал(Н - 1);
КонецФункции

Факториал(3) развернётся в 3 * 2 * 1 = 6. Главное в любой рекурсии — условие выхода. Здесь это Если Н <= 1. Без него вызовы не остановятся.

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

Процедура ОбойтиГруппу(Родитель)
    Выборка = Справочники.Номенклатура.Выбрать(Родитель);
    Пока Выборка.Следующий() Цикл
        Если Выборка.ЭтоГруппа Тогда
            ОбойтиГруппу(Выборка.Ссылка); // рекурсия: спускаемся на уровень ниже
        Иначе
            // обрабатываем элемент
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

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

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

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

Асинхронные вызовы и диалоги

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

Первый способ — через ОписаниеОповещения. Указываете имя процедуры, которую платформа вызовет после закрытия диалога:

&НаКлиенте
Процедура УдалитьСтроку(Команда)
    Оповещение = Новый ОписаниеОповещения("ПослеОтветаНаВопрос", ЭтотОбъект);
    ПоказатьВопрос(Оповещение, "Удалить выбранную строку?", РежимДиалогаВопрос.ДаНет);
КонецПроцедуры

&НаКлиенте
Процедура ПослеОтветаНаВопрос(Результат, ДополнительныеПараметры) Экспорт
    Если Результат = КодВозвратаДиалога.Да Тогда
        // удаляем строку
    КонецЕсли;
КонецПроцедуры

Процедуру-обработчик, на которую ссылается ОписаниеОповещения, обязательно объявляют с Экспорт.

Второй способ короче и доступен в современных релизах платформы (появились в версии 8.3.18) — директива Асинх и оператор Ждать. Асинхронный вызов пишется почти как обычный, без разрыва на две процедуры:

&НаКлиенте
Асинх Процедура УдалитьСтрокуАсинх(Команда)
    Ответ = Ждать ВопросАсинх("Удалить строку?", РежимДиалогаВопрос.ДаНет);
    Если Ответ = КодВозвратаДиалога.Да Тогда
        // удаляем строку
    КонецЕсли;
КонецПроцедуры

У асинхронных методов суффикс «Асинх» (ВопросАсинх, ПредупреждениеАсинх), Ждать можно писать только внутри процедуры или функции с директивой Асинх. Диалоги — функция интерфейса, поэтому такие вызовы живут только на клиенте.

Проверьте себя
Что делает ключевое слово `Знач` перед параметром?
Чтобы функцию из общего модуля можно было вызвать из модуля формы, её объявляют...
Можно ли с сервера напрямую вызвать клиентскую процедуру?

Частые ошибки при вызове процедур

Самые распространённые сообщения платформы и их причины:

  • 🔸 «Процедура не обнаружена (ИмяПроцедуры)» — у процедуры в другом модуле нет Экспорт, либо вызов идёт не из того модуля, либо опечатка в имени. Проверьте признак экспорта и имя модуля.
  • 🔸 «Обращение к процедуре как к функции» — вы пытаетесь присвоить результат процедуре, которая ничего не возвращает. Нужна функция с Возврат.
  • 🔸 «Недостаточно фактических параметров» — вызвали процедуру без обязательного аргумента. Либо передайте его, либо задайте параметру значение по умолчанию.
  • 🔸 «Слишком много фактических параметров» — в вызове больше аргументов, чем объявлено. Сверьтесь со строкой объявления.
  • 🔸 Прямой вызов серверной процедуры из клиентского кода без серверного метода формы — платформа не скомпилирует такой переход.

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

Отладка: как увидеть, что и откуда вызывается

Когда непонятно, почему вызов не срабатывает или приходит не то значение, помогает отладчик конфигуратора. Сначала разрешите отладку: в конфигураторе Сервис → Параметры → Запуск 1С:Предприятия, вкладка «Дополнительные», установите режим разрешения отладки.

Дальше — стандартный набор:

  1. Поставьте точку останова на строке вызова — клавиша F9.
  2. Запустите в режиме отладки. Когда выполнение остановится, идите по коду: F11 — шаг внутрь процедуры (зайти в вызываемую), F10 — шаг через (не заходя внутрь), Shift+F11 — выйти из текущей процедуры, F5 — продолжить.
  3. Откройте стек вызовов (Ctrl+Alt+C) — он покажет всю цепочку: кто кого вызвал и на каком уровне вы сейчас находитесь.
  4. Значения переменных смотрите в табло или наведением мыши; разовое выражение считайте через «Вычислить выражение» (Shift+F9).

Стек вызовов особенно полезен при рекурсии: по нему сразу видно глубину вложенности и значения параметров на каждом уровне.

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

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