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

· 11 мин

Содержание

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

Ниже — синтаксис объявления, как работают модификатор Экспорт и общий модуль со свойством «Глобальный», чем передача Знач отличается от передачи по ссылке, когда писать процедуру, а когда функцию, и какие ошибки чаще всего тормозят код. Примеры — на встроенном языке платформы 8.3.

Синтаксис объявления и первый вызов

Минимальная процедура выглядит так:

Процедура ПоказатьСообщение()
    Сообщить("Готово");
КонецПроцедуры

Вызов — имя и круглые скобки, даже если параметров нет:

ПоказатьСообщение();

Разберём элементы объявления:

  • 🔹 ключевое слово Процедура открывает блок, КонецПроцедуры — закрывает;
  • 🔹 после имени обязательны круглые скобки — в них перечисляются параметры;
  • 🔹 тело располагается между открывающей и закрывающей строкой;
  • 🔹 точка с запятой после КонецПроцедуры не ставится.

Имя процедуры пишут без пробелов, обычно в стиле «КаждоеСловоСБольшойБуквы». Имена методов, встроенных в платформу, занимать нельзя — транслятор выдаст конфликт.

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

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

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

Внутри тела можно досрочно выйти оператором Возврат без значения — остаток кода выполнен не будет:

Процедура ОбработатьСтроку(Строка)
    Если Строка = "" Тогда
        Возврат;
    КонецЕсли;
    // основная логика только для непустой строки
    Сообщить("Обрабатываю: " + Строка);
КонецПроцедуры

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

Модификатор Экспорт и общий модуль «Глобальный»

По умолчанию процедура видна только внутри своего модуля. Чтобы её можно было вызвать снаружи, после списка параметров ставят ключевое слово Экспорт:

Процедура ЗаписатьОтметку(Текст) Экспорт
    Сообщить(ТекущаяДата() + ": " + Текст);
КонецПроцедуры

Где именно станет доступна экспортная процедура — зависит от типа модуля:

МодульКак вызвать экспортную процедуру
Общий модульИмяМодуля.ЗаписатьОтметку("…")
Модуль менеджера объектаСправочники.Номенклатура.Метод()
Модуль формычерез реквизит формы, из своего контекста

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

// обычный общий модуль:
РаботаСТекстом.ОчиститьПробелы(Строка);

// глобальный общий модуль — имя модуля не нужно:
ОчиститьПробелы(Строка);

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

Имена экспортных процедур в глобальных модулях не должны пересекаться между собой и со встроенными методами платформы — иначе получите неоднозначность вызова.

Где живут процедуры: типы модулей

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

  • 🔹 Модуль формы — обработчики элементов и команд: ПриИзмененииПоля, нажатия кнопок, открытие и закрытие формы. Здесь обязательны директивы компиляции.
  • 🔹 Модуль объекта (справочника, документа) — события самого объекта: ПриЗаписи, ОбработкаПроведения у документа, ОбработкаЗаполнения. Процедуры видят реквизиты объекта напрямую.
  • 🔹 Модуль менеджера — методы, относящиеся к объекту в целом, а не к одному экземпляру; удобны для экспортных функций-«конструкторов».
  • 🔹 Общий модуль — переиспользуемые процедуры и функции. Флаги модуля (Клиент, Сервер, Вызов сервера, Глобальный) определяют контекст и видимость.
  • 🔹 Модуль приложения и модуль сеанса — события запуска и завершения системы.

Простое правило: код, который нужен в нескольких местах, выносите в общий модуль с Экспорт. Код, привязанный к конкретной форме или объекту, держите в их собственных модулях — так его проще сопровождать.

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

Параметры: по ссылке и по значению (Знач)

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

Процедура Обнулить(Сумма)
    Сумма = 0;
КонецПроцедуры

Итого = 500;
Обнулить(Итого);
// теперь Итого = 0

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

Процедура Обнулить(Знач Сумма)
    Сумма = 0;
КонецПроцедуры

Итого = 500;
Обнулить(Итого);
// Итого по-прежнему 500

Есть тонкость с объектами. Знач защищает от переприсваивания самой переменной, но не «замораживает» содержимое ссылочного объекта. Если в процедуру передали таблицу значений с модификатором Знач, присвоить Таблица = Новый ТаблицаЗначений внутри уже не получится повлиять на оригинал, а вот Таблица.Добавить() всё равно добавит строку в исходную таблицу — вы работаете с тем же объектом.

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

