Функции во встроенном языке 1С от объявления до вызова

· 9 мин

Содержание

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

Главное отличие функции от процедуры — она отдаёт значение, а процедура просто выполняет действия. Если вам нужен результат для дальнейших вычислений, пишите функцию; если нужно что-то сделать (записать документ, вывести сообщение) — процедуру. Ниже — разбор с примерами кода: как объявить функцию, как передать аргументы по ссылке и по значению, что вернуть и как не наступить на типичные грабли с рекурсией и областью видимости.

Функция или процедура: что выбрать под задачу

Оба метода объявляются и устроены почти одинаково, но ведут себя по-разному при вызове.

ПризнакФункцияПроцедура
Ключевое словоФункция … КонецФункцииПроцедура … КонецПроцедуры
Возвращает значениеда, обязательно по смыслунет
Как вызываютв выражении: Р = Расчёт(А);оператором: Записать(Док);
Оператор Возвратс значением: Возврат Итог;без значения, для раннего выхода

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

Называйте функции по тому, что они отдают (ПолучитьКурс, ЭтоРабочийДень), а процедуры — по действию (ПровестиДокумент, ОчиститьКэш). По имени сразу видно, ждать результат или нет.

Синтаксис объявления и тело функции

Заголовок состоит из ключевого слова, имени, списка параметров в скобках и необязательного слова Экспорт. Тело — любой код, который заканчивается оператором Возврат.

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

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

  • Функция — начало объявления, обязательно.
  • СуммаСНДС — имя. Русские и латинские буквы, цифры и подчёркивание; цифра не может быть первой.
  • (Цена, Ставка = 20) — параметры; у Ставка задано значение по умолчанию.
  • Экспорт — делает функцию доступной из других модулей (подробнее ниже).
  • Возврат Цена + НДС; — отдаёт результат и завершает работу.
  • КонецФункции — закрывающее слово, обязательно.

Вызов в коде выглядит так:

Итого = СуммаСНДС(1000);        // 1200, ставка взята по умолчанию
ИтогоЛьгота = СуммаСНДС(1000, 10); // 1100

Проверим арифметику первого вызова: НДС = 1000 × 20 / 100 = 200, значит результат 1000 + 200 = 1200. Второй: 1000 × 10 / 100 = 100, результат 1100.

Несколько правил, которые экономят время:

  • ⚠️ Объявлять функцию внутри другой функции нельзя — все методы объявляются на уровне модуля.
  • Порядок объявления в модуле не важен: функция, описанная ниже, вызывается из метода выше — платформа компилирует весь модуль целиком.
  • Если выполнение дошло до КонецФункции, не встретив Возврат, функция вернёт Неопределено. Это частый источник ошибок «значение не определено» у вызывающего кода.

Параметры и передача аргументов

По умолчанию параметры передаются по ссылке: если внутри метода изменить значение параметра, изменение увидит и вызывающий код.

Процедура Удвоить(Число)
    Число = Число * 2;
КонецПроцедуры

Х = 5;
Удвоить(Х);
// теперь Х равно 10

Чтобы защитить переданную переменную от изменений, поставьте перед параметром ключевое слово Знач — передача пойдёт по значению, метод получит копию:

Процедура Удвоить(Знач Число)
    Число = Число * 2;  // снаружи Х останется 5
КонецПроцедуры

Здесь есть тонкость, на которой спотыкаются даже опытные разработчики. Знач защищает только от переприсваивания самой переменной. Если параметр — объект-ссылка (массив, структура, таблица значений), то изменение его содержимого видно снаружи даже со Знач:

Процедура ДобавитьСтроку(Знач Список)
    Список.Добавить("новый");  // элемент попадёт в исходный массив!
КонецПроцедуры

Числа, строки, даты и булево при Знач копируются полностью, а объекты остаются общими — Знач лишь запрещает заменить сам объект другим. Если нужна настоящая независимая копия коллекции, делайте её явно, например через Скопировать() у таблицы значений.

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

Параметр со значением по умолчанию можно не указывать при вызове. Значение по умолчанию задаётся только литералом-константой (число, строка, Истина, Ложь, Неопределено, дата), выражение писать нельзя.

Функция Приветствие(Имя, Текст = "Здравствуйте") Экспорт
    Возврат Текст + ", " + Имя + "!";
КонецФункции

Все параметры со значениями по умолчанию должны идти в конце списка. Нельзя задать значение первому параметру и оставить без значения второй. Если нужно пропустить средний аргумент при вызове, оставьте его пустым: Метод(А, , В).

Возврат значения и типы данных

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

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

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

Контрагент = НайтиКонтрагента("7701234567");
Если Контрагент = Неопределено Тогда
    Сообщить("Контрагент не найден");
    Возврат;
КонецЕсли;

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

Функция РазобратьФИО(ПолноеИмя) Экспорт
    Части = СтрРазделить(ПолноеИмя, " ", Ложь);
    Результат = Новый Структура("Фамилия, Имя, Отчество");
    Результат.Фамилия  = ?(Части.Количество() > 0, Части[0], "");
    Результат.Имя      = ?(Части.Количество() > 1, Части[1], "");
    Результат.Отчество = ?(Части.Количество() > 2, Части[2], "");
    Возврат Результат;
КонецФункции

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

Экспорт, область видимости и директивы компиляции

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

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

Переменные тоже имеют область видимости:

  • Локальная переменная объявляется (или возникает при первом присваивании) внутри метода и живёт только во время его работы.
  • Переменная модуля объявляется оператором Перем в начале модуля и доступна во всех его методах; с Перем … Экспорт — и снаружи.
Перем КэшКурсов Экспорт;   // переменная модуля, видна извне

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

В управляемом приложении у методов есть директивы компиляции — они задают, на клиенте или на сервере выполняется код. Директиву пишут строкой выше объявления:

  • &НаКлиенте — метод исполняется на клиенте, видит данные формы и может работать с интерактивными диалогами.
  • &НаСервере — исполняется на сервере в контексте формы, имеет доступ к базе данных.
  • &НаСервереБезКонтекста — на сервере, но без передачи данных формы; быстрее, подходит для вспомогательных расчётов.
  • &НаКлиентеНаСервереБезКонтекста — один код, доступный и клиенту, и серверу.
&НаСервереБезКонтекста
Функция ПолучитьОстаток(Номенклатура, Склад) Экспорт
    // обращение к регистру — только на сервере
КонецФункции

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

Рекурсия и её ограничения

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

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

У рекурсии обязательно должно быть условие выхода (здесь Н <= 1), иначе функция будет вызывать себя бесконечно. Глубина вложенности вызовов в платформе ограничена: при слишком большом числе уровней вы получите ошибку о превышении допустимой глубины вложенности, а не тихое зависание.

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

// то же, что рекурсивный факториал, но циклом
Функция Факториал(Н)
    Результат = 1;
    Для Сч = 2 По Н Цикл
        Результат = Результат * Сч;
    КонецЦикла;
    Возврат Результат;
КонецФункции

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

Проверьте себя
Чем функция отличается от процедуры?
Что вернёт функция, если выполнение дошло до КонецФункции без оператора Возврат?
Как передать параметр так, чтобы метод не изменил исходную переменную?

Практики, которые упростят поддержку кода

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

  • 🎯 Одна функция — одна задача. Если метод и считает, и записывает, и выводит сообщения — разбейте его. Короткую функцию проще отладить и переиспользовать.
  • 🏷 Осмысленные имена. РассчитатьПени, а не Обработка2. Для функций-проверок удобны имена-вопросы: ЭтоПустаяСтрока, ДокументПроведён.
  • 🛡 Входные параметры — со Знач, если метод их не меняет. Так явно видно намерение, и случайное изменение не уйдёт к вызывающему коду.
  • 📦 Несколько результатов — через структуру, а не через ссылочные параметры. Вызов читается проще.
  • 🚪 Ранний выход вместо глубоких Если. Проверили некорректные данные в начале, сделали Возврат — основная логика остаётся без лишних отступов.
  • 🧩 Экспортируйте только то, что нужно снаружи. Остальные методы держите без Экспорт — это скрывает внутреннюю кухню модуля.

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

Для часто вызываемых функций думайте о производительности: не открывайте запрос внутри цикла по тысячам строк, а получайте данные одним запросом заранее. Кэшируйте результат в переменной модуля или в параметрах сеанса, если он не меняется в рамках операции.

Проверка перед тем, как считать функцию готовой
Выполнено: 0 / 6

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

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