Пишем функцию на встроенном языке 1С: от синтаксиса до возврата значения

· 10 мин

Содержание

Функция в 1С объявляется парой операторов Функция и КонецФункции, а наружу отдаёт результат через Возврат. Минимальный рабочий вариант выглядит так:

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

Вызвали СуммаСНДС(1000, 20) — получили 1200. Дальше разберём каждый элемент этой конструкции: чем функция отличается от процедуры, как объявлять и передавать параметры, что делает Возврат, где живут переменные и как ловить ошибки внутри вычислений. Код пишется в Конфигураторе или в режиме «1С:Предприятие» через расширения — синтаксис один и тот же на платформе 8.3.

Функция и процедура: когда что писать

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

  • Функция считает и отдаёт результат: сумму, строку, ссылку, таблицу значений, булево. Её вызов можно подставить в выражение: Итог = СуммаСНДС(Цена, 20);.
  • Процедура выполняет действие без возврата: проводит документ, пишет в регистр, показывает сообщение. Её вызывают отдельной строкой: ПоказатьПредупреждение(, "Готово");.

Отсюда простое правило выбора. Нужно получить значение и использовать его дальше — пишите функцию. Нужно просто «сделать» — пишите процедуру.

Если метод что-то считает, но результат нигде не используется, это всё равно процедура. Не превращайте в функцию только ради красоты — чужой код станет труднее читать.

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

Процедура ОбработатьСтроку(Строка)
    Если Не ЗначениеЗаполнено(Строка.Номенклатура) Тогда
        Возврат; // вышли, дальше не идём
    КонецЕсли;
    // основная логика
КонецПроцедуры

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

Синтаксис объявления: имя, скобки, тело

Полная форма объявления такая:

&НаСервере
Функция ПолучитьОстаток(Номенклатура, Склад, Дата) Экспорт
    // тело функции
    Возврат Остаток;
КонецФункции

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

  1. Директива компиляции (&НаСервере, &НаКлиенте, &НаСервереБезКонтекста и др.) — пишется перед объявлением в модулях управляемой формы и в общих модулях с соответствующими флагами. В модуле объекта или менеджера директива не нужна.
  2. Ключевое слово Функция открывает объявление.
  3. Имя — на русском или латинице, без пробелов, начинается с буквы. Договоритесь в команде об одном языке и пишите имена осмысленно: РассчитатьСкидку, а не Расч1.
  4. Круглые скобки с параметрами — обязательны, даже если параметров нет: Функция ТекущаяДата().
  5. Экспорт — делает функцию доступной извне модуля. Без него функция видна только внутри своего модуля.
  6. Тело — любой код, обязательно с Возврат.
  7. КонецФункции закрывает объявление.

Ключевое слово Экспорт — то, из-за чего новички получают ошибку «Процедура или функция с указанным именем не определена». Если вы вызываете метод общего модуля снаружи, а он объявлен без Экспорт, платформа его «не видит». Добавьте Экспорт — и вызов ОбщийМодуль.ПолучитьОстаток(...) заработает.

Как передавать параметры: по ссылке и по значению

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

Функция ДобавитьПрефикс(Строка)
    Строка = "ТД-" + Строка; // меняем параметр
    Возврат Строка;
КонецФункции

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

Функция ДобавитьПрефикс(Знач Строка)
    Строка = "ТД-" + Строка; // внешняя переменная не пострадает
    Возврат Строка;
КонецФункции

Для чисел, строк, дат и булевых всегда пишите Знач, если не собираетесь намеренно менять параметр снаружи. Это убирает скрытые побочные эффекты и делает код предсказуемым.

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

Функция СформироватьНомер(Префикс, Длина = 9)
    Возврат Префикс + Формат(ТекущийНомер, "ЧЦ=" + Длина);
КонецФункции

Вызовы СформироватьНомер("ТД") и СформироватьНомер("ТД", 11) оба корректны. Параметры со значением по умолчанию всегда ставятся в конце списка.

Оператор Возврат и типы результата

Возврат завершает функцию и отдаёт значение наружу. Выполнение останавливается на этой строке — код после Возврат в той же ветке уже не сработает.

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

Что возвращаемПример выражения
ЧислоВозврат Цена * Количество;
СтрокуВозврат "Документ проведён";
БулевоВозврат Остаток > 0;
СсылкуВозврат Справочники.Контрагенты.ПустаяСсылка();
СтруктуруВозврат Новый Структура("Код, Наименование", 1, "Товар");
Таблицу значенийВозврат РезультатЗапроса.Выгрузить();

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

Функция СтавкаНДС(ВидТовара)
    Если ВидТовара = "Льготный" Тогда
        Возврат 10;
    ИначеЕсли ВидТовара = "Экспорт" Тогда
        Возврат 0;
    Иначе
        Возврат 20; // ветка «по умолчанию» обязательна
    КонецЕсли;
