Аннотации расширения 1С определяют, когда ваш метод выполнится относительно метода типовой конфигурации. &Перед — до исходного, &После — после него, &Вместо — вместо, с возможностью вызвать исходный через ПродолжитьВызов(). Для функций доступна только &Вместо (и &ИзменениеИКонтроль), для процедур — все три. Правильный выбор аннотации решает, переживёт ли доработка следующее обновление.

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

Как расширение перехватывает методы

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

В заимствованном модуле можно:

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

Механизм появился в ветке платформы 8.3.9 и с тех пор стал основным способом дорабатывать типовые конфигурации без снятия с поддержки. Есть ограничения: глобальные серверные общие модули заимствовать нельзя, а в самом расширении нельзя создавать привилегированные и глобальные серверные общие модули. Методы привилегированного общего модуля, перехваченные в расширении, по умолчанию выполняются без привилегированного режима; это регулируется профилем безопасности.

Добавить метод в расширение просто: в модуле конфигурации правой кнопкой по заголовку процедуры → «Добавить в расширение», выбрать расширение и тип вызова. Конфигуратор сам заимствует модуль и создаст заготовку с префиксом расширения и аннотацией.

&Перед: код до типового метода

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

1С
&Перед("ОбработкаПроверкиЗаполнения")
Процедура Расш1_ОбработкаПроверкиЗаполнения(Отказ, ПроверяемыеРеквизиты)

	// Комментарий обязателен, если менеджер дал ручную скидку
	Если Расш1_ЕстьРучнаяСкидка() Тогда
		ПроверяемыеРеквизиты.Добавить("Комментарий");
	КонецЕсли;

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

Функция Расш1_ЕстьРучнаяСкидка()

	Для Каждого СтрокаТовара Из Товары Цикл
		Если СтрокаТовара.ПроцентРучнойСкидки <> 0 Тогда
			Возврат Истина;
		КонецЕсли;
	КонецЦикла;

	Возврат Ложь;

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

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

Важно: &Перед не может отменить вызов исходного метода. Если в исходной процедуре нет нужного события (например, ОбработкаПроверкиЗаполнения не определена в модуле конфигурации), перехватывать нечего — назначьте обработчик события самого объекта в расширении.

&После: доработка результата типового кода

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

1С
&НаСервере
&После("ПриЧтенииНаСервере")
Процедура Расш1_ПриЧтенииНаСервере(ТекущийОбъект)

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

	Порог = 1000000;
	Если ТекущийОбъект.СуммаДокумента >= Порог Тогда
		ЭлементСуммы.ЦветТекста = WebЦвета.Красный;
	Иначе
		ЭлементСуммы.ЦветТекста = Новый Цвет;
	КонецЕсли;

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

Проверка Элементы.Найти здесь не формальность: после обновления типовой формы элемент могут переименовать, и без проверки расширение начнёт падать на открытии формы. Новый Цвет возвращает автоцвет, то есть стандартное оформление. В модуле формы директива компиляции (&НаСервере) пишется перед аннотацией перехвата — так её генерирует конфигуратор.

Тот же приём работает для процедур общих модулей, которые формы вызывают из своих обработчиков. В типовых конфигурациях логика форм часто вынесена в общие модули вида ИмяОбъектаФормы, и перехватывать удобнее там.

&Вместо и ПродолжитьВызов

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

Внутри &Вместо доступен метод ПродолжитьВызов(...): он вызывает исходный метод (или следующий слой расширений) с переданными параметрами и возвращает его результат. С ним &Вместо превращается в гибкую обёртку.

Обработка результата функции

Пусть типовая функция ПолучитьАдресФото(ФизическоеЛицо) в общем модуле возвращает адрес картинки во временном хранилище или пустую строку. Нужна картинка-заглушка, если фото нет:

1С
&Вместо("ПолучитьАдресФото")
Функция Расш1_ПолучитьАдресФото(ФизическоеЛицо)

	// Сначала выполняем типовую логику
	АдресФото = ПродолжитьВызов(ФизическоеЛицо);

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

	// Фото нет — отдаем картинку-заглушку из расширения
	ДанныеЗаглушки = БиблиотекаКартинок.Расш1_НетФото.ПолучитьДвоичныеДанные();
	Возврат ПоместитьВоВременноеХранилище(ДанныеЗаглушки, Новый УникальныйИдентификатор);

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

Общая картинка Расш1_НетФото добавлена в само расширение. Это аналог &После для функции: типовой код выполнился, вы работаете с результатом.

Условный вызов исходного метода

Нужно скрывать фото от пользователей без отдельной роли расширения:

1С
&Вместо("ПолучитьАдресФото")
Функция Расш1_ПолучитьАдресФото(ФизическоеЛицо)

	Если Не РольДоступна("Расш1_ПросмотрФотоСотрудников") Тогда
		Возврат "";
	КонецЕсли;

	Возврат ПродолжитьВызов(ФизическоеЛицо);

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

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

