Как устроена функция во встроенном языке 1С и где на ней ошибаются
Содержание
Функция в 1С — это именованный блок кода между Функция и КонецФункции, который возвращает значение через Возврат. Частое заблуждение новичков: «параметры функции — это копии, их можно спокойно менять». Нет. Во встроенном языке 1С параметры по умолчанию передаются по ссылке, и присваивание параметру внутри функции меняет переменную у вызывающего кода.
Ниже — как работают функции в 1С на практике: синтаксис, параметры, возвращаемые значения, директивы &НаСервере и &НаКлиенте, рекурсия и ошибки программирования, которые чаще всего всплывают на ревью. Примеры написаны для платформы 8.3 и управляемых форм.
Функция или процедура: в чём разница
Обе конструкции — подпрограммы. Разница одна, но принципиальная: функция возвращает результат, процедура — нет.
| Признак | Процедура | Функция |
|---|---|---|
| Ключевые слова | Процедура … КонецПроцедуры | Функция … КонецФункции |
Возврат | без значения, просто выход | Возврат Значение; |
| Можно в выражении | нет | да: Сумма = Цена(Товар) * 2 |
| Можно вызвать отдельной строкой | да | да, результат отбрасывается |
Отсюда два практических правила:
- 🔹 Функцию разрешено вызвать как процедуру:
ЗаписатьВЖурнал(Текст);— результат просто потеряется. - 🔹 Процедуру нельзя использовать в выражении. Модуль не скомпилируется, синтаксический контроль выдаст ошибку
Использование процедуры как функции.
Когда что выбирать? Если подпрограмма вычисляет что-то — сумму, признак, ссылку, — пишите функцию. Если делает действие — записывает объект, заполняет табличную часть, выводит сообщение, — процедуру. Функция, которая и меняет базу, и что-то возвращает, допустима, но её назначение должно быть видно из имени: СоздатьДокументПоступления(), а не ПолучитьДокумент().
Синтаксис функции в 1С по частям
Полная форма объявления выглядит так:
// Возвращает сумму документа с учётом скидки.
//
// Параметры:
// Сумма - Число - сумма до скидки.
// Процент - Число - процент скидки, по умолчанию 0.
//
// Возвращаемое значение:
// Число - сумма после скидки, округлённая до копеек.
//
Функция СуммаСоСкидкой(Знач Сумма, Знач Процент = 0) Экспорт
Если Процент <= 0 Тогда
Возврат Сумма;
КонецЕсли;
Возврат Окр(Сумма * (100 - Процент) / 100, 2);
КонецФункции
Разберём строку за строкой:
- Комментарий-описание над функцией. Платформа его не требует, но по стандартам разработки 1С он обязателен для экспортных методов. EDT и конфигуратор показывают его в подсказке при вызове.
Функцияи имя. Имя пишут глаголом или существительным-результатом, слитно, каждое слово с заглавной буквы.- Список параметров в скобках через запятую. Скобки обязательны, даже если параметров нет:
Функция ТекущийПользователь(). Экспорт— делает функцию видимой из других модулей. Без него она доступна только внутри своего модуля.- Тело — любые операторы языка.
Возврат— завершает функцию и отдаёт значение. Их может быть несколько, сработает первый достигнутый.КонецФункции— без точки с запятой.
Проверка на числах: СуммаСоСкидкой(1500, 10) вернёт 1500 * 90 / 100 = 1350. Вызов СуммаСоСкидкой(1500) вернёт 1500, потому что процент берётся по умолчанию.
Ранний Возврат в начале функции («сторожевое условие») избавляет от вложенных Если на три уровня. Проверили входные данные, вышли — дальше пишете основную логику без отступов.
Параметры функций: по ссылке, Знач и значения по умолчанию
Это главный источник неожиданных багов. Сравним два варианта:
Функция Удвоить(Число)
Число = Число * 2;
Возврат Число;
КонецФункции
А = 5;
Б = Удвоить(А);
// Б = 10, но и А теперь 10
Параметр Число передан по ссылке. Присваивание внутри функции изменило переменную А снаружи. Добавьте Знач — и А останется равной 5:
Функция Удвоить(Знач Число)
Число = Число * 2;
Возврат Число;
КонецФункции
Как Знач ведёт себя с объектами
С таблицами значений, массивами и структурами Знач работает не так, как ждут. Передаётся копия ссылки на коллекцию, а не копия самой коллекции:
- 🔸
Таблица.Добавить()внутри функции — строка появится и у вызывающего кода, даже сЗнач. - 🔸
Таблица = Новый ТаблицаЗначенийвнутри функции — сЗначвызывающий код этого не увидит, безЗнач— увидит новую пустую таблицу.
Нужна независимая копия — делайте её явно: Таблица.Скопировать() для таблицы значений, Новый Массив с переносом элементов для массива.
Значения по умолчанию
Необязательные параметры пишут в конце списка: Функция Найти(Код, Владелец = Неопределено, ТолькоАктивные = Истина). Значение по умолчанию — только литерал: число, строка, дата, Истина/Ложь, Неопределено, NULL. Выражение вроде ТекущаяДата() там не допускается.
Пропустить параметр в середине можно запятой: Найти("00-001", , Ложь).
Параметры при вызове сервера
Когда клиентская процедура вызывает серверную, параметры сериализуются и уходят на сервер. Параметры, переданные без Знач, после выполнения возвращаются обратно на клиент — это второй проход по сети. Поэтому у серверных функций, которые не должны менять аргументы, ставьте Знач у каждого параметра. На медленном канале разница заметна.
| Способ | Что видит вызывающий код | Когда использовать |
|---|---|---|
Без Знач | любые изменения параметра | функция обязана заполнить выходной параметр |
Знач + примитив | ничего | почти всегда |
Знач + коллекция | изменения содержимого, но не замену | нужно читать коллекцию, менять осознанно |
| Параметр по умолчанию | — | необязательная настройка поведения |
Что возвращает функция и как вернуть несколько значений
Функция возвращает одно значение любого типа: число, строку, ссылку, коллекцию, Неопределено.
Если выполнение дошло до КонецФункции без Возврат, функция вернёт Неопределено. Синтаксический контроль об этом не предупреждает. Отсюда классика: в одной ветке Если возврат есть, в другой забыли — и вызывающий код падает на Неопределено где-то далеко от места ошибки.
Когда результатов несколько, есть три рабочих способа:
- Структура — самый читаемый вариант:
Функция ОстатокТовара(Номенклатура, Склад)
Результат = Новый Структура("Количество, Сумма", 0, 0);
// ... запрос к регистру, заполнение полей
Возврат Результат;
КонецФункции
Остаток = ОстатокТовара(Товар, ОсновнойСклад);
Если Остаток.Количество > 0 Тогда
// ...
КонецЕсли;
- Выходные параметры без
Знач:Функция Проверить(Документ, ТекстОшибки)возвращаетИстина/Ложь, а текст ошибки кладёт в параметр. Удобно для функций-проверок. - Массив — когда значения однородны: список ссылок, набор дат.
Функция, вызванная с клиента на сервере, может вернуть только сериализуемое значение, доступное на клиенте. ТаблицаЗначений, РезультатЗапроса, объекты справочников и документов на клиент не передаются. Возвращайте структуры, массивы, ссылки или поместите таблицу во временное хранилище.
Где выполняется функция: директивы и область видимости
В модуле управляемой формы перед каждой функцией стоит директива компиляции. Она определяет, на какой стороне функция работает и что ей доступно.
| Директива | Где выполняется | Доступ к данным формы | База данных |
|---|---|---|---|
&НаКлиенте | клиент | да | нет |
&НаСервере | сервер | да, форма передаётся на сервер | да |
&НаСервереБезКонтекста | сервер | нет | да |
&НаКлиентеНаСервереБезКонтекста | там, откуда вызвали | нет | только на сервере |
&НаСервереБезКонтекста — самый дешёвый вызов сервера: на сервер не уходят данные формы. Если функции нужны только параметры, а не Объект и Элементы, выбирайте её.
Видимость переменных
- 🔹 Локальные — объявлены внутри функции через
Перемили неявно первым присваиванием. Живут до выхода из функции. - 🔹 Переменные модуля —
Перемв начале модуля объекта или формы. Видны всем методам модуля. В общих модулях переменные модуля объявлять нельзя. - 🔹 Параметры — ведут себя как локальные переменные, но связаны с аргументами по правилам из раздела выше.
Экспортная функция общего модуля доступна там, где модуль скомпилирован: флажки Клиент, Сервер, Вызов сервера в свойствах модуля. Чтобы клиент мог вызвать серверную функцию общего модуля, у модуля должен стоять флажок Вызов сервера.
Рекурсия и скорость вызовов функций в 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 использует его для подсказок и проверки типов.
- ✅ Минимум побочных эффектов. Функция-вычисление не пишет в базу и не выводит сообщений.
- ✅ Экспорт только тому, что нужно снаружи. Внутренние помощники оставляйте без
Экспорт— меньше связей, проще рефакторинг. - ✅ Свой код в типовой — в расширение. Новые функции для типовой конфигурации кладите в расширение или в свой общий модуль, а не правьте типовые модули: так обновления пройдут без ручного сравнения.