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

В этой статье мы разберём не только синтаксис и базовые принципы работы функций, но и нюансы, которые редко упоминаются в документации. Например, как функции в 1С отличаются от процедур по механизму вызова стека, почему некоторые функции могут "забывать" контекст выполнения, и как правильно документировать их для командной работы. Материал будет полезен как тем, кто только осваивает 1С 8.3, так и тем, кто хочет оптимизировать существующий код.

1. Чем функция отличается от процедуры в 1С

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

Это влияет на:

  • 🔹 Стек вызовов: функции могут быть вложены друг в друга, образуя цепочку возвращаемых значений, что упрощает отладку.
  • 🔹 Производительность: вызов функции требует дополнительных ресурсов на обработку возвращаемого значения, особенно если оно сложного типа (например, ТаблицаЗначений).
  • 🔹 Читаемость кода: функции часто используются для вычислений, а процедуры — для изменений (например, запись в базу).

Пример: если вам нужно рассчитать скидку для клиента, логичнее оформить это как функцию РассчитатьСкидку(Клиент), которая вернёт числовое значение. А вот обновление статуса заказа лучше оформить как процедуру ОбновитьСтатусЗаказа(Заказ, НовыйСтатус).

⚠️ Внимание: В 1С 8.3.20+ появилась возможность использовать Возврат в процедурах для досрочного завершения, но это не делает процедуру функцией! Возвращаемое значение всё равно будет проигнорировано.

2. Синтаксис и структура функции: разбор по шагам

Общий вид функции в выглядит так:

Функция ИмяФункции(Параметр1, Параметр2, ...) Экспорт

// Тело функции

Возврат Результат;

КонецФункции

Разберём каждый элемент:

  • 📌 ИмяФункции: должно быть уникальным в пределах модуля. Рекомендуется использовать CamelCase (например, ПолучитьДанныеКлиента).
  • 📌 Параметры: перечисляются через запятую. Можно указывать типы (например, Клиент Тип Клиент), но это необязательно.
  • 📌 Экспорт: если указано, функция будет доступна вне модуля (например, для вызова из других объектов конфигурации).
  • 📌 Возврат: обязателен! Без него функция вернёт Неопределён, что может привести к ошибкам.

Пример функции с типизацией параметров:

Функция РассчитатьНДС(Сумма Тип Число, СтавкаНДС Тип Число = 20) Экспорт

Возврат Сумма * СтавкаНДС / 100;

КонецФункции

Обратите внимание на параметр СтавкаНДС со значением по умолчанию (= 20). Это позволяет вызывать функцию без указания ставки, если она стандартная.

📊 Как часто вы используете типизацию параметров в функциях 1С?
Всегда
Только для критических функций
Рядом не стоял
Не знаю, что это

3. Параметры функций: передача по значению vs по ссылке

В параметры функций передаются по значению, но с нюансами. Это означает, что функция работает с копией переданного значения, а не с оригиналом. Однако для сложных типов (например, СправочникСсылка, ДокументОбъект) копируется только ссылка на объект, а не сам объект.

Пример:

Процедура ТестПередачи()

Число = 10;

ИзменитьЧисло(Число);

Сообщить(Число); // Выведет 10 - оригинал не изменился

Док = Документы.ЗаказКлиента.СоздатьДокумент();

ИзменитьДокумент(Док);

Сообщить(Док.Номер); // Номер изменится - работаем со ссылкой!

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

Функция ИзменитьЧисло(Значение)

Значение = 20;

КонецФункции

Процедура ИзменитьДокумент(Док)

Док.Номер = "ТЕСТ001";

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

Это важно учитывать при работе с:

  • 🔸 Примитивными типами (Число, Строка, Дата): изменения внутри функции не затронут оригинал.
  • 🔸 Ссылочными типами (СправочникСсылка, ДокументОбъект): изменения затронут оригинал, если он не был скопирован явно.
  • 🔸 Коллекциями (Массив, Структура): поведение зависит от того, как они были созданы (например, Новый Массив vs Массив1 = Массив2).
⚠️ Внимание: При передаче ТаблицыЗначений в функцию изменения затронут оригинал, если не использовать Копировать(). Это частая причина багов при параллельной обработке данных!

Использовать копирование для изменяемых коллекций

Документировать ожидаемые типы параметров

Избегать изменений входных параметров без необходимости

Тестировать функцию с пустыми значениями (Неопределён, NULL)