Процедура Логировать(Текст, Уровень = "Инфо")
    Сообщить("[" + Уровень + "] " + Текст);
КонецПроцедуры

Логировать("Старт");              // Уровень = "Инфо"
Логировать("Сбой", "Ошибка");     // Уровень = "Ошибка"
  • ✅ параметры со значением по умолчанию ставьте в конце списка;
  • ✅ большие коллекции без необходимости не передавайте через Знач — создание копии на тысячах строк заметно тормозит;
  • ✅ если процедура не должна портить входные данные, Знач для простых типов (число, строка, дата) — хорошая страховка.

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

Параметры = Новый Структура;
Параметры.Вставить("Дата", ТекущаяДата());
Параметры.Вставить("Комментарий", "Загрузка");
ЗаписатьОтметку(Параметры);

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

Разница одна, но принципиальная: функция возвращает значение, процедура — нет. Отсюда и всё остальное.

// Процедура: выполнила действие и завершилась
Процедура ВывестиИтог(Сумма)
    Сообщить("Итого: " + Сумма);
КонецПроцедуры

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

Для суммы 1000 при ставке 20% функция вернёт 1000 + 1000 * 20 / 100 = 1200. Результат можно присвоить переменной или использовать в выражении:

Итог = СуммаСНДС(1000);   // 1200

Короткая шпаргалка, что выбрать:

Что нужноПроцедура или функция
Вывести сообщение, записать данные, провести документпроцедура
Посчитать и вернуть число, строку, ссылкуфункция
Проверить условие и вернуть Истина/Ложьфункция
Изменить переданный параметр по ссылкепроцедура (или функция)

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

Если метод «и считает, и выводит», обычно его стоит разделить на функцию-расчёт и процедуру-вывод. Так расчёт можно переиспользовать и покрыть проверками.

Есть ещё договорённость по именам, которая облегчает чтение чужого кода. Функции обычно называют существительным-результатом (СуммаДокумента, ТекстОшибки) или с вопросительной частицей для булевых (ЭтоГруппа, ДоступноРедактирование). Процедуры называют глаголом-действием (ЗаписатьДанные, ЗаполнитьТаблицу, ОчиститьКэш). Прочитав имя, коллега сразу понимает, вернёт метод значение или просто что-то сделает.

Где процедура видна: контекст клиента и сервера

В модулях управляемых форм перед объявлением процедуры ставят директиву компиляции — она говорит, в каком контексте выполняется код:

&НаКлиенте
Процедура ПриИзмененииПоля(Элемент)
    // выполняется на клиенте
КонецПроцедуры

&НаСервере
Процедура ЗаполнитьРеквизиты()
    // выполняется на сервере, видит реквизиты формы
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ЗагрузитьСписок(Параметры)
    // на сервере, без доступа к данным формы — самый лёгкий вызов
КонецПроцедуры

Коротко про директивы:

  • 🔹 &НаКлиенте — код на стороне пользователя: обработчики интерфейса, вопросы, открытие форм;
  • 🔹 &НаСервере — код на сервере с доступом к реквизитам формы; каждый вызов тянет форму на сервер и обратно;
  • 🔹 &НаСервереБезКонтекста — серверный код без данных формы, самый дешёвый по трафику;
  • 🔹 в общих модулях контекст задаётся флагами самого модуля (Клиент/Сервер), директива не нужна.

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

Важно и то, какие данные переходят границу клиент–сервер. На клиент нельзя передать объект (например, СправочникОбъект) — туда «пролезают» только значения: число, строка, дата, ссылка, а также простые коллекции. Поэтому процедуры &НаСервереБезКонтекста обычно принимают и возвращают ссылки, примитивные значения или структуры, а тяжёлую работу с объектами ведут целиком на сервере.

Вне форм действуют свои правила видимости: процедура без Экспорт живёт в пределах модуля; с Экспорт в общем модуле — доступна по имени модуля; в глобальном — напрямую.

Рекурсия и примеры из практики

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

Функция Факториал(Н)
    Если Н <= 1 Тогда
        Возврат 1;
    КонецЕсли;
    Возврат Н * Факториал(Н - 1);