КонецФункции

Если убрать ветку Иначе, функция при неизвестном виде товара вернёт Неопределено, и последующее Сумма * СтавкаНДС(...) / 100 упадёт с ошибкой преобразования типа.

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

Переменные, которые вы объявляете или которым присваиваете значение внутри функции, — локальные. Они создаются при входе в функцию и исчезают при выходе. Соседняя функция их не видит.

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

Явно объявить переменную можно оператором Перем — он ставится в самом начале тела, до первого исполняемого оператора:

Функция Расчёт()
    Перем Итог, Процент;
    Итог = 0;
    // ...
    Возврат Итог;
КонецФункции

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

Перем КэшКурсов; // модульная переменная

Функция КурсВалюты(Валюта)
    // КэшКурсов доступна здесь и в любом другом методе модуля
КонецФункции

Если к модульной переменной добавить Экспорт (Перем КэшКурсов Экспорт;), она станет доступна и снаружи — через имя модуля. Пользоваться этим стоит осторожно: глобальное состояние усложняет отладку.

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

Ловим ошибки: Попытка, Исключение, ВызватьИсключение

Внутри функции часто есть операции, которые могут не выполниться: деление, преобразование типа, обращение к внешнему ресурсу. Чтобы ошибка не обрушила весь алгоритм, её оборачивают в Попытка … Исключение … КонецПопытки:

Функция БезопасноеДеление(Делимое, Делитель)
    Попытка
        Возврат Делимое / Делитель;
    Исключение
        ЗаписьЖурналаРегистрации("Расчёт",
            УровеньЖурналаРегистрации.Ошибка, , ,
            ОписаниеОшибки());
        Возврат 0;
    КонецПопытки;
КонецФункции

В блоке Исключение доступны две функции для разбора причины:

  • ОписаниеОшибки() — текст ошибки строкой, удобно писать в журнал регистрации.
  • ИнформацияОбОшибке() — объект с детальной структурой ошибки (для программного анализа).

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

Функция КорникКвадратный(Число)
    Если Число < 0 Тогда
        ВызватьИсключение "Нельзя извлечь корень из отрицательного числа";
    КонецЕсли;
    Возврат Pow(Число, 0.5);
КонецФункции

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

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

Как писать функции быстрее и чище

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

Не ставьте запрос внутрь цикла. Классическая ошибка — обходить таблицу и на каждой строке выполнять Запрос.Выполнить(). Для 500 строк это 500 обращений к СУБД. Соберите все нужные данные одним запросом до цикла, а в цикле читайте из готовой таблицы значений или соответствия.

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

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

Разбивайте длинные функции. Метод на 200 строк тяжело читать и отлаживать. Выделяйте логические куски в отдельные функции с говорящими именами — код станет самодокументируемым.

Выносите общую логику в общие модули. Если одна и та же функция нужна в нескольких местах, её место — в общем модуле с флагом нужного контекста (сервер/клиент) и ключевым словом Экспорт. Копировать код по формам — прямой путь к рассинхрону.

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

Проверьте себя
Функция обязана содержать оператор Возврат со значением?
Что делает ключевое слово Знач перед параметром?
Где объявляют переменную, видимую всем методам модуля?
Чем поймать ошибку деления на ноль внутри функции?

Собираем всё вместе: функция на реальной задаче

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

// В общем модуле с флагом «Сервер»
Функция РеквизитыКонтрагента(Знач Контрагент) Экспорт

    Если Не ЗначениеЗаполнено(Контрагент) Тогда
        Возврат "Контрагент не выбран";
    КонецЕсли;

    Попытка
        Данные = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(
            Контрагент, "ИНН, КПП");
    Исключение
        ЗаписьЖурналаРегистрации("Контрагенты",
            УровеньЖурналаРегистрации.Ошибка, , ,
            ОписаниеОшибки());
        Возврат "Не удалось прочитать реквизиты";
    КонецПопытки;

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

    Результат = "ИНН " + Данные.ИНН;
    Если Не ПустаяСтрока(Данные.КПП) Тогда
        Результат = Результат + ", КПП " + Данные.КПП;
    КонецЕсли;

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

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

Пройдёмся по тому, что здесь собрано воедино:

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

Вызов из любого места выглядит одинаково коротко: Строка = ОбщийМодуль.РеквизитыКонтрагента(ВыбранныйКонтрагент);. Читающий код сразу понимает, что метод отдаёт строку и ничего не портит.

Частые ошибки при создании функций

Эти промахи ловят новичков чаще всего — и все они не про логику задачи, а про сам механизм функций.

Диагностика: выберите симптом

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

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