-->

4. Возвращаемые значения: типы и особенности

Функция в может возвращать любые типы данных, включая:

  • 📦 Примитивы: Число, Строка, Булево, Дата.
  • 📦 Ссылочные типы: СправочникСсылка.Номенклатура, ДокументСсылка.ЗаказКлиента.
  • 📦 Коллекции: Массив, Структура, ТаблицаЗначений.
  • 📦 Объекты: ДокументОбъект.ЗаказКлиента, СправочникОбъект.Контрагент.
  • 📦 Неопределён: если Возврат не вызван или возвращено пустое значение.

Особенности возвращаемых значений:

  1. Множественные возвраты: в нет кортежей (как в Python), но можно возвращать Структуру или Массив с несколькими значениями:
    Функция ПолучитьДанныеКлиента(Клиент)
    

    Данные = Новый Структура();

    Данные.Вставить("Имя", Клиент.Наименование);

    Данные.Вставить("Баланс", Клиент.Баланс);

    Возврат Данные;

    КонецФункции

  2. Ленивые вычисления: если функция возвращает объект, его свойства могут подгружаться по мере обращения (актуально для ДокументОбъект).
  3. Неопределён vs NULL: в это разные вещи! Неопределён — отсутствие значения, NULL — специальное значение для СУБД.

Типичная ошибка: забыть про Возврат в ветке Иначе:

Функция ПроверкаСкидки(Клиент)

Если Клиент.Категория = Перечисление.КатегорииКлиентов.ВИП Тогда

Возврат 15;

Иначе

// Забыли Возврат! Функция вернёт Неопределён

КонецЕсли;

КонецФункции

💡

Используйте Возврат Неопределён явно, если функция может не вернуть значение в некоторых случаях. Это упростит отладку.

5. Контекст выполнения и область видимости

Функции в выполняются в определённом контексте, который зависит от того, где они объявлены:

Тип модуля Контекст Особенности
Модуль объекта (например, документа) Объект и его реквизиты Можно обращаться к ЭтотОбъект, Объект.Реквизит
Модуль менеджера (например, справочника) Тип данных (например, Справочник.Номенклатура) Нет доступа к конкретным объектам без параметров
Общий модуль Зависит от свойства Глобальный Если глобальный — доступен везде, иначе только через ОбщийМодуль.ИмяФункции
Модуль формы Элементы формы и её данные Можно обращаться к ЭлементыФормы, ПараметрыФормы

Пример проблемы с контекстом:

// В модуле документа "ЗаказКлиента"

Функция ПолучитьСумму()

Возврат СуммаДокумента; // Ошибка! Нужно обращаться через ЭтотОбъект

КонецФункции

// Правильно:

Функция ПолучитьСумму()

Возврат ЭтотОбъект.СуммаДокумента;

КонецФункции

Ещё один нюанс: если функция объявлена в общем модуле с установленным флагом Глобальный, её можно вызывать без указания имени модуля. Однако это может привести к конфликтам имён, если функции с одинаковыми именами объявлены в разных модулях.

⚠️ Внимание: В 1С 8.3.18+ появилась возможность явно указывать контекст с помощью ПривязкаКонтекста в свойствах общего модуля. Это помогает избежать случайных обращений к несуществующим объектам.

6. Рекурсия и оптимизация вызовов функций

Рекурсия в — мощный инструмент для обработки иерархических данных (например, дерево справочников), но она требует осторожности. Основные риски:

  • 🔥 Переполнение стека: в глубина рекурсии ограничена (обычно ~1000 вызовов).
  • 🔥 Утечки памяти: каждый вызов функции создаёт новую область видимости для переменных.
  • 🔥 Производительность: рекурсивные функции могут быть медленнее итеративных решений.

Пример рекурсивной функции для обхода дерева справочника:

Функция ПолучитьВсеПодчиненныеЭлементы(Родитель, Результат = Неопределён)

Если Результат = Неопределён Тогда

Результат = Новый Массив();

КонецЕсли;

Запрос = Новый Запрос();

Запрос.Текст = "ВЫБРАТЬ Ссылку ИЗ Справочник.Номенклатура КАК Номенклатура ГДЕ Номенклатура.Родитель = &Родитель";

Запрос.УстановитьПараметр("Родитель", Родитель);

Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл

Результат.Добавить(Выборка.Ссылка);

ПолучитьВсеПодчиненныеЭлементы(Выборка.Ссылка, Результат); // Рекурсия

КонецЦикла;

Возврат Результат;

КонецФункции

Как оптимизировать рекурсию:

  1. Используйте хвостовую рекурсию (если возможно), чтобы уменьшить нагрузку на стек.
  2. Для глубоких деревьев замените рекурсию на итеративный обход с использованием стека (массива).
  3. Кэшируйте результаты повторяющихся вычислений (мемоизация).
Что будет при переполнении стека?

В 1С:Предприятие при превышении лимита рекурсии возникнет ошибка "Переполнение стека". При этом все несохранённые данные в транзакции будут отменены, а сеанс пользователя может быть разорван. В логах сервера появится запись с трассировкой стека вызовов.

Альтернатива рекурсии — итеративный обход с использованием стека:

Функция ПолучитьВсеПодчиненныеЭлементыИтеративно(Родитель)

Результат = Новый Массив();

Стек = Новый Массив();

Стек.Добавить(Родитель);

Пока Стек.Количество() > 0 Цикл

Текущий = Стек.Удалить(0);

Запрос = Новый Запрос();

Запрос.Текст = "ВЫБРАТЬ Ссылку ИЗ Справочник.Номенклатура КАК Номенклатура ГДЕ Номенклатура.Родитель = &Текущий";

Запрос.УстановитьПараметр("Текущий", Текущий);

Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл

Результат.Добавить(Выборка.Ссылка);

Стек.Добавить(Выборка.Ссылка);

КонецЦикла;

КонецЦикла;

Возврат Результат;

КонецФункции

💡

Рекурсия удобна для простых иерархий, но для глубоких деревьев (более 100 уровней) лучше использовать итеративный подход.

7. Типичные ошибки и как их избегать

Даже опытные разработчики иногда допускают ошибки при работе с функциями. Вот самые распространённые:

  1. Игнорирование возвращаемого значения:
    Результат = РассчитатьСкидку(Клиент); // А если функция вернёт Неопределён?
    

    Если Результат > 0 Тогда

    // Логика

    КонецЕсли;

    Решение: всегда проверяйте возвращаемое значение на Неопределён или NULL.

  2. Изменение входных параметров:
    Функция ОбработатьМассив(Массив)
    

    Массив.Добавить("Новый элемент"); // Изменит оригинальный массив!

    Возврат Массив.Количество();

    КонецФункции

    Решение: используйте Массив = Новый Массив(ИсходныйМассив) для создания копии.

  3. Утечки памяти в рекурсии:

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

    Решение: ограничивайте глубину рекурсии или используйте итеративный подход.

  4. Неявная типизация:
    Функция Сложить(А, Б)
    

    Возврат А + Б; // А если А - строка, а Б - число?

    КонецФункции

    Решение: явно указывайте типы параметров или добавляйте проверки.

Ещё одна распространённая проблема — зависимость от контекста. Например, функция, написанная в модуле документа, может перестать работать, если её перенести в общий модуль, потому что потеряется доступ к ЭтотОбъект.

⚠️ Внимание: В 1С 8.3.21+ появился механизм ОбластьВидимостиПеременных, который позволяет явно контролировать время жизни переменных в функциях. Это помогает избежать утечек памяти в длительных операциях.

8. Лучшие практики написания функций в 1С

Чтобы функции были надёжными, читаемыми и легко поддерживаемыми, следуйте этим рекомендациям:

  • 🛠 Документируйте функции:

    Используйте комментарии в формате:

    /// <summary>
    

    /// Рассчитывает скидку для клиента на основе его категории и истории заказов.

    /// </summary>

    /// <param name="Клиент">Ссылка на справочник Контрагенты</param>

    /// <returns>Число - процент скидки или Неопределён при ошибке</returns>

  • 🛠 Разделяйте логику:

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

  • 🛠 Используйте параметры по умолчанию:
    Функция ФорматироватьСтроку(Строка, Разделитель = ", ", МаксДлина = 100)
  • 🛠 Тестируйте крайние случаи:

    Проверяйте функцию с пустыми параметрами, Неопределён, NULL и некорректными типами.

  • 🛠 Ограничивайте права доступа:

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

    Функция ПолучитьЗарплатуСотрудника(Сотрудник)
    

    Если НЕ ЗначениеЗаполнено(Пользователь.Права.ПросмотрЗарплат) Тогда

    Возврат Неопределён;

    КонецЕсли;

    // ...

    КонецФункции

Пример хорошо структурированной функции:

/// <summary>

/// Возвращает список активных заказов клиента за указанный период.

/// </summary>

/// <param name="Клиент">Ссылка на справочник Контрагенты</param>

/// <param name="ДатаНачала">Дата - начало периода (по умолчанию: начало текущего месяца)</param>

/// <param name="ДатаОкончания">Дата - конец периода (по умолчанию: текущая дата)</param>

/// <returns>ТаблицаЗначений - список заказов или Неопределён при ошибке</returns>

Функция ПолучитьАктивныеЗаказыКлиента(Клиент, ДатаНачала = Неопределён, ДатаОкончания = Неопределён) Экспорт

Если НЕ ЗначениеЗаполнено(Клиент) Тогда

Возврат Неопределён;

КонецЕсли;

Если ДатаНачала = Неопределён Тогда

ДатаНачала = НачалоМесяца(ТекущаяДата());

КонецЕсли;

Если ДатаОкончания = Неопределён Тогда

ДатаОкончания = ТекущаяДата();

КонецЕсли;

Запрос = Новый Запрос();

Запрос.Текст =

"ВЫБРАТЬ

| Заказ.Ссылка КАК Ссылка,

| Заказ.Дата КАК Дата,

| Заказ.СуммаДокумента КАК Сумма

|ИЗ

| Документ.ЗаказКлиента КАК Заказ

|ГДЕ

| Заказ.Клиент = &Клиент

| И Заказ.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания

| И Заказ.ПометкаУдаления = ЛОЖЬ";

Запрос.УстановитьПараметр("Клиент", Клиент);

Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);

Запрос.УстановитьПараметр("ДатаОкончания", ДатаОкончания);

Результат = Запрос.Выполнить().Выгрузить();

Возврат Результат;

КонецФункции

💡

Хорошая функция — это функция, которую можно использовать не задумываясь о её внутренней реализации. Стремитесь к "чёрному ящику": на входе — чёткие параметры, на выходе — предсказуемый результат.

FAQ: Ответы на частые вопросы

Можно ли в 1С вернуть из функции несколько значений?

Прямо — нет, но можно:

  1. Вернуть Структуру или Массив с несколькими элементами.
  2. Использовать выходные параметры (через ссылки, но это небезопасно).
  3. В 1С 8.3.20+ появилась поддержка кортежей через ЗначениеВМассиве().

Пример:

Функция ПолучитьИмяИБаланс(Клиент)

Возврат Новый Структура("Имя, Баланс", Клиент.Наименование, Клиент.Баланс);

КонецФункции

Почему моя функция возвращает Неопределён, хотя я указал Возврат?

Возможные причины:

  • Вы забыли Возврат в одной из веток Если.
  • Функция завершилась с ошибкой до Возврат.
  • Вы возвращаете результат другой функции, которая сама вернула Неопределён.

Чтобы отладить, добавьте перед Возврат:

Сообщить("Дошли до возврата: " + Результат);
Как передать в функцию необязательный параметр?

Используйте значения по умолчанию:

Функция Тест(А, Б = 10, В = "Привет")

// ...

КонецФункции

При вызове можно пропустить необязательные параметры:

Тест(1); // Б = 10, В = "Привет"

Тест(1, 20); // В = "Привет"

Тест(1, , "Пока"); // Б = 10

Можно ли вызвать функцию из 1С через веб-сервис?

Да, если:

  1. Функция объявлена в общем модуле с свойством ВызовСервера.
  2. Модуль опубликован как веб-сервис в настройках 1С:Предприятия.
  3. Параметры и возвращаемые значения поддерживаются WS-стандартом (например, не все сложные типы 1С можно передать через SOAP).

Пример:

// В общем модуле с флагом "ВызовСервера"

Функция ПолучитьКурсВалюты(Валюта, Дата) Экспорт

// ...

КонецФункции

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

Способы оптимизации:

  • 🚀 Замените рекурсию на итерацию для глубоких структур.
  • 🚀 Кэшируйте результаты повторяющихся вычислений (например, с помощью Структуры).
  • 🚀 Избегайте лишних обращений к базе данных — используйте ОбъединениеЗапросов.
  • 🚀 Уменьшайте количество временных объектов (например, ТаблицаЗначений).
  • 🚀 Используйте ПоместитьВоВременноеХранилище() для больших данных.