ПродолжитьВызов в процедуре

Для процедуры &Вместо с ПродолжитьВызов эквивалентна паре &Перед + &После, но с одним отличием: код до и после вызова живёт в одном контексте. Локальная переменная, вычисленная до ПродолжитьВызов, доступна после него. С отдельными &Перед и &После пришлось бы передавать состояние через переменную модуля или параметры.

1С
&Вместо("ПересчитатьСуммы")
Процедура Расш1_ПересчитатьСуммы(Отказ)

	// Модуль объекта: реквизиты документа доступны напрямую
	СуммаДоПересчета = СуммаДокумента;

	ПродолжитьВызов(Отказ);

	Если Не Отказ И СуммаДоПересчета <> СуммаДокумента Тогда
		Расш1_ЗарегистрироватьИзменениеСуммы(СуммаДоПересчета, СуммаДокумента);
	КонецЕсли;

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

ПересчитатьСуммы и Расш1_ЗарегистрироватьИзменениеСуммы здесь условные имена — подставьте метод своей конфигурации.

Порядок выполнения при нескольких расширениях

Расширения накладываются на конфигурацию слоями, как луковица. Внешний слой — расширение, применённое последним, затем предыдущие, в центре — основная конфигурация. Вызов метода проходит от внешнего слоя к центру и обратно:

СитуацияЧто выполнится
Расш2 &Перед, Расш1 &ПередРасш2, Расш1, конфигурация
Расш2 &После, Расш1 &ПослеКонфигурация, Расш1, Расш2
Расш2 &Вместо с ПродолжитьВызов, Расш1 &ПередРасш2 (до вызова), Расш1, конфигурация, Расш2 (после вызова)
Расш2 &Вместо без ПродолжитьВызовТолько Расш2; Расш1 и конфигурация не выполняются

Последняя строка — источник самых неприятных ошибок. Кто-то ставит «временное» расширение с &Вместо без ПродолжитьВызов, и доработки из других расширений молча перестают работать. Поэтому моё правило: одно расширение — одна предметная задача, и &Вместо без ПродолжитьВызов только тогда, когда типовой код действительно должен быть выключен.

&ИзменениеИКонтроль: когда правка нужна внутри метода

Бывает, что нужно изменить пару строк в середине длинной типовой процедуры. &Вместо с копией всего тела опасна: после обновления копия устареет, а вы этого не заметите. Для таких случаев есть &ИзменениеИКонтроль("ИмяМетода"). В расширение копируется текст метода, правки отмечаются директивами #Вставка/#КонецВставки и #Удаление/#КонецУдаления, а платформа сверяет исходный текст: если поставщик изменил метод, расширение не применится, пока вы не актуализируете его.

1С
&ИзменениеИКонтроль("ЗаполнитьПоОснованию")
Процедура Расш1_ЗаполнитьПоОснованию(Основание)

	// ... исходный текст метода ...
	#Вставка
	Комментарий = СтрШаблон("Создан на основании %1", Основание);
	#КонецВставки
	// ... исходный текст метода ...

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

Это лучший выбор для точечных правок: обновление не пройдёт мимо вас.

Типичные ошибки при работе с аннотациями

  • Копия типового тела в &Вместо. Работает до первого обновления. Используйте ПродолжитьВызов или &ИзменениеИКонтроль.
  • Ожидание, что &Перед отменит вызов. Не отменит. Нужна условная логика — &Вместо.
  • Несовпадение параметров. Сигнатура метода расширения должна совпадать с исходной. После обновления, если типовой метод получил новый параметр, расширение перестанет применяться.
  • Обращение к элементам формы без проверки. Переименованный в обновлении элемент ломает открытие формы. Всегда Элементы.Найти(...).
  • ПродолжитьВызов вне &Вместо. В &Перед и &После этот метод не используется.
  • Конфликт расширений. Два расширения с &Вместо на одном методе, одно без ПродолжитьВызов. Проверяйте пересечения при установке нового расширения.
Хорошее расширение не дублирует типовой код, а оборачивает его. Чем меньше скопированных строк, тем дешевле каждое обновление.

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

Итог

  • &Перед и &После — для процедур, когда типовой код должен выполниться полностью; отменить его они не могут.
  • &Вместо — единственная классическая аннотация для функций; с ПродолжитьВызов она становится обёрткой с общим контекстом до и после вызова.
  • Расширения выполняются слоями: последнее применённое — снаружи; &Вместо без ПродолжитьВызов отключает все внутренние слои.
  • Для правки в середине метода используйте &ИзменениеИКонтроль: изменения типового кода не пройдут незамеченными.
  • Проверяйте элементы формы через Найти и держите сигнатуры методов в актуальном состоянии после обновлений.