КонецФункции

Факториал(5) развернётся в 5 * 4 * 3 * 2 * 1 = 120. Главное в любой рекурсии — условие выхода: ветка, на которой вызов себя прекращается. Здесь это Н <= 1.

Процедуры рекурсивно обходят древовидные данные — например, группы и подгруппы справочника или строки дерева значений:

Процедура ОбойтиСтроки(Строки)
    Для Каждого Строка Из Строки Цикл
        Сообщить(Строка.Наименование);
        ОбойтиСтроки(Строка.Строки);  // спуск на уровень ниже
    КонецЦикла;
КонецПроцедуры

Рекурсия без условия выхода уходит в бесконечность и упирается в ограничение платформы на глубину вложенных вызовов — сеанс завершится ошибкой. Для плоских коллекций обычный цикл Для Каждого проще и быстрее рекурсии.

Когда при обходе дерева нужно что-то накопить (список, сумму, таблицу), удобно завести параметр-накопитель и передавать его по ссылке на каждый уровень вложенности. Тогда все ветки рекурсии дописывают результат в один и тот же объект:

Процедура СобратьНаименования(Строки, Результат)
    Для Каждого Строка Из Строки Цикл
        Результат.Добавить(Строка.Наименование);
        СобратьНаименования(Строка.Строки, Результат);
    КонецЦикла;
КонецПроцедуры

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

Пример защитной процедуры с ранним выходом и необязательным параметром — типичная заготовка для прикладного кода:

Процедура ПроверитьЗаполнение(Значение, ИмяПоля = "поле")
    Если ЗначениеЗаполнено(Значение) Тогда
        Возврат;
    КонецЕсли;
    Сообщить("Не заполнено: " + ИмяПоля);
КонецПроцедуры
Проверьте себя
Что вернёт процедура в точку вызова?
Какое ключевое слово делает процедуру доступной из других модулей?
Как передать параметр так, чтобы оригинал не менялся?

Частые ошибки и как ускорить код

Разберём то, что реально ломает работу или тормозит выполнение.

  1. Забыли Экспорт. Процедуру вызывают из другого модуля, а транслятор пишет «процедура не обнаружена». Проверьте, стоит ли Экспорт после списка параметров.
  2. Присвоили значение параметру с Знач и ждёте изменений снаружи. Так не работает : копия изменится, оригинал — нет. Либо уберите Знач, либо верните результат функцией.
  3. Функция без Возврат со значением. Вернётся Неопределено, а дальше по коду — ошибка сравнения или сложения типов. Убедитесь, что значение возвращается на всех ветках.
  4. Передача больших таблиц и списков через Знач. Платформа копирует коллекцию целиком. На больших объёмах это ощутимая задержка — передавайте по ссылке, если копия не нужна.
  5. Серверные вызовы в цикле на клиенте. Каждый вызов &НаСервере-процедуры из клиентского цикла — отдельный обмен с сервером. Соберите данные и вызовите сервер один раз.
  6. Рекурсия без условия выхода. Бесконечный спуск упрётся в предел глубины вызовов и оборвёт сеанс.

Приёмы, которые ускоряют и упрощают код:

  • ⚡ выносите повторяющийся код в отдельную процедуру — дублирование убирается, править нужно в одном месте;
  • ⚡ для вычислений используйте функции, для действий — процедуры: код становится предсказуемым;
  • ⚡ минимизируйте число переходов клиент–сервер, передавая данные пачкой через параметры;
  • ⚡ для простых типов применяйте Знач как защиту входных данных — риск случайно испортить переменную вызывающего кода пропадает;
  • ⚡ держите одну процедуру ответственной за одно действие: короткий метод проще прочитать, повторно использовать и отладить, чем «простыню» на сто строк с десятком Если.

Хорошая процедура читается как одно понятное предложение: взяли вход, выполнили действие, при необходимости сообщили результат. Если при чтении приходится держать в голове с десяток переменных и перескакивать по вложенным условиям, это сигнал разбить её на несколько мелких, каждая со своим именем-глаголом. Такой код переживает обновления конфигурации и смену разработчика гораздо спокойнее.

Перед вызовом процедуры из другого модуля
Выполнено: 0 / 5

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

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