Возврат значения из функции во встроенном языке 1С
Содержание
Значение из функции отдаёт оператор Возврат: пишете Возврат Результат; — и выполнение метода прекращается, а вычисленная величина уходит в точку вызова. Без этого оператора функция вернёт Неопределено, и получите частую ошибку новичка: Итог = Расчёт();, а в Итог лежит пустота.
Путаница обычно в другом — чем функция отличается от процедуры, почему параметр, изменённый внутри метода, вдруг поменялся и снаружи, и какой тип данных окажется в результате. Разберём по порядку, с примерами кода на встроенном языке.
Синтаксис функции и оператор Возврат
Функция объявляется ключевым словом Функция, заканчивается КонецФункции. Внутри обязателен оператор Возврат с выражением — это и есть отдаваемое значение.
Функция Сумма(Первое, Второе)
Результат = Первое + Второе;
Возврат Результат;
КонецФункции
Вызов пишут в правой части присваивания или внутри выражения:
Итог = Сумма(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 Тогда
Возврат 1; // условие выхода
КонецЕсли;
Возврат Н * Факториал(Н - 1);
КонецФункции
Частые ошибки при возврате значения
- 🔴 Функция без
Возвратна какой-то ветке. Самая частая. Результат —Неопределено, причём молча. Проверяйте все веткиЕсли. - 🔴
Возвратс выражением в процедуре. Синтаксическая ошибка. Нужен результат — объявляйте функцию. - 🟡 Код после
Возврат. Строки ниже сработавшегоВозвратне выполнятся. Если рассчитывали, что выполнятся, — логика сломана. - 🟡 Разные типы на разных ветках. То число, то строка, то
Неопределено— вызывающий код обрастает проверками. Держите один тип. - 🟡 Забыли
Значу входного параметра. Метод меняет переменную вызывающего кода, хотя не должен. Побочный эффект ловится долго. - 🟡 Понадеялись на
Значдля массива. Содержимое объекта всё равно изменится снаружи. Для независимой копии копируйте явно.
Частые вопросы
Можно ли вернуть несколько значений одним оператором Возврат?
Возврат отдаёт одну величину. Но этой величиной может быть структура или массив: Возврат Новый Структура("Код, Имя", Код, Имя);. Либо дополнительные результаты отдают через параметры по ссылке.Что будет, если вызвать функцию как процедуру — отдельной строкой?
Чем `Возврат;` отличается от `Возврат Значение;`?
Возврат; без выражения допустим только в процедуре и означает досрочный выход. Возврат Значение; обязателен в функции и отдаёт величину. В функции пустой Возврат; приведёт к ошибке.Как узнать, какой тип вернула функция?
ТипЗнч(Результат) — она отдаёт тип значения, который сравнивают с Тип("Число"), Тип("Строка") и т. д. Для проверки «вернулось ли что-то» сравнивают с Неопределено.Почему параметр изменился снаружи функции, хотя я менял его внутри?
Знач. Для объектов Знач защищает от переназначения, но не от изменения содержимого.