Как устроена функция во встроенном языке 1С и где на ней ошибаются

· 10 мин

Содержание

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

Ниже — как работают функции в 1С на практике: синтаксис, параметры, возвращаемые значения, директивы &НаСервере и &НаКлиенте, рекурсия и ошибки программирования, которые чаще всего всплывают на ревью. Примеры написаны для платформы 8.3 и управляемых форм.

Функция или процедура: в чём разница

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

ПризнакПроцедураФункция
Ключевые словаПроцедура … КонецПроцедурыФункция … КонецФункции
Возвратбез значения, просто выходВозврат Значение;
Можно в выражениинетда: Сумма = Цена(Товар) * 2
Можно вызвать отдельной строкойдада, результат отбрасывается

Отсюда два практических правила:

  • 🔹 Функцию разрешено вызвать как процедуру: ЗаписатьВЖурнал(Текст); — результат просто потеряется.
  • 🔹 Процедуру нельзя использовать в выражении. Модуль не скомпилируется, синтаксический контроль выдаст ошибку Использование процедуры как функции.

Когда что выбирать? Если подпрограмма вычисляет что-то — сумму, признак, ссылку, — пишите функцию. Если делает действие — записывает объект, заполняет табличную часть, выводит сообщение, — процедуру. Функция, которая и меняет базу, и что-то возвращает, допустима, но её назначение должно быть видно из имени: СоздатьДокументПоступления(), а не ПолучитьДокумент().

Синтаксис функции в 1С по частям

Полная форма объявления выглядит так:

// Возвращает сумму документа с учётом скидки.
//
// Параметры:
//  Сумма   - Число - сумма до скидки.
//  Процент - Число - процент скидки, по умолчанию 0.
//
// Возвращаемое значение:
//  Число - сумма после скидки, округлённая до копеек.
//
Функция СуммаСоСкидкой(Знач Сумма, Знач Процент = 0) Экспорт
	
	Если Процент <= 0 Тогда
		Возврат Сумма;
	КонецЕсли;
	
	Возврат Окр(Сумма * (100 - Процент) / 100, 2);
	
КонецФункции

Разберём строку за строкой:

  1. Комментарий-описание над функцией. Платформа его не требует, но по стандартам разработки 1С он обязателен для экспортных методов. EDT и конфигуратор показывают его в подсказке при вызове.
  2. Функция и имя. Имя пишут глаголом или существительным-результатом, слитно, каждое слово с заглавной буквы.
  3. Список параметров в скобках через запятую. Скобки обязательны, даже если параметров нет: Функция ТекущийПользователь().
  4. Экспорт — делает функцию видимой из других модулей. Без него она доступна только внутри своего модуля.
  5. Тело — любые операторы языка.
  6. Возврат — завершает функцию и отдаёт значение. Их может быть несколько, сработает первый достигнутый.
  7. КонецФункции — без точки с запятой.

Проверка на числах: СуммаСоСкидкой(1500, 10) вернёт 1500 * 90 / 100 = 1350. Вызов СуммаСоСкидкой(1500) вернёт 1500, потому что процент берётся по умолчанию.

Ранний Возврат в начале функции («сторожевое условие») избавляет от вложенных Если на три уровня. Проверили входные данные, вышли — дальше пишете основную логику без отступов.

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

Это главный источник неожиданных багов. Сравним два варианта:

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

А = 5;
Б = Удвоить(А);
// Б = 10, но и А теперь 10

Параметр Число передан по ссылке. Присваивание внутри функции изменило переменную А снаружи. Добавьте Знач — и А останется равной 5:

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

Как Знач ведёт себя с объектами

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

  • 🔸 Таблица.Добавить() внутри функции — строка появится и у вызывающего кода, даже с Знач.
  • 🔸 Таблица = Новый ТаблицаЗначений внутри функции — с Знач вызывающий код этого не увидит, без Знач — увидит новую пустую таблицу.

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

Значения по умолчанию

Необязательные параметры пишут в конце списка: Функция Найти(Код, Владелец = Неопределено, ТолькоАктивные = Истина). Значение по умолчанию — только литерал: число, строка, дата, Истина/Ложь, Неопределено, NULL. Выражение вроде ТекущаяДата() там не допускается.

Пропустить параметр в середине можно запятой: Найти("00-001", , Ложь).

Параметры при вызове сервера

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

СпособЧто видит вызывающий кодКогда использовать
Без Значлюбые изменения параметрафункция обязана заполнить выходной параметр
Знач + примитивничегопочти всегда
Знач + коллекцияизменения содержимого, но не заменунужно читать коллекцию, менять осознанно
Параметр по умолчанию—необязательная настройка поведения

Что возвращает функция и как вернуть несколько значений

Функция возвращает одно значение любого типа: число, строку, ссылку, коллекцию, Неопределено.

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

Когда результатов несколько, есть три рабочих способа:

  1. Структура — самый читаемый вариант:
Функция ОстатокТовара(Номенклатура, Склад)
	Результат = Новый Структура("Количество, Сумма", 0, 0);
	// ... запрос к регистру, заполнение полей
	Возврат Результат;
КонецФункции

Остаток = ОстатокТовара(Товар, ОсновнойСклад);
Если Остаток.Количество > 0 Тогда
	// ...
КонецЕсли;
  1. Выходные параметры без Знач: Функция Проверить(Документ, ТекстОшибки) возвращает Истина/Ложь, а текст ошибки кладёт в параметр. Удобно для функций-проверок.
  2. Массив — когда значения однородны: список ссылок, набор дат.

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

Где выполняется функция: директивы и область видимости

В модуле управляемой формы перед каждой функцией стоит директива компиляции. Она определяет, на какой стороне функция работает и что ей доступно.

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

