Пишем функцию на встроенном языке 1С: от синтаксиса до возврата значения
Содержание
Функция в 1С объявляется парой операторов Функция и КонецФункции, а наружу отдаёт результат через Возврат. Минимальный рабочий вариант выглядит так:
Функция СуммаСНДС(СуммаБезНДС, Ставка)
Возврат СуммаБезНДС + СуммаБезНДС * Ставка / 100;
КонецФункции
Вызвали СуммаСНДС(1000, 20) — получили 1200. Дальше разберём каждый элемент этой конструкции: чем функция отличается от процедуры, как объявлять и передавать параметры, что делает Возврат, где живут переменные и как ловить ошибки внутри вычислений. Код пишется в Конфигураторе или в режиме «1С:Предприятие» через расширения — синтаксис один и тот же на платформе 8.3.
Функция и процедура: когда что писать
Встроенный язык 1С знает два вида методов — функции и процедуры. Разница одна, но принципиальная: функция обязана вернуть значение, процедура — нет.
- Функция считает и отдаёт результат: сумму, строку, ссылку, таблицу значений, булево. Её вызов можно подставить в выражение:
Итог = СуммаСНДС(Цена, 20);. - Процедура выполняет действие без возврата: проводит документ, пишет в регистр, показывает сообщение. Её вызывают отдельной строкой:
ПоказатьПредупреждение(, "Готово");.
Отсюда простое правило выбора. Нужно получить значение и использовать его дальше — пишите функцию. Нужно просто «сделать» — пишите процедуру.
Если метод что-то считает, но результат нигде не используется, это всё равно процедура. Не превращайте в функцию только ради красоты — чужой код станет труднее читать.
Процедура тоже может содержать Возврат, но без значения — он лишь досрочно прерывает выполнение:
Процедура ОбработатьСтроку(Строка)
Если Не ЗначениеЗаполнено(Строка.Номенклатура) Тогда
Возврат; // вышли, дальше не идём
КонецЕсли;
// основная логика
КонецПроцедуры
А вот Возврат без значения внутри функции — ошибка компиляции: функция всегда обязана вернуть результат по любой ветке кода.
Синтаксис объявления: имя, скобки, тело
Полная форма объявления такая:
&НаСервере
Функция ПолучитьОстаток(Номенклатура, Склад, Дата) Экспорт
// тело функции
Возврат Остаток;
КонецФункции
Разберём по частям:
- Директива компиляции (
&НаСервере,&НаКлиенте,&НаСервереБезКонтекстаи др.) — пишется перед объявлением в модулях управляемой формы и в общих модулях с соответствующими флагами. В модуле объекта или менеджера директива не нужна. - Ключевое слово
Функцияоткрывает объявление. - Имя — на русском или латинице, без пробелов, начинается с буквы. Договоритесь в команде об одном языке и пишите имена осмысленно:
РассчитатьСкидку, а неРасч1. - Круглые скобки с параметрами — обязательны, даже если параметров нет:
Функция ТекущаяДата(). Экспорт— делает функцию доступной извне модуля. Без него функция видна только внутри своего модуля.- Тело — любой код, обязательно с
Возврат. КонецФункциизакрывает объявление.
Ключевое слово Экспорт — то, из-за чего новички получают ошибку «Процедура или функция с указанным именем не определена». Если вы вызываете метод общего модуля снаружи, а он объявлен без Экспорт, платформа его «не видит». Добавьте Экспорт — и вызов ОбщийМодуль.ПолучитьОстаток(...) заработает.
Как передавать параметры: по ссылке и по значению
По умолчанию параметры в 1С передаются по ссылке. Это значит: если внутри функции изменить параметр, изменение увидит и вызывающий код.
Функция ДобавитьПрефикс(Строка)
Строка = "ТД-" + Строка; // меняем параметр
Возврат Строка;
КонецФункции
Чтобы защитить переданную переменную от изменений, перед именем параметра ставят Знач — тогда функция работает с копией:
Функция ДобавитьПрефикс(Знач Строка)
Строка = "ТД-" + Строка; // внешняя переменная не пострадает
Возврат Строка;
КонецФункции
Для чисел, строк, дат и булевых всегда пишите Знач, если не собираетесь намеренно менять параметр снаружи. Это убирает скрытые побочные эффекты и делает код предсказуемым.
Параметрам можно задавать значения по умолчанию через =. Такой параметр при вызове можно не указывать:
Функция СформироватьНомер(Префикс, Длина = 9)
Возврат Префикс + Формат(ТекущийНомер, "ЧЦ=" + Длина);
КонецФункции
Вызовы СформироватьНомер("ТД") и СформироватьНомер("ТД", 11) оба корректны. Параметры со значением по умолчанию всегда ставятся в конце списка.
Оператор Возврат и типы результата
Возврат завершает функцию и отдаёт значение наружу. Выполнение останавливается на этой строке — код после Возврат в той же ветке уже не сработает.
Функция может возвращать значение любого типа платформы:
| Что возвращаем | Пример выражения |
|---|---|
| Число | Возврат Цена * Количество; |
| Строку | Возврат "Документ проведён"; |
| Булево | Возврат Остаток > 0; |
| Ссылку | Возврат Справочники.Контрагенты.ПустаяСсылка(); |
| Структуру | Возврат Новый Структура("Код, Наименование", 1, "Товар"); |
| Таблицу значений | Возврат РезультатЗапроса.Выгрузить(); |
Частая задача — функция с несколькими ветками. Проследите, чтобы Возврат был на каждой ветке, иначе при неохваченном условии платформа вернёт Неопределено, а вы получите неожиданную ошибку дальше по коду:
Функция СтавкаНДС(ВидТовара)
Если ВидТовара = "Льготный" Тогда
Возврат 10;
ИначеЕсли ВидТовара = "Экспорт" Тогда
Возврат 0;
Иначе
Возврат 20; // ветка «по умолчанию» обязательна
КонецЕсли;
КонецФункции
Если убрать ветку Иначе, функция при неизвестном виде товара вернёт Неопределено, и последующее Сумма * СтавкаНДС(...) / 100 упадёт с ошибкой преобразования типа.
Где живут переменные: локальные и модульные
Переменные, которые вы объявляете или которым присваиваете значение внутри функции, — локальные. Они создаются при входе в функцию и исчезают при выходе. Соседняя функция их не видит.
Функция Пример()
Сумма = 100; // локальная, только внутри Пример()
Возврат Сумма;
КонецФункции
Явно объявить переменную можно оператором Перем — он ставится в самом начале тела, до первого исполняемого оператора:
Функция Расчёт()
Перем Итог, Процент;
Итог = 0;
// ...
Возврат Итог;
КонецФункции
Чтобы переменная была видна всем методам модуля, её объявляют в начале модуля, до первой процедуры или функции:
Перем КэшКурсов; // модульная переменная
Функция КурсВалюты(Валюта)
// КэшКурсов доступна здесь и в любом другом методе модуля
КонецФункции
Если к модульной переменной добавить Экспорт (Перем КэшКурсов Экспорт;), она станет доступна и снаружи — через имя модуля. Пользоваться этим стоит осторожно: глобальное состояние усложняет отладку.
Главный принцип области видимости — держите данные как можно локальнее. Если значение нужно только одной функции, не выносите его в модуль. Чем уже область видимости, тем проще найти, кто и где меняет переменную.
Ловим ошибки: Попытка, Исключение, ВызватьИсключение
Внутри функции часто есть операции, которые могут не выполниться: деление, преобразование типа, обращение к внешнему ресурсу. Чтобы ошибка не обрушила весь алгоритм, её оборачивают в Попытка … Исключение … КонецПопытки:
Функция БезопасноеДеление(Делимое, Делитель)
Попытка
Возврат Делимое / Делитель;
Исключение
ЗаписьЖурналаРегистрации("Расчёт",
УровеньЖурналаРегистрации.Ошибка, , ,
ОписаниеОшибки());
Возврат 0;
КонецПопытки;
КонецФункции
В блоке Исключение доступны две функции для разбора причины:
ОписаниеОшибки()— текст ошибки строкой, удобно писать в журнал регистрации.ИнформацияОбОшибке()— объект с детальной структурой ошибки (для программного анализа).
Иногда ошибку нужно сгенерировать самому — например, когда функция получила недопустимые данные. Для этого есть ВызватьИсключение:
Функция КорникКвадратный(Число)
Если Число < 0 Тогда
ВызватьИсключение "Нельзя извлечь корень из отрицательного числа";
КонецЕсли;
Возврат Pow(Число, 0.5);
КонецФункции
Такое исключение «всплывёт» наверх и будет поймано ближайшим внешним блоком Попытка. Если его не поймать — платформа покажет сообщение пользователю и прервёт операцию.
Не «глотайте» ошибки пустым блоком Исключение. Как минимум пишите причину в журнал регистрации — иначе сбой останется невидимым, а данные молча испортятся.
Как писать функции быстрее и чище
Производительность в 1С чаще упирается не в сам язык, а в то, что функция делает с базой. Несколько приёмов, которые реально ускоряют код.
Не ставьте запрос внутрь цикла. Классическая ошибка — обходить таблицу и на каждой строке выполнять Запрос.Выполнить(). Для 500 строк это 500 обращений к СУБД. Соберите все нужные данные одним запросом до цикла, а в цикле читайте из готовой таблицы значений или соответствия.
Используйте соответствие (Соответствие) для поиска. Если внутри цикла нужно много раз находить значение по ключу, заранее сложите данные в Новый Соответствие — поиск по нему почти мгновенный, в отличие от перебора таблицы в цикле.
Передавайте большие коллекции по ссылке. Таблицу значений на тысячи строк не нужно помечать Знач, если функция её не меняет, — лишнее копирование впустую тратит память.
Разбивайте длинные функции. Метод на 200 строк тяжело читать и отлаживать. Выделяйте логические куски в отдельные функции с говорящими именами — код станет самодокументируемым.
Выносите общую логику в общие модули. Если одна и та же функция нужна в нескольких местах, её место — в общем модуле с флагом нужного контекста (сервер/клиент) и ключевым словом Экспорт. Копировать код по формам — прямой путь к рассинхрону.
Проверить себя на базовых правилах синтаксиса можно так:
Собираем всё вместе: функция на реальной задаче
Покажем, как перечисленные правила работают в одном методе. Задача: по ссылке на контрагента вернуть строку с его ИНН и КПП, а если данные не заполнены — понятную заглушку. Функцию положим в общий модуль, вызывать будем и с сервера, и из отчётов.
// В общем модуле с флагом «Сервер»
Функция РеквизитыКонтрагента(Знач Контрагент) Экспорт
Если Не ЗначениеЗаполнено(Контрагент) Тогда
Возврат "Контрагент не выбран";
КонецЕсли;
Попытка
Данные = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(
Контрагент, "ИНН, КПП");
Исключение
ЗаписьЖурналаРегистрации("Контрагенты",
УровеньЖурналаРегистрации.Ошибка, , ,
ОписаниеОшибки());
Возврат "Не удалось прочитать реквизиты";
КонецПопытки;
Если ПустаяСтрока(Данные.ИНН) Тогда
Возврат "ИНН не заполнен";
КонецЕсли;
Результат = "ИНН " + Данные.ИНН;
Если Не ПустаяСтрока(Данные.КПП) Тогда
Результат = Результат + ", КПП " + Данные.КПП;
КонецЕсли;
Возврат Результат;
КонецФункции
Пройдёмся по тому, что здесь собрано воедино:
- параметр помечен
Знач— внешнюю ссылку функция не меняет; Экспортоткрывает вызов снаружи модуля;- на каждой логической ветке есть свой
Возврат— функция никогда не отдаётНеопределено; - потенциально опасное чтение реквизита обёрнуто в
Попытка, причина сбоя уходит в журнал регистрации; Результат— локальная переменная, живёт только внутри вызова.
Вызов из любого места выглядит одинаково коротко: Строка = ОбщийМодуль.РеквизитыКонтрагента(ВыбранныйКонтрагент);. Читающий код сразу понимает, что метод отдаёт строку и ничего не портит.
Частые ошибки при создании функций
Эти промахи ловят новичков чаще всего — и все они не про логику задачи, а про сам механизм функций.
Экспорт либо не совпал контекст вызова (клиент дёргает серверный метод напрямую). Добавьте Экспорт, проверьте флаги модуля.Если/ИначеЕсли без Возврат. При неохваченном условии вернётся Неопределено, и следующая арифметика упадёт. Добавьте ветку Иначе с Возврат по умолчанию.Знач, если менять внешнюю переменную не нужно.Перем, либо (лучше) передавайте её параметром.Функция Ф(А = 1, Б) не скомпилируется. Все параметры со значениями по умолчанию ставятся в конце списка.Частые вопросы
Можно ли вызвать функцию до того, как она объявлена ниже по тексту модуля?
Чем отличается `&НаСервереБезКонтекста` от `&НаСервере`?
&НаСервере имеет доступ к данным формы (реквизитам, таблицам), но передаёт их на сервер и обратно — это дороже. &НаСервереБезКонтекста работает без контекста формы, вызывается быстрее и подходит для расчётов, которым форма не нужна. По возможности выбирайте внеконтекстный вариант.Сколько значений может вернуть одна функция?
Структуру или Массив: Возврат Новый Структура("Сумма, НДС", Сумма, НДС);. Вызывающий код прочитает Результат.Сумма и Результат.НДС.Почему функция общего модуля не вызывается из формы?
Экспорт, либо не совпадает контекст компиляции — например, из клиентского кода вызывается серверный метод напрямую. Проверьте Экспорт и флаги общего модуля (Клиент/Сервер/Вызов сервера).Можно ли задать параметру значение по умолчанию в виде пустого массива или структуры?
Неопределено). Для «пустого массива по умолчанию» поставьте = Неопределено, а внутри функции создайте массив, если параметр не передан.