Функции во встроенном языке 1С: как устроены, как вызываются и что возвращают

· 10 мин

Содержание

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

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

Функция и процедура: где проходит граница

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

ПризнакПроцедураФункция
Ключевые словаПроцедура … КонецПроцедурыФункция … КонецФункции
Возвращает значениенетда, через Возврат
Где можно вызватьотдельным операторомотдельным оператором и внутри выражения
Типичная задачадействие: записать, провести, вывестивычисление: найти, посчитать, проверить

Хороший признак «это функция»: у метода нет побочных эффектов, он только считает и отдаёт ответ. Если внутри вы пишете в базу и заодно возвращаете Истина — подумайте, не разделить ли это на два метода.

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

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

Как объявить функцию: синтаксис и ключевые слова

Минимальная функция выглядит так:

Функция ВыделитьНДС(СуммаСНДС, Ставка = 20) Экспорт
	Возврат Окр(СуммаСНДС * Ставка / (100 + Ставка), 2);
КонецФункции

Разберём по частям:

  1. Функция и имя. Имя — по правилам идентификаторов: буквы, цифры, подчёркивание, без пробелов, не начинается с цифры. Регистр не важен.
  2. Список параметров в скобках, через запятую. Скобки обязательны даже при нуле параметров.
  3. Экспорт — необязательное слово. Делает функцию доступной из других модулей. Без него функция видна только внутри своего модуля.
  4. Тело: любые операторы, Перем для локальных переменных (объявляются в начале тела), условия, циклы.
  5. Возврат <выражение> — выход с результатом. Оператор можно ставить в нескольких местах, например в ветках Если.
  6. КонецФункции.

Проверим арифметику: из 12 000 руб. с НДС 20 % налог равен 12000 * 20 / 120 = 2000. Именно столько вернёт ВыделитьНДС(12000).

Если выполнение дошло до КонецФункции, а Возврат с выражением так и не встретился, функция вернёт Неопределено. Это самая частая причина странных «пустых» значений: забыли Иначе в условии, и один из путей остался без возврата.

Где в модуле размещать функции

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

Для порядка в больших модулях методы группируют в области #Область … #КонецОбласти: программный интерфейс, служебные процедуры и функции, обработчики событий.

Область видимости: что видно внутри, а что снаружи

Тут путаются чаще всего, поэтому по пунктам.

Переменные

  • Локальные — объявлены внутри функции (Перем или просто присвоены). Живут до выхода из функции, снаружи недоступны.
  • Переменные модуля — объявлены в начале модуля, до первого метода. Видны всем методам этого модуля, значение сохраняется, пока жив модуль (для формы — пока она открыта).
  • Глобальных переменных, доступных во всей конфигурации, в привычном смысле нет. Для общего состояния используют общие модули с повторным использованием возвращаемых значений или параметры сеанса.

Если имя локальной переменной совпало с именем переменной модуля, внутри функции «побеждает» локальная. Лишняя причина не называть переменные одной буквой.

Вызов между модулями

  • Внутри одного модуля вызываем по имени: ВыделитьНДС(Сумма).
  • Из другого модуля функция должна быть с Экспорт, а вызов идёт через имя общего модуля: РаботаСНалогами.ВыделитьНДС(Сумма).
  • Функции модуля объекта или формы вызываются через ссылку на объект, и снова нужен Экспорт.

Клиент и сервер

В управляемых формах у каждого метода есть директива компиляции, и она меняет, где код выполняется:

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

Вызов серверной функции с клиента — это сетевой обмен. &НаСервереБезКонтекста дешевле обычного &НаСервере: контекст формы не пересылается туда и обратно. Если функции не нужны реквизиты формы, берите именно эту директиву.

Результат серверной функции приходит на клиент только если он «сериализуем»: числа, строки, даты, ссылки, структуры, массивы, таблицы значений. Объект запроса или курсор через границу не пройдут.

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

Здесь у платформы своя логика, отличная от «привычных» языков.

Передача по ссылке — по умолчанию

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

Функция Увеличить(Число)
	Число = Число + 1;
	Возврат Число;
КонецФункции

А = 5;
Б = Увеличить(А);
// А = 6, Б = 6

Передача по значению — слово Знач

Со Знач функция получает копию, исходная переменная не меняется:

Функция УвеличитьБезВлияния(Знач Число)
	Число = Число + 1;
	Возврат Число;
КонецФункции

А = 5;
Б = УвеличитьБезВлияния(А);
// А = 5, Б = 6

Правило практика: если не собираетесь возвращать данные через параметр, пишите Знач. Читать такой код легче — сразу видно, что аргумент «неприкосновенный».

Есть тонкость. Для объектов (структура, массив, соответствие, таблица значений) Знач копирует ссылку, а не содержимое. Добавили элемент в массив внутри функции — он появится и снаружи. Но если параметру целиком присвоить новый массив, исходная переменная останется прежней. Хотите изолировать данные — копируйте явно, например Новый Массив(МассивИсточник).

Необязательные параметры

Значение по умолчанию задаётся прямо в объявлении, как у Ставка = 20 выше. Правила:

  1. Необязательные идут в конце списка, обязательные — перед ними.
  2. Пропустить параметр в середине можно, оставив пустое место между запятыми: Расчёт(Сумма, , Дата). Пропущенный параметр получит значение по умолчанию, а если его нет — Неопределено.
  3. Значением по умолчанию может быть только константа: число, строка, дата, Истина/Ложь, Неопределено, Null. Выражение или вызов функции не подойдут.

Когда параметров становится много

Больше четырёх-пяти параметров — сигнал, что пора передавать структуру:

Параметры = Новый Структура;
Параметры.Вставить("Организация", Организация);
Параметры.Вставить("Период", Период);
Параметры.Вставить("УчитыватьСкидки", Истина);
Результат = РасчётЦены(Параметры);

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

Что функция может вернуть

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

Что обычно возвращают:

  • 🔢 Число, строку, дату, булево — простые вычисления и проверки.
  • 🔗 Ссылку на элемент — найти контрагента по ИНН и отдать СправочникСсылка.Контрагенты.
  • 📦 Структуру — когда результатов несколько: сумма, количество, признак ошибки.
  • 📋 Массив или таблицу значений — выборка списка, результат запроса.
  • ❔ Неопределено — «ничего не найдено». Договорённость обязательно описывайте в комментарии.

Несколько значений за один вызов

Языковой конструкции «вернуть пару» в 1С нет. Есть три приёма:

  1. Вернуть структуру: Возврат Новый Структура("Сумма, НДС", 12000, 2000);
  2. Использовать параметры по ссылке как «выходные»: вызывающий код передаёт переменные, функция их заполняет.
  3. Вернуть основное значение, а дополнительное отдать через параметр-структуру.

Первый вариант чище и понятнее, второй сегодня считается устаревшим стилем.

Раннее завершение

Возврат останавливает функцию сразу, остальной код не выполняется. На этом строится приём «защитных проверок» в начале:

Функция НайтиКонтрагентаПоИНН(ИНН) Экспорт
	Если ПустаяСтрока(ИНН) Тогда
		Возврат Справочники.Контрагенты.ПустаяСсылка();
	КонецЕсли;
	// дальше основной запрос
КонецФункции

Так код остаётся плоским, без глубокой вложенности Если.

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

Рекурсия в 1С работает: функция может вызвать себя же. Классический пример — факториал:

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

Факториал(5) даёт 5 · 4 · 3 · 2 · 1 = 120.

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

Ограничения

  • У каждой рекурсивной функции обязательно есть условие выхода. Без него вызовы бесконечны, и сеанс завершится ошибкой переполнения стека вызовов.
  • Глубина вложенных вызовов ограничена платформой. Точное число зависит от версии и режима работы, поэтому на глубокие цепочки (тысячи уровней) не рассчитывайте.
  • Каждый вызов тратит память и время на передачу параметров.

Что быстрее: рекурсия или цикл

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

И ещё о скорости в целом, для любой функции:

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

В иерархических справочниках для выборки всего поддерева запросом есть оператор ИЕРАРХИЯ/В ИЕРАРХИИ. Он обычно эффективнее рекурсивных обращений к базе.

Три рабочих примера для копирования

Пример 1. Безопасное деление

Функция Разделить(Делимое, Делитель, ЗначениеПриОшибке = 0)
	Если Делитель = 0 Тогда
		Возврат ЗначениеПриОшибке;
	КонецЕсли;
	Возврат Делимое / Делитель;
КонецФункции

Разделить(10, 4) вернёт 2.5, а Разделить(10, 0) — 0 вместо ошибки деления на ноль. Применяйте, когда ноль в знаменателе — штатная ситуация (например, средняя цена при нулевом количестве).

Пример 2. Поиск значения с понятным «не найдено»

Функция ПолучитьЦенуТовара(Товар, Знач НаДату = Неопределено) Экспорт
	Если НаДату = Неопределено Тогда
		НаДату = ТекущаяДата();
	КонецЕсли;
	
	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ ПЕРВЫЕ 1
	|	Цены.Цена КАК Цена
	|ИЗ
	|	РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, Номенклатура = &Товар) КАК Цены";
	Запрос.УстановитьПараметр("Дата", НаДату);
	Запрос.УстановитьПараметр("Товар", Товар);
	
	Выборка = Запрос.Выполнить().Выбрать();
	Если Выборка.Следующий() Тогда
		Возврат Выборка.Цена;
	КонецЕсли;
	Возврат Неопределено;
КонецФункции

Имена регистра и полей подставьте из своей конфигурации. Сам приём не меняется: необязательная дата, запрос, Неопределено как честный ответ «цены нет».

Пример 3. Рекурсивный обход группы справочника

Функция СобратьПотомков(Группа, Результат = Неопределено) Экспорт
	Если Результат = Неопределено Тогда
		Результат = Новый Массив;
	КонецЕсли;
	
	Выборка = Справочники.Номенклатура.Выбрать(Группа);
	Пока Выборка.Следующий() Цикл
		Результат.Добавить(Выборка.Ссылка);
		Если Выборка.ЭтоГруппа Тогда
			СобратьПотомков(Выборка.Ссылка, Результат);
		КонецЕсли;
	КонецЦикла;
	Возврат Результат;
КонецФункции

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

Типичные ошибки при работе с функциями

Не повторяя то, что уже сказано выше, соберём симптомы, которые встречаются на практике:

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

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

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