&НаСервереБезКонтекста — самый дешёвый вызов сервера: на сервер не уходят данные формы. Если функции нужны только параметры, а не Объект и Элементы, выбирайте её.

Видимость переменных

  • 🔹 Локальные — объявлены внутри функции через Перем или неявно первым присваиванием. Живут до выхода из функции.
  • 🔹 Переменные модуля — Перем в начале модуля объекта или формы. Видны всем методам модуля. В общих модулях переменные модуля объявлять нельзя.
  • 🔹 Параметры — ведут себя как локальные переменные, но связаны с аргументами по правилам из раздела выше.

Экспортная функция общего модуля доступна там, где модуль скомпилирован: флажки Клиент, Сервер, Вызов сервера в свойствах модуля. Чтобы клиент мог вызвать серверную функцию общего модуля, у модуля должен стоять флажок Вызов сервера.

Рекурсия и скорость вызовов функций в 1С

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

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

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

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

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

Листьев = КоличествоЛистьев(Дерево.Строки);

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

Что реально ускоряет код

Сам вызов функции в 1С дешёвый. Медленно то, что внутри и вокруг:

  • ⚡ Запрос в цикле. Функция с запросом внутри, вызванная для 5000 строк, — это 5000 запросов. Соберите ссылки в массив и получите данные одним запросом с условием В (&Список).
  • ⚡ Вызов сервера в цикле на клиенте. Каждая итерация — сетевой обмен. Передайте на сервер весь массив одним вызовом.
  • ⚡ Обращение через точку к реквизитам ссылки. Документ.Контрагент.ИНН в цикле читает объект целиком. Значения реквизитов берите запросом.
  • ⚡ Повторное вычисление одного и того же. Для неизменных данных используйте общий модуль со свойством Повторное использование возвращаемых значений («На время вызова» или «На время сеанса»).

Функции модулей с повторным использованием возвращают закэшированный результат. Изменять его в вызывающем коде нельзя — правка испортит кэш для следующих вызовов. Возвращайте ФиксированнаяСтруктура или ФиксированныйМассив.

Асинхронные функции

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

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

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

Ошибки программирования в функциях 1С и как их избегать

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

Ещё несколько ошибок, которые синтаксический контроль не ловит:

  • ❌ Изменение параметра-коллекции «по пути». Функция ОтобратьАктивных(Список) удаляет из Список лишние элементы — и портит массив вызывающего кода. Собирайте результат в новый массив.
  • ❌ Удаление элементов в прямом цикле. При удалении в Для Каждого или прямом цикле по индексу элементы пропускаются. Идите с конца: Для Сч = 1 По Коллекция.Количество() Цикл Элемент = Коллекция[Коллекция.Количество() - Сч].
  • ❌ Склейка числа со строкой. "Строк: " + Количество при 1500 даст разделитель разрядов. Пишите "Строк: " + Формат(Количество, "ЧГ=0") или СтрШаблон("Строк: %1", Формат(Количество, "ЧГ=0")).
  • ❌ Глотание исключений. Пустой блок Исключение … КонецПопытки прячет причину. Хотя бы запишите ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()) в журнал регистрации.

Как писать функции, которые легко поддерживать

Правила, которые проверяют на ревью в командах, работающих по стандартам разработки 1С:

  • ✅ Одна функция — одна задача. Если в описании приходится писать «и», функцию стоит разделить. Ориентир — функция помещается на один экран.
  • ✅ Имя говорит о результате. ЕстьОстатокНаСкладе() возвращает булево, ОстатокНаСкладе() — число. Префикс Получить в большинстве случаев лишний.
  • ✅ Не больше 5–7 параметров. Когда их больше, передавайте структуру параметров.
  • ✅ Знач по умолчанию. Без него — только когда параметр действительно выходной, и это сказано в описании.
  • ✅ Описание у каждой экспортной функции: параметры с типами и возвращаемое значение. EDT использует его для подсказок и проверки типов.
  • ✅ Минимум побочных эффектов. Функция-вычисление не пишет в базу и не выводит сообщений.
  • ✅ Экспорт только тому, что нужно снаружи. Внутренние помощники оставляйте без Экспорт — меньше связей, проще рефакторинг.
  • ✅ Свой код в типовой — в расширение. Новые функции для типовой конфигурации кладите в расширение или в свой общий модуль, а не правьте типовые модули: так обновления пройдут без ручного сравнения.
Проверьте себя
Чему равна переменная А после вызова Удвоить(А), если А = 5 и параметр объявлен без Знач, а внутри Число = Число * 2?
Что вернёт функция, если выполнение дошло до КонецФункции без оператора Возврат?
Какая директива не передаёт данные формы на сервер?

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

Можно ли объявить функцию внутри другой функции?
Нет. Во встроенном языке 1С вложенных функций нет. Все методы объявляются на уровне модуля, а вспомогательный код выносят в отдельную функцию того же модуля без Экспорт.
В каком порядке объявлять функции в модуле?
Порядок не важен: функцию можно вызвать выше по тексту, чем она объявлена. Для читаемости модули делят на области директивой #Область: программный интерфейс, обработчики событий, служебные процедуры и функции.
Можно ли передать функцию как параметр?
Напрямую нет. Обходные пути — передать имя функции строкой и вызвать её через общий модуль, либо использовать ОписаниеОповещения на клиенте. Вычисление строки через Выполнить() снижает безопасность и в безопасном режиме ограничено.
Есть ли перегрузка функций по числу параметров?
Нет, два метода с одним именем в модуле объявить нельзя. Используйте необязательные параметры со значениями по умолчанию или структуру параметров.
Сколько параметров может быть у функции?
Жёсткого практического предела нет, но по стандартам больше 7 параметров — повод передать структуру. Длинный список легко перепутать при вызове.