Функция 1С возвращает одно значение: как отдать сразу несколько

· 9 мин

Содержание

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

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

Структура, когда у значений разный смысл

Самый читаемый способ. Каждое значение лежит под своим именем, и на стороне вызова код говорит сам за себя: Результат.НДС, а не Результат[1].

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

Функция РассчитатьСтроку(Количество, Цена, СтавкаНДС)
    Сумма = Количество * Цена;
    НДС = Сумма - Сумма / (1 + СтавкаНДС / 100);

    Возврат Новый Структура("Сумма, НДС, СуммаБезНДС", Сумма, НДС, Сумма - НДС);
КонецФункции

На стороне вызова:

Итог = РассчитатьСтроку(10, 1200, 20);

Сообщить("Сумма: " + Формат(Итог.Сумма, "ЧГ=0"));
Сообщить("НДС: " + Формат(Итог.НДС, "ЧГ=0"));

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

Если полей много или они вычисляются по ходу, структуру удобнее собрать по частям методом Вставить:

Результат = Новый Структура;
Результат.Вставить("Проведено", Истина);
Результат.Вставить("Документ", СсылкаНаДокумент);
Результат.Вставить("ТекстОшибки", "");
Возврат Результат;

Обращаться к полю можно двумя способами — через точку Результат.Проведено и по имени Результат["Проведено"]. Второй нужен, когда имя поля хранится в переменной.

Массив, когда значения однородные

Когда функция возвращает список одинаковых по смыслу элементов — найденные ссылки, коды ошибок, строки для обработки — берут Массив. Порядок элементов вы задаёте сами, доступ по индексу, нумерация с нуля.

Функция НайтиПросроченные(ТаблицаЗадач)
    Результат = Новый Массив;

    Для Каждого Строка Из ТаблицаЗадач Цикл
        Если Строка.Срок < ТекущаяДата() Тогда
            Результат.Добавить(Строка.Задача);
        КонецЕсли;
    КонецЦикла;

    Возврат Результат;
КонецФункции

На стороне вызова перебираете результат циклом:

Просроченные = НайтиПросроченные(Задачи);

Если Просроченные.Количество() = 0 Тогда
    Сообщить("Просроченных задач нет");
Иначе
    Для Каждого Задача Из Просроченные Цикл
        Сообщить(Задача);
    КонецЦикла;
КонецЕсли;

Массивом возвращают и фиксированный набор из двух-трёх значений — например, Возврат Новый Массив(ДатаНачала, ДатаОкончания). Так писать короче, но читать тяжелее: на стороне вызова Период[0] и Период[1] ничего не говорят о смысле. Для разнородных значений выбирайте Структура, а не Массив с индексами — через полгода этот код будете разбирать вы же.

Нужен неизменяемый результат — верните Новый ФиксированныйМассив(Результат). Его нельзя дополнить или перезаписать, что защищает от случайной правки на стороне вызова.

Параметры по ссылке: функция вообще не нужна

В 1С параметры по умолчанию передаются по ссылке. Это значит: если внутри процедуры изменить параметр, изменение увидит и вызывающий код. Так результат «возвращают» прямо через аргументы, а сама подпрограмма может быть процедурой без Возврат.

Процедура РассчитатьИтоги(ТаблицаСтрок, СуммаВсего, КоличествоСтрок)
    СуммаВсего = 0;
    КоличествоСтрок = 0;

    Для Каждого Строка Из ТаблицаСтрок Цикл
        СуммаВсего = СуммаВсего + Строка.Сумма;
        КоличествоСтрок = КоличествоСтрок + 1;
    КонецЦикла;
КонецПроцедуры

Вызов: переменные заводите заранее, процедура их заполнит.

Сумма = 0;
Количество = 0;
РассчитатьИтоги(Таблица, Сумма, Количество);
Сообщить("Строк: " + Количество + ", на сумму " + Формат(Сумма, "ЧГ=0"));

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

Частый приём — функция возвращает признак успеха, а данные отдаёт через параметр:

Функция ПопытатьсяНайтиКонтрагента(ИНН, НайденныйКонтрагент)
    Отбор = Новый Структура("ИНН", ИНН);
    Выборка = Справочники.Контрагенты.Выбрать(, , Отбор);

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

Передача по ссылке делает код менее очевидным: глядя на вызов РассчитатьИтоги(Таблица, Сумма, Количество), не видно, что Сумма и Количество будут перезаписаны. Структура-результат в этом смысле честнее. Передачу по ссылке держите для случаев, когда функция уже занята возвратом флага.

Соответствие, список и таблица значений — когда данных больше

Для наборов «ключ — значение» с произвольными ключами (в том числе не строковыми) Структура не подойдёт — её ключи обязаны быть валидными именами. Здесь берут Соответствие:

Функция ОстаткиПоСкладам(Номенклатура)
    Результат = Новый Соответствие; // Ключ — склад, значение — остаток
    // ... заполнение из запроса: Результат.Вставить(Склад, Остаток) ...
    Возврат Результат;
КонецФункции

Читать соответствие на стороне вызова нужно через Получить — он вернёт Неопределено, если ключа нет, и исключения не будет:

Остатки = ОстаткиПоСкладам(Товар);

Остаток = Остатки.Получить(НужныйСклад);
Если Остаток = Неопределено Тогда
    Сообщить("По этому складу остатков нет");
Иначе
    Сообщить("Остаток: " + Остаток);
КонецЕсли;

Другие контейнеры под конкретные задачи:

  • 📋 СписокЗначений — когда нужны пары «значение + представление» и, возможно, пометки, как в форме выбора.
  • 📊 ТаблицаЗначений — табличный результат с колонками: функция формирует её из запроса (Результат.Выгрузить()) и отдаёт целиком.
  • 🔒 ФиксированнаяСтруктура / ФиксированноеСоответствие — неизменяемые версии, если результат не должен меняться после возврата.

Смысл один и тот же: Возврат отдаёт одну ссылку на объект-контейнер, а внутри него — сколько угодно значений.

Какой способ выбрать

СпособКогда братьДоступ на стороне вызоваМинус
Структура2–7 разных по смыслу значений.Сумма, .НДС — по имениключи только валидные имена
Массиводнородный список, число элементов заранее неизвестно[0], перебор цикломиндексы не раскрывают смысл
Параметры по ссылкефункция уже возвращает флаг успехаобычные переменныенеочевидно при чтении вызова
Соответствиепары «ключ — значение», ключ не имя.Получить(Ключ)нет доступа через точку
ТаблицаЗначенийтабличные данные с колонкамикак по таблицетяжелее примитивов

Практическое правило: по умолчанию — Структура. Она читается лучше всех и расширяется без боли — добавили новое поле, старый код вызова не сломался. На Массив переходите, когда элементы однородные и их число заранее неизвестно.

Что должна вернуть функция?

Результат рискованной операции: флаг успеха и текст ошибки

Когда внутри функции есть операция, способная упасть — запрос к внешнему источнику, запись объекта, разбор строки, — результат удобно собрать так, чтобы вызывающий код узнал и исход, и причину. Исключение ловят через Попытка, а наружу отдают структуру с полями Успех, Данные и ТекстОшибки.

Функция ЗаписатьДокумент(ДокументОбъект)
    Результат = Новый Структура("Успех, Ссылка, ТекстОшибки", Ложь, Неопределено, "");

    Попытка
        ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
        Результат.Успех = Истина;
        Результат.Ссылка = ДокументОбъект.Ссылка;
    Исключение
        Результат.ТекстОшибки = ОписаниеОшибки();
    КонецПопытки;

    Возврат Результат;
КонецФункции

На стороне вызова проверка в одну ветку:

Итог = ЗаписатьДокумент(Объект);
Если НЕ Итог.Успех Тогда
    Сообщить("Не удалось записать: " + Итог.ТекстОшибки);
    Возврат;
КонецЕсли;

Такой контракт удобен тем, что формат результата один при любом исходе — не нужно гадать, вернулась ссылка, Неопределено или строка ошибки. Метод ОписаниеОшибки() внутри блока Исключение даёт текст последней ошибки; в серверном коде его дополнительно пишут в журнал регистрации.

Не глушите исключение молча — пустой блок Исключение без записи причины превращает ошибку в невидимую. Как минимум положите текст в поле результата или запишите в журнал регистрации.

Ошибки, на которых спотыкаются чаще всего

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

Склад = Неопределено;
Если Результат.Свойство("Склад", Склад) Тогда
    // Склад получен
КонецЕсли;

Свойство возвращает Истина/Ложь и, если второй аргумент передан, кладёт в него значение. Это безопаснее, чем ловить исключение.

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

Перепутан порядок значений в конструкторе структуры. Новый Структура("Сумма, НДС", НДС, Сумма) — имена и значения разъехались, ошибки не будет, но данные перепутаются местами. При длинном списке полей собирайте структуру через Вставить — там имя и значение стоят рядом и спутать их труднее.

Возврат Неопределено вместо пустого контейнера. Если при «ничего не найдено» функция возвращает Неопределено, вызывающий код упадёт на Результат.Количество(). Возвращайте пустой Массив или пустую Структура — тогда перебор просто ничего не сделает, без лишних проверок на стороне вызова.

Изменение возвращённого объекта портит общие данные. Контейнер передаётся по ссылке. Если функция вернула ссылку на объект, который используется ещё где-то, правка результата заденет и тот объект. Когда это нежелательно — возвращайте фиксированную версию (ФиксированныйМассив, ФиксированнаяСтруктура) или копию.

Как писать, чтобы код не разваливался

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

  1. Договоритесь о составе структуры-результата и опишите его в комментарии у объявления функции: какие ключи, что в них. Тот, кто вызывает функцию, не должен открывать её тело, чтобы узнать имена полей.
  2. Один формат результата на функцию. Не возвращайте то структуру, то Неопределено, то строку в зависимости от условий — вызывающий код вынужден проверять тип. Пусть при любом исходе возвращается структура, а исход лежит в её поле (Успех, ТекстОшибки).
  3. Флаг плюс данные — через структуру, а не через Возврат + параметр. Возврат Новый Структура("Успех, Данные, Ошибка", ...) читается лучше, чем функция с флагом и выходным параметром. Исключение — когда данные объёмные и копировать их в структуру накладно.
  4. Фиксируйте неизменяемый результат. Справочные наборы, которые не должны меняться после возврата, отдавайте фиксированными коллекциями — это ловит случайные правки на этапе выполнения.
  5. Не плодите выходных параметров. Если их набирается больше двух-трёх, функцию уже тяжело читать и вызывать: заведите структуру-результат. Один возвращаемый контейнер почти всегда понятнее, чем длинный хвост аргументов, часть из которых передаётся только ради заполнения.

В типовых конфигурациях (БП 3.0, ЗУП 3.1, УТ 11) стандарт один: функции бизнес-логики почти всегда возвращают Структура с предсказуемым набором полей. Посмотрите на любую процедуру проведения — результат идёт структурой с флагом отказа и дополнительными данными. Это удобная модель для собственного кода.

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

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