Как написать процедуру или функцию на встроенном языке 1С

· 10 мин

Содержание

Процедура в 1С выполняет действия и ничего не возвращает, функция — возвращает значение через Возврат. Процедуру объявляют парой Процедура … КонецПроцедуры, функцию — Функция … КонецФункции. Чтобы метод был виден из других модулей, после списка параметров ставят ключевое слово Экспорт.

Всё остальное — передача параметров, директивы &НаСервере и &НаКлиенте, значения по умолчанию, оформление — влияет на то, будет ли код работать быстро и предсказуемо. Синтаксис и поведение одинаковы для всех конфигураций на платформе 8.3: БП, ЗУП, УТ или своей разработки.

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

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

ПризнакПроцедураФункция
ОбъявлениеПроцедура Имя() … КонецПроцедурыФункция Имя() … КонецФункции
Возвращает значениеНетДа, через Возврат Значение
Вызов в выражении (А = Имя())Ошибка компиляцииМожно
Вызов отдельной строкой (Имя();)МожноМожно, результат отбрасывается
Без ВозвратВыполняется до КонецПроцедурыВернёт Неопределено

Когда что выбирать:

  • 🔧 Процедура — если метод что-то меняет: заполняет табличную часть, записывает объект, выводит сообщение, устанавливает видимость элементов формы.
  • 📤 Функция — если метод что-то вычисляет или получает: сумму, ссылку на элемент справочника, результат запроса, признак «можно/нельзя».
  • ⚖️ Спорный случай — метод и меняет данные, и сообщает об успехе. Тогда удобна функция, которая возвращает Истина/Ложь. Но лучше не смешивать: если метод «Записать» вдруг возвращает таблицу, код читать тяжело.

Хорошая проверка: если имя метода — глагол действия («Заполнить», «Пересчитать», «Записать»), это процедура. Если имя описывает результат («СуммаДокумента», «ОстатокТовара», «ЕстьПраваНаИзменение»), это функция.

Синтаксис объявления процедур и функций

Общий шаблон

&НаСервере
Процедура ЗаполнитьТовары(Контрагент, Знач ДатаОстатков = Неопределено) Экспорт

	Перем ТекущаяСтрока;

	Если ДатаОстатков = Неопределено Тогда
		ДатаОстатков = ТекущаяДатаСеанса();
	КонецЕсли;

	// тело процедуры

КонецПроцедуры

Функция СуммаСНДС(Сумма, СтавкаПроцентов) Экспорт

	Возврат Сумма + Сумма * СтавкаПроцентов / 100;

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

Из чего состоит объявление:

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

Экспорт: кто увидит метод

Без Экспорт метод доступен только внутри своего модуля. С ним — из других мест:

  • 📦 Общий модуль — вызов через имя модуля: РаботаСТоварами.ЗаполнитьТовары(...).
  • 🗂️ Модуль менеджера — через менеджер объекта: Справочники.Номенклатура.НайтиПоАртикулу(...).
  • 📄 Модуль объекта — через сам объект: ДокументОбъект.Пересчитать().
  • 🖥️ Модуль формы — экспортные методы формы вызывают через переменную формы, например из другой формы, открытой из неё.

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

Директивы компиляции в модуле формы

В модуле формы код делится на клиентский и серверный. Директива пишется строкой над объявлением:

ДирективаГде выполняетсяДоступ к данным формы
&НаКлиентеНа клиентеЕсть
&НаСервереНа сервереЕсть, форма передаётся на сервер
&НаСервереБезКонтекстаНа сервереНет, только переданные параметры
&НаКлиентеНаСервереБезКонтекстаИ там, и тамНет

Если директиву не указать, метод формы считается серверным (&НаСервере). Ошибиться легко: обработчик кнопки без директивы не сработает как клиентский.

&НаСервереБезКонтекста быстрее, чем &НаСервере: на сервер не уходят данные формы. Если серверному методу нужны только два-три значения, передайте их параметрами и используйте вызов без контекста.

В общих модулях директив нет. Там место выполнения задают флажки в свойствах модуля: «Клиент (управляемое приложение)», «Сервер», «Вызов сервера», «Привилегированный» и другие.

Переменные и область видимости

Переменные внутри метода бывают двух видов:

  • ✍️ Объявленные через Перем — строки Перем идут в начале тела, до остального кода.
  • ⚡ Созданные присваиванием — Итог = 0;. Так пишут чаще всего.

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

Переменные модуля объявляют через Перем в самом начале модуля, до первой процедуры. Они видны всем методам этого модуля, а с Экспорт — и снаружи (в модулях объекта и формы).

Переменные модуля объекта не сохраняются в базе. После записи и повторного получения объекта их значения пропадают. Для передачи данных между событиями записи используйте ДополнительныеСвойства объекта.

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

Параметры процедур: передача по значению и по ссылке

По умолчанию — по ссылке

Если перед параметром нет слова Знач, он передаётся по ссылке. Присвоение параметру нового значения внутри метода меняет переменную в вызывающем коде:

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

А = 5;
Удвоить(А);
// А = 10

Знач — передача по значению

С Знач метод получает копию значения. Что бы ни происходило с параметром внутри, исходная переменная не меняется:

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

А = 5;
Удвоить(А);
// А = 5

Ловушка с массивами, структурами и таблицами

Для объектных типов Знач копирует ссылку на объект, а не сам объект. Поэтому:

Действие внутри методаБез ЗначС Знач
Массив.Добавить(1)Исходный массив изменитсяИсходный массив изменится
Массив = Новый МассивПеременная снаружи укажет на новый массивСнаружи останется старый массив

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

Знач при вызове сервера

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

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

Функция ЦенаТовара(Номенклатура, ВидЦены = Неопределено, Дата = Неопределено) Экспорт

Правила:

  1. Параметры со значением по умолчанию ставят в конец списка.
  2. Значением может быть только литерал: число, строка, дата, Истина, Ложь, Неопределено. Выражение вроде ТекущаяДата() не подойдёт.
  3. При вызове такие параметры можно не указывать: ЦенаТовара(Товар).
  4. Пропустить параметр в середине можно запятой: ЦенаТовара(Товар, , ДатаЦены).

Если параметров набирается много, передайте одну структуру с настройками. Стандарты разработки 1С советуют не раздувать список: ориентир — не больше семи параметров.

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

Возврат значений и выход из метода

Как функция возвращает результат

Возврат Выражение; сразу завершает функцию и отдаёт значение. Код после него не выполняется. Таких операторов в функции может быть несколько:

Функция СтатусОстатка(Количество) Экспорт

	Если Количество < 0 Тогда
		Возврат "Отрицательный";
	ИначеЕсли Количество = 0 Тогда
		Возврат "Нет в наличии";
	КонецЕсли;

	Возврат "В наличии";

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

Если функция дошла до КонецФункции, не встретив Возврат, она вернёт Неопределено. Это частый источник скрытых ошибок: на одной из веток забыли вернуть результат, а вызывающий код спокойно работает с Неопределено.

Ранний выход из процедуры

В процедуре Возврат пишут без значения. Он прерывает выполнение — удобно для проверок в начале метода:

Процедура ПересчитатьСуммы(Объект)

	Если Объект.Товары.Количество() = 0 Тогда
		Возврат;
	КонецЕсли;

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

КонецПроцедуры

Такой приём — «охранное условие» — избавляет от лишнего уровня вложенности Если.

Как вернуть несколько значений

Функция возвращает одно значение. Если нужно несколько, есть два пути:

  • 📋 Вернуть структуру — Новый Структура("Сумма, НДС", 1000, 200). Вызывающий код берёт Результат.Сумма, Результат.НДС.
  • 🔁 Дописать выходные параметры — передать переменные без Знач и заполнить их внутри. Работает, но читается хуже.

Ошибки внутри метода

Если метод не может выполнить задачу, не возвращайте «магическое» значение вроде -1. Для действительно ошибочной ситуации есть ВызватьИсключение "Не указан склад";. Вызывающий код перехватит её в блоке Попытка … Исключение … КонецПопытки или покажет пользователю.

Проверьте себя
Что вернёт функция, если выполнение дошло до КонецФункции без оператора Возврат?
Параметр объявлен как Знач Массив. Внутри метода вызвано Массив.Добавить(5). Что будет с массивом в вызывающем коде?
Метод формы объявлен без директивы компиляции. Где он выполнится?

Асинхронные процедуры и функции

Начиная с платформы 8.3.18 в языке появились асинхронные методы. Их объявляют с ключевым словом Асинх, а внутри ждут результата через Ждать:

&НаКлиенте
Асинх Процедура УдалитьСтроки(Команда)

	Ответ = Ждать ВопросАсинх("Удалить выделенные строки?", РежимДиалогаВопрос.ДаНет);
	Если Ответ = КодВозвратаДиалога.Да Тогда
		УдалитьСтрокиНаСервере();
	КонецЕсли;

КонецПроцедуры

Раньше тот же диалог требовал отдельной процедуры-обработчика и объекта ОписаниеОповещения. Асинхронная запись короче и читается сверху вниз.

Асинхронная функция возвращает не само значение, а объект Обещание. Получить результат можно через Ждать в другом асинхронном методе. Если в вашей базе режим совместимости ниже 8.3.18, Асинх недоступен — остаётся вариант с ОписаниеОповещения.

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

Комментарий-описание

Над экспортными методами пишут комментарий в принятом формате. Конфигуратор и EDT по нему показывают подсказку при вводе:

// Рассчитывает сумму с НДС.
//
// Параметры:
//  Сумма           - Число - сумма без НДС.
//  СтавкаПроцентов - Число - ставка, например 20.
//
// Возвращаемое значение:
//  Число - сумма с НДС.
//
Функция СуммаСНДС(Сумма, СтавкаПроцентов) Экспорт

Имена

  • ✅ Полные слова без сокращений: КоличествоСтрок, а не КолСтр.
  • ✅ Каждое слово с заглавной буквы, без подчёркиваний.
  • ✅ Процедуры называют действием, функции — результатом (см. совет в первом разделе).
  • ❌ Не называйте метод так же, как метод платформы или глобального контекста: Сообщить, Формат, Найти.

Области модуля

Методы группируют инструкциями #Область … #КонецОбласти. В общих модулях типовых конфигураций принят порядок: ПрограммныйИнтерфейс (экспортные методы для всех), СлужебныйПрограммныйИнтерфейс (для своей подсистемы), СлужебныеПроцедурыИФункции (внутренние). Если делаете доработку, повторите эту схему — так код проще сопровождать и обновлять.

Размер и ответственность метода

Один метод — одна задача. Если процедура занимает несколько экранов, у неё почти наверняка несколько обязанностей. Вынесите части в отдельные методы с говорящими именами: основной метод превратится в читаемый «план действий».

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

Ошибки при написании процедур и функций

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

Ещё несколько промахов, которые компилятор не поймает:

  • 🐢 Серверный вызов в цикле с клиента. Каждый вызов &НаСервере — отдельный обмен с сервером. Сто строк — сто обращений. Соберите данные и сделайте один вызов.
  • 🧩 Склейка числа со строкой. "Строк: " + Количество даст число с пробелами-разделителями тысяч. Используйте Формат(Количество, "ЧГ=0") или СтрШаблон("Строк: %1", Количество).
  • 🔄 Удаление элементов в прямом цикле. При удалении строк коллекции идите с конца: Для Сч = 1 По Коллекция.Количество() Цикл Элемент = Коллекция[Коллекция.Количество() - Сч]; ….
  • 🗃️ Изменение типового метода напрямую. Правка процедуры типовой конфигурации усложняет обновления. Доработку лучше вынести в расширение и использовать аннотации &Перед, &После или &Вместо.

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

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