Вызов функции внутри процедуры на встроенном языке 1С

· 9 мин

Содержание

Чтобы вызвать функцию из процедуры, напишите её имя со скобками как обычное выражение и присвойте результат переменной: Итог = ВычислитьНДС(Сумма, 20);. Процедура сама ничего не возвращает, поэтому результат работы она берёт именно у функции — внутри своего тела, в нужный момент.

Разберём это по шагам: чем отличается функция от процедуры, как вызвать функцию в одном модуле и из другого, что происходит с параметрами, можно ли передать функцию «как аргумент» и как ловить ошибки при вызове. Все примеры — на встроенном языке 1С 8.3, конфигурация роли не играет: синтаксис один и тот же в Бухгалтерии, ЗУП и УТ.

Процедура и функция: одно отличие, которое всё решает

Разница всего одна, но она определяет, как вы будете их вызывать.

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

Объявляются они почти одинаково, различаются ключевые слова:

// Процедура — делает, но не отдаёт результат
Процедура ЗаписатьВЖурнал(Текст)
    ЗаписьЖурналаРегистрации("Расчёт", , , , Текст);
КонецПроцедуры

// Функция — вычисляет и отдаёт значение
Функция ВычислитьНДС(Сумма, Ставка)
    Возврат Сумма * Ставка / 100;
КонецФункции

Отсюда правило вызова: функцию вы ставите туда, где нужен результат (Налог = ВычислитьНДС(...)), а процедуру вызываете как самостоятельное действие (ЗаписатьВЖурнал("Готово");).

Технически функцию можно вызвать и как процедуру — просто проигнорировать возвращённое значение: написать ВычислитьНДС(Сумма, 20); без присваивания. Платформа не ругается. Но так теряется смысл функции, поэтому делают это редко.

Базовый синтаксис: вызов функции в теле процедуры

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

Процедура РассчитатьДокумент()

    Сумма = 100;
    Ставка = 20;

    // вызов функции — результат сразу в переменную
    Налог = ВычислитьНДС(Сумма, Ставка);

    // результат можно использовать в другом выражении
    ИтогСНДС = Сумма + Налог;

    Сообщить("НДС: " + Налог + ", итого: " + ИтогСНДС);

КонецПроцедуры

Функция ВычислитьНДС(Сумма, Ставка)
    Возврат Сумма * Ставка / 100;
КонецФункции

В примере 100 * 20 / 100 даёт 20, итог — 120. Функцию можно вызывать прямо внутри другого выражения, не заводя лишнюю переменную:

Сообщить("Итого: " + (Сумма + ВычислитьНДС(Сумма, 20)));

Порядок объявления в модуле значения не имеет: процедура может стоять выше функции, которую вызывает, — платформа компилирует модуль целиком. Важно только, чтобы обе были в области видимости вызова.

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

Вызов функции из другого модуля

Если функция лежит в соседнем модуле, одного имени мало — нужен префикс и ключевое слово Экспорт у самой функции. Без Экспорт функция видна только внутри своего модуля.

Общий модуль — самый частый случай. Пусть есть общий модуль РаботаСНДС с экспортной функцией:

// В общем модуле "РаботаСНДС"
Функция ВычислитьНДС(Сумма, Ставка) Экспорт
    Возврат Сумма * Ставка / 100;
КонецФункции

Вызов из любой процедуры — через имя модуля как префикс:

Налог = РаботаСНДС.ВычислитьНДС(Сумма, 20);

Чтобы вызов прошёл, у общего модуля должны быть выставлены флаги контекста (Клиент / Сервер / Внешнее соединение) под то место, откуда вы вызываете. Серверную функцию из клиентского кода вызвать можно, обратное — нет.

Модуль менеджера объекта — функции справочников, документов, регистров. Обращение идёт через менеджер:

Данные = Справочники.Номенклатура.ПолучитьОстаток(Товар);

Модуль объекта — функцию вызывают через конкретную ссылку или объект:

ОбъектДокумента = Документ.ПолучитьОбъект();
Результат = ОбъектДокумента.ПроверитьЗаполнение();

Коротко, куда класть экспортную функцию:

Где лежит функцияКак вызватьКогда использовать
Общий модульИмяМодуля.Функция()Логика, нужная в нескольких местах
Модуль менеджераСправочники.Имя.Функция()Операции над видом объектов
Модуль объектаОбъект.Функция()Логика конкретного экземпляра

Параметры: по значению, по ссылке и значения по умолчанию

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

// Знач — функция работает с копией, оригинал не меняется
Функция Удвоить(Знач Число)
    Число = Число * 2;
    Возврат Число;
КонецФункции

Процедура Проверить()
    Х = 5;
    Y = Удвоить(Х);
    Сообщить("" + Х + " / " + Y); // выведет 5 / 10
КонецПроцедуры

Без Знач переменная Х тоже стала бы равна 10 — функция изменила бы её напрямую. Для функций всегда указывайте Знач, если не собираетесь намеренно менять входной параметр: это убирает незаметные побочные эффекты.

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

Функция ОкруглитьСумму(Сумма, Разрядность = 2)
    Возврат Окр(Сумма, Разрядность);
КонецФункции

// оба вызова корректны
А = ОкруглитьСумму(10.457);      // 10.46
Б = ОкруглитьСумму(10.457, 0);   // 10

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

Как передать функцию «как параметр»

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

1. Передать имя строкой и вызвать через Вычислить. Подходит, когда вызываемая функция видна в текущем модуле.

Процедура ПрименитьОперацию(ИмяФункции, А, Б)
    Выражение = ИмяФункции + "(" + А + ", " + Б + ")";
    Результат = Вычислить(Выражение);
    Сообщить(Результат);
КонецПроцедуры

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

2. Оповещение ОписаниеОповещения — штатный механизм обратного вызова (callback) в управляемых формах, в том числе для асинхронных операций.

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

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

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

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

Обработка ошибок при вызове функции

Если функция может «упасть» — деление на ноль, разбор некорректной строки, обращение к несуществующему объекту — оберните вызов в Попытка … Исключение … КонецПопытки.

Попытка
    Результат = РазобратьЧисло(ВходнаяСтрока);
Исключение
    Результат = 0;
    ЗаписьЖурналаРегистрации("Разбор", УровеньЖурналаРегистрации.Ошибка,
        , , ОписаниеОшибки());
КонецПопытки;

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

Если же ошибку должна «поднимать» сама функция — когда входные данные недопустимы, — используйте ВызватьИсключение:

Функция РазделитьСумму(Сумма, Делитель)
    Если Делитель = 0 Тогда
        ВызватьИсключение "Делитель не может быть нулём";
    КонецЕсли;
    Возврат Сумма / Делитель;
КонецФункции

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

Когда выносить код в функцию, а когда не стоит

Вынос логики в отдельную функцию почти всегда оправдан, но не превращайте это в самоцель.

Выносите в функцию, когда:

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

Оставьте «по месту», когда:

  • 🔸 это одна простая строка, которая нигде больше не встретится;
  • 🔸 вынос потребует тащить в функцию десяток параметров — связность выше, чем польза.

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

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

Примеры вызова: от простого расчёта до рекурсии

Соберём разобранное в три примера нарастающей сложности — их можно вставить в модуль и запустить.

Простой: функция в выражении. Процедура считает строку табличной части, вызывая функцию на каждой итерации.

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

Функция вызывается внутри цикла, но к базе не обращается — считает в памяти, поэтому цикл остаётся быстрым.

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

Функция РазложитьСумму(Сумма, Ставка)
    Результат = Новый Структура("БезНДС, НДС, Всего");
    Результат.НДС    = Сумма * Ставка / (100 + Ставка);
    Результат.БезНДС = Сумма - Результат.НДС;
    Результат.Всего  = Сумма;
    Возврат Результат;
КонецФункции

Процедура Показать()
    Итог = РазложитьСумму(120, 20);
    Сообщить("Без НДС: " + Итог.БезНДС + ", НДС: " + Итог.НДС);
КонецПроцедуры

Для суммы 120 со ставкой 20% получим НДС 120 * 20 / 120 = 20 и сумму без НДС 100.

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

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

Вызов Факториал(5) развернётся в 5 * 4 * 3 * 2 * 1 = 120. У любой рекурсивной функции обязательно должно быть условие выхода — ветка, где она возвращает результат без обращения к себе. Иначе стек вызовов переполнится и платформа аварийно завершит модуль.

Проверьте себя
Чем функция отличается от процедуры в 1С?
Что делает ключевое слово Знач перед параметром?
Как вызвать экспортную функцию ВычислитьНДС из общего модуля РаботаСНДС?

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

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