Возврат значения из функции во встроенном языке 1С

· 9 мин

Содержание

Значение из функции отдаёт оператор Возврат: пишете Возврат Результат; — и выполнение метода прекращается, а вычисленная величина уходит в точку вызова. Без этого оператора функция вернёт Неопределено, и получите частую ошибку новичка: Итог = Расчёт();, а в Итог лежит пустота.

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

Синтаксис функции и оператор Возврат

Функция объявляется ключевым словом Функция, заканчивается КонецФункции. Внутри обязателен оператор Возврат с выражением — это и есть отдаваемое значение.

Функция Сумма(Первое, Второе)
    Результат = Первое + Второе;
    Возврат Результат;
КонецФункции

Вызов пишут в правой части присваивания или внутри выражения:

Итог = Сумма(10, 5);        // Итог = 15
Сообщить(Сумма(2, 3) * 4);  // 20

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

Функция Больше(А, Б)
    Возврат ?(А > Б, А, Б);
КонецФункции

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

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

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

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

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

ПризнакФункцияПроцедура
Ключевые словаФункция … КонецФункцииПроцедура … КонецПроцедуры
Оператор Возвратс выражением, обязателенбез выражения, необязателен
Вызов в выраженииХ = Метод(); можнонельзя
Вызов как командытоже можноМетод();
Что отдаётзначениеничего

Процедура вызывается отдельной строкой — её нельзя поставить в правую часть присваивания:

Процедура ЗаписатьВЖурнал(Текст)
    ЗаписьЖурналаРегистрации("Обработка", , , , Текст);
КонецПроцедуры

ЗаписатьВЖурнал("Готово");   // верно
Х = ЗаписатьВЖурнал("...");  // ошибка: процедура не возвращает значение

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

Как выбрать между ними на практике: если метод отвечает на вопрос («какая цена?», «есть ли долг?», «сколько строк?») — это функция, и её удобно читать прямо в условии Если ЕстьДолг(Клиент) Тогда. Если метод выполняет команду и ничего не вычисляет («провести», «записать», «очистить») — процедура. Функции, которые заодно меняют данные в базе, считаются плохим тоном: из имени не видно, что вызов приводит к записи.

Распространённая правка: объявили Процедура, а внутри написали Возврат Значение;. Будет синтаксическая ошибка — процедура не принимает выражение после Возврат. Либо меняйте на Функция, либо убирайте выражение.

Какой тип данных вернёт функция

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

Функция НайтиЦену(Номенклатура)
    Если Номенклатура = Неопределено Тогда
        Возврат Неопределено;     // вернули Неопределено
    КонецЕсли;
    Возврат Номенклатура.Цена;    // вернули Число
КонецФункции

Из-за этого на стороне вызова результат часто проверяют перед использованием:

Цена = НайтиЦену(Товар);
Если Цена = Неопределено Тогда
    Цена = 0;
КонецЕсли;

Определить фактический тип помогает функция ТипЗнч():

Если ТипЗнч(Результат) = Тип("Число") Тогда
    // ...
КонецЕсли;

Хороший тон — чтобы функция на всех ветках возвращала значения одного типа (или Неопределено как «ничего не нашли»). Функция, которая то число, то массив, то строку, превращает вызывающий код в набор проверок.

В комментарии перед функцией принято описывать, что она возвращает: это заменяет статическую типизацию и помогает тому, кто будет её вызывать. Платформа подхватывает такое описание и показывает подсказку по Ctrl+Пробел.

Досрочный выход и несколько операторов Возврат

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

Функция СкидкаКлиента(Клиент)
    Если Клиент = Неопределено Тогда
        Возврат 0;
    КонецЕсли;
    Если НЕ Клиент.Постоянный Тогда
        Возврат 0;
    КонецЕсли;
    Возврат Клиент.ПроцентСкидки;
КонецФункции

Три оператора Возврат, но сработает ровно один — тот, до которого первым дойдёт управление. Остальной код после него не выполняется.

Этот же приём работает и в процедурах, только Возврат пишут без выражения — он просто прерывает выполнение:

Процедура ОбработатьСписок(Список)
    Если Список.Количество() = 0 Тогда
        Возврат;         // выходим, обрабатывать нечего
    КонецЕсли;
    Для Каждого Строка Из Список Цикл
        // ...
    КонецЦикла;
КонецПроцедуры

Возврат корректно срабатывает и изнутри цикла — он прерывает и цикл, и функцию сразу. Это удобно для поиска: нашли нужное — вернули и вышли, не доходя до конца коллекции.

Функция НайтиСтроку(Таблица, Артикул)
    Для Каждого Стр Из Таблица Цикл
        Если Стр.Артикул = Артикул Тогда
            Возврат Стр;        // нашли — выходим немедленно
        КонецЕсли;
    КонецЦикла;
    Возврат Неопределено;       // перебрали всё, не нашли
КонецФункции

Внутри блока Попытка … Исключение … КонецПопытки оператор Возврат тоже допустим и отрабатывает штатно — функция завершится, отдав указанное значение.

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

Проверьте мысленно: в примере со скидкой, если убрать последнюю строку Возврат Клиент.ПроцентСкидки;, постоянный клиент получит Неопределено вместо процента.

Параметры по ссылке и по значению: ключевое слово Знач

Второй способ «вернуть» данные из метода — изменить его параметр. Но работает это только если параметр передан по ссылке. По умолчанию во встроенном языке параметры и передаются по ссылке: присваивание параметру внутри метода меняет переменную вызывающего кода.

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

Х = 5;
Удвоить(Х);
Сообщить(Х);   // 10 — переменная изменилась снаружи

Чтобы метод работал с копией и не трогал оригинал, перед параметром ставят ключевое слово Знач (сокращение от «значение»):

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

Х = 5;
Удвоить(Х);
Сообщить(Х);   // 5 — оригинал не тронут

Отсюда два подхода к возврату нескольких результатов:

  • Функция + параметр по ссылке. Основной результат — через Возврат, дополнительный — через выходной параметр.
  • Процедура с несколькими параметрами по ссылке. Когда возвращаемых величин несколько и ни одна не главная.
Функция РазобратьСтроку(Знач Исходная, Префикс, Остаток)
    Префикс = Лев(Исходная, 3);
    Остаток = Сред(Исходная, 4);
    Возврат СтрДлина(Исходная);
КонецФункции

П = "";
О = "";
Длина = РазобратьСтроку("АБВ12345", П, О);
// Длина = 8, П = "АБВ", О = "12345"

Здесь Исходная помечена Знач (мы её не меняем), а Префикс и Остаток — обычные параметры по ссылке, через них уходят дополнительные результаты.

Тонкость с объектами

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

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

М = Новый Массив;
ДобавитьЭлемент(М);
Сообщить(М.Количество());   // 1, хотя стоит Знач

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

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

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

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

Округлить(3.14159);      // Разрядность = 2, результат 3.14
Округлить(3.14159, 0);   // Разрядность = 0, результат 3

Два правила, о которые спотыкаются:

  1. Значением по умолчанию может быть только литерал — число, строка, дата, Истина, Ложь, Неопределено. Выражение или вызов функции туда не поставить.
  2. Параметры с умолчанием должны идти в конце списка. Нельзя задать умолчание среднему параметру, а следующий оставить обязательным.

Ключевое слово Знач и значение по умолчанию сочетаются: Функция Ф(Знач Префикс = "").

Если нужно понять внутри функции, передавали параметр или нет, частый приём — умолчание Неопределено и проверка Если Параметр = Неопределено Тогда ….

Где объявлять функцию и как вызвать её из другого модуля

Функция живёт в том модуле, где объявлена. Чтобы вызывать её из других мест, нужны два условия: модуль должен быть доступен, а функция — помечена Экспорт.

  • Общий модуль — для функций, которые переиспользуются по всей конфигурации. Галочки «Клиент» и «Сервер» в свойствах модуля определяют, где функция исполняется.
  • Модуль объекта (документа, справочника) — для логики, привязанной к конкретному объекту.
  • Модуль формы — для обработчиков и вспомогательных функций этой формы.

Вызов экспортной функции из общего модуля идёт через имя модуля:

Результат = ОбщегоНазначения.СокращённоеНаименование(Элемент);

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

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

Частые ошибки при возврате значения

  • 🔴 Функция без Возврат на какой-то ветке. Самая частая. Результат — Неопределено, причём молча. Проверяйте все ветки Если.
  • 🔴 Возврат с выражением в процедуре. Синтаксическая ошибка. Нужен результат — объявляйте функцию.
  • 🟡 Код после Возврат. Строки ниже сработавшего Возврат не выполнятся. Если рассчитывали, что выполнятся, — логика сломана.
  • 🟡 Разные типы на разных ветках. То число, то строка, то Неопределено — вызывающий код обрастает проверками. Держите один тип.
  • 🟡 Забыли Знач у входного параметра. Метод меняет переменную вызывающего кода, хотя не должен. Побочный эффект ловится долго.
  • 🟡 Понадеялись на Знач для массива. Содержимое объекта всё равно изменится снаружи. Для независимой копии копируйте явно.
Проверьте себя
Что вернёт функция, если выполнение дойдёт до `КонецФункции`, не встретив ни одного оператора `Возврат`?
Какой параметр метод НЕ сможет изменить снаружи?
Можно ли поставить вызов процедуры в правую часть присваивания (`Х = Процедура();`)?

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

Можно ли вернуть несколько значений одним оператором Возврат?
Одним — нет, Возврат отдаёт одну величину. Но этой величиной может быть структура или массив: Возврат Новый Структура("Код, Имя", Код, Имя);. Либо дополнительные результаты отдают через параметры по ссылке.
Что будет, если вызвать функцию как процедуру — отдельной строкой?
Платформа выполнит её и проигнорирует результат. Ошибки не будет. Но если результат не нужен в принципе, чище объявить метод процедурой.
Чем `Возврат;` отличается от `Возврат Значение;`?
Возврат; без выражения допустим только в процедуре и означает досрочный выход. Возврат Значение; обязателен в функции и отдаёт величину. В функции пустой Возврат; приведёт к ошибке.
Как узнать, какой тип вернула функция?
Функцией ТипЗнч(Результат) — она отдаёт тип значения, который сравнивают с Тип("Число"), Тип("Строка") и т. д. Для проверки «вернулось ли что-то» сравнивают с Неопределено.
Почему параметр изменился снаружи функции, хотя я менял его внутри?
По умолчанию параметры передаются по ссылке. Чтобы функция работала с копией, добавьте перед параметром Знач. Для объектов Знач защищает от переназначения, но не от изменения содержимого.