Функции во встроенном языке 1С: как устроены, как вызываются и что возвращают
Содержание
Функция в 1С — это именованный блок кода, который выполняет действия и обязательно отдаёт результат через оператор Возврат. Процедура работает так же, но ничего не возвращает. Вся разница в этом: если результат нужен в выражении (Сумма = ПосчитатьНДС(Цена)), пишите функцию, если нужно просто «сделать» (записать документ, показать сообщение) — процедуру.
Ниже по порядку: чем функция отличается от процедуры, как её объявить, как работают параметры, что можно вернуть, где функция «видна» и как писать рекурсию. В конце — три рабочих примера, которые можно взять в свой модуль.
Функция и процедура: где проходит граница
Обе конструкции живут в модулях: общих, модулях объектов, форм, менеджеров. Вызываются по имени, принимают параметры. Отличаются результатом.
| Признак | Процедура | Функция |
|---|---|---|
| Ключевые слова | Процедура … КонецПроцедуры | Функция … КонецФункции |
| Возвращает значение | нет | да, через Возврат |
| Где можно вызвать | отдельным оператором | отдельным оператором и внутри выражения |
| Типичная задача | действие: записать, провести, вывести | вычисление: найти, посчитать, проверить |
Хороший признак «это функция»: у метода нет побочных эффектов, он только считает и отдаёт ответ. Если внутри вы пишете в базу и заодно возвращаете Истина — подумайте, не разделить ли это на два метода.
Функцию можно вызвать и как процедуру, проигнорировав результат: ЗаполнитьТаблицу(Данные);. Платформа это разрешает. Но если результат потерян, а функция не делает ничего полезного кроме расчёта, вызов бессмысленен.
Обратное не работает : у процедуры нельзя взять значение, оно не определено, и конфигуратор покажет ошибку при проверке синтаксиса.
Как объявить функцию: синтаксис и ключевые слова
Минимальная функция выглядит так:
Функция ВыделитьНДС(СуммаСНДС, Ставка = 20) Экспорт
Возврат Окр(СуммаСНДС * Ставка / (100 + Ставка), 2);
КонецФункции
Разберём по частям:
Функцияи имя. Имя — по правилам идентификаторов: буквы, цифры, подчёркивание, без пробелов, не начинается с цифры. Регистр не важен.- Список параметров в скобках, через запятую. Скобки обязательны даже при нуле параметров.
Экспорт— необязательное слово. Делает функцию доступной из других модулей. Без него функция видна только внутри своего модуля.- Тело: любые операторы,
Перемдля локальных переменных (объявляются в начале тела), условия, циклы. Возврат <выражение>— выход с результатом. Оператор можно ставить в нескольких местах, например в веткахЕсли.КонецФункции.
Проверим арифметику: из 12 000 руб. с НДС 20 % налог равен 12000 * 20 / 120 = 2000. Именно столько вернёт ВыделитьНДС(12000).
Если выполнение дошло до КонецФункции, а Возврат с выражением так и не встретился, функция вернёт Неопределено. Это самая частая причина странных «пустых» значений: забыли Иначе в условии, и один из путей остался без возврата.
Где в модуле размещать функции
Сначала идёт область объявления переменных модуля, затем процедуры и функции, и только потом — операторы основной программы (если они есть). Переставить нельзя: сборка модуля даст ошибку. В общих модулях и формах «операторов основной программы» обычно нет вовсе, так что всё ограничивается переменными и методами.
Для порядка в больших модулях методы группируют в области #Область … #КонецОбласти: программный интерфейс, служебные процедуры и функции, обработчики событий.
Область видимости: что видно внутри, а что снаружи
Тут путаются чаще всего, поэтому по пунктам.
Переменные
- Локальные — объявлены внутри функции (
Перемили просто присвоены). Живут до выхода из функции, снаружи недоступны. - Переменные модуля — объявлены в начале модуля, до первого метода. Видны всем методам этого модуля, значение сохраняется, пока жив модуль (для формы — пока она открыта).
- Глобальных переменных, доступных во всей конфигурации, в привычном смысле нет. Для общего состояния используют общие модули с повторным использованием возвращаемых значений или параметры сеанса.
Если имя локальной переменной совпало с именем переменной модуля, внутри функции «побеждает» локальная. Лишняя причина не называть переменные одной буквой.
Вызов между модулями
- Внутри одного модуля вызываем по имени:
ВыделитьНДС(Сумма). - Из другого модуля функция должна быть с
Экспорт, а вызов идёт через имя общего модуля:РаботаСНалогами.ВыделитьНДС(Сумма). - Функции модуля объекта или формы вызываются через ссылку на объект, и снова нужен
Экспорт.
Клиент и сервер
В управляемых формах у каждого метода есть директива компиляции, и она меняет, где код выполняется:
| Директива | Где работает | Доступ к данным формы |
|---|---|---|
&НаКлиенте | клиент | да |
&НаСервере | сервер | да, форма передаётся целиком |
&НаСервереБезКонтекста | сервер | нет, только параметры |
Вызов серверной функции с клиента — это сетевой обмен. &НаСервереБезКонтекста дешевле обычного &НаСервере: контекст формы не пересылается туда и обратно. Если функции не нужны реквизиты формы, берите именно эту директиву.
Результат серверной функции приходит на клиент только если он «сериализуем»: числа, строки, даты, ссылки, структуры, массивы, таблицы значений. Объект запроса или курсор через границу не пройдут.
Параметры: по ссылке, по значению и необязательные
Здесь у платформы своя логика, отличная от «привычных» языков.
Передача по ссылке — по умолчанию
Параметр без модификатора — это ссылка на переменную вызывающего кода. Присвоили значение внутри функции — изменилась и исходная переменная.
Функция Увеличить(Число)
Число = Число + 1;
Возврат Число;
КонецФункции
А = 5;
Б = Увеличить(А);
// А = 6, Б = 6
Передача по значению — слово Знач
Со Знач функция получает копию, исходная переменная не меняется:
Функция УвеличитьБезВлияния(Знач Число)
Число = Число + 1;
Возврат Число;
КонецФункции
А = 5;
Б = УвеличитьБезВлияния(А);
// А = 5, Б = 6
Правило практика: если не собираетесь возвращать данные через параметр, пишите Знач. Читать такой код легче — сразу видно, что аргумент «неприкосновенный».
Есть тонкость. Для объектов (структура, массив, соответствие, таблица значений) Знач копирует ссылку, а не содержимое. Добавили элемент в массив внутри функции — он появится и снаружи. Но если параметру целиком присвоить новый массив, исходная переменная останется прежней. Хотите изолировать данные — копируйте явно, например Новый Массив(МассивИсточник).
Необязательные параметры
Значение по умолчанию задаётся прямо в объявлении, как у Ставка = 20 выше. Правила:
- Необязательные идут в конце списка, обязательные — перед ними.
- Пропустить параметр в середине можно, оставив пустое место между запятыми:
Расчёт(Сумма, , Дата). Пропущенный параметр получит значение по умолчанию, а если его нет —Неопределено. - Значением по умолчанию может быть только константа: число, строка, дата,
Истина/Ложь,Неопределено,Null. Выражение или вызов функции не подойдут.
Когда параметров становится много
Больше четырёх-пяти параметров — сигнал, что пора передавать структуру:
Параметры = Новый Структура;
Параметры.Вставить("Организация", Организация);
Параметры.Вставить("Период", Период);
Параметры.Вставить("УчитыватьСкидки", Истина);
Результат = РасчётЦены(Параметры);
Порядок аргументов больше не нужно держать в голове, а новый параметр добавляется без правки всех вызовов.
Что функция может вернуть
Тип результата в объявлении не указывается. Функция вернёт то, что стоит после Возврат, и вызывающий код должен быть готов к этому типу.
Что обычно возвращают:
- 🔢 Число, строку, дату, булево — простые вычисления и проверки.
- 🔗 Ссылку на элемент — найти контрагента по ИНН и отдать
СправочникСсылка.Контрагенты. - 📦 Структуру — когда результатов несколько: сумма, количество, признак ошибки.
- 📋 Массив или таблицу значений — выборка списка, результат запроса.
- ❔
Неопределено— «ничего не найдено». Договорённость обязательно описывайте в комментарии.
Несколько значений за один вызов
Языковой конструкции «вернуть пару» в 1С нет. Есть три приёма:
- Вернуть структуру:
Возврат Новый Структура("Сумма, НДС", 12000, 2000); - Использовать параметры по ссылке как «выходные»: вызывающий код передаёт переменные, функция их заполняет.
- Вернуть основное значение, а дополнительное отдать через параметр-структуру.
Первый вариант чище и понятнее, второй сегодня считается устаревшим стилем.
Раннее завершение
Возврат останавливает функцию сразу, остальной код не выполняется. На этом строится приём «защитных проверок» в начале:
Функция НайтиКонтрагентаПоИНН(ИНН) Экспорт
Если ПустаяСтрока(ИНН) Тогда
Возврат Справочники.Контрагенты.ПустаяСсылка();
КонецЕсли;
// дальше основной запрос
КонецФункции
Так код остаётся плоским, без глубокой вложенности Если.
Рекурсия и скорость: когда функция вызывает сама себя
Рекурсия в 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С вернуть из функции сразу несколько значений?
Чем `Возврат` в процедуре отличается от `Возврат` в функции?
Возврат без выражения просто прерывает выполнение. В функции после него нужно выражение с результатом.Нужно ли ставить `Экспорт` на каждую функцию?
Что произойдёт, если вызвать функцию без присвоения результата?
Можно ли задать у параметра тип данных?
ТипЗнч().