При доработке типовых конфигураций 1С с помощью расширений разработчики часто сталкиваются с необходимостью изменить логику, которая контролируется директивами препроцессора, например, #Если ВебКлиент Тогда. Рассмотрим ситуацию, когда требуется удалить существующую директиву и вставить новую. Например, изменить условие с #Если ВебКлиент Тогда на #Если ВебКлиент или Клиент Тогда.
#Удаление
#Если ВебКлиент Тогда
#КонецУдаления
#Вставка
#Если ВебКлиент или Клиент Тогда
#КонецВставки
На первый взгляд, такой подход кажется логичным, но на практике он не работает, и система контроля синтаксиса 1С сразу выдаст ошибку. Почему так происходит и как найти выход из этой ситуации, мы подробно разберем далее.
Основная причина заключается в том, что директивы препроцессора 1С (такие как #Если, #ИначеЕсли, #КонецЕсли, #Область, #КонецОбласти) обрабатываются на очень раннем этапе, еще до того, как код модуля будет "скомпилирован" или к нему будут применены изменения из расширения. Механизмы расширений, использующие директивы #Вставка и #Удаление, оперируют с уже сформированным кодом модуля, а не с его исходными препроцессорными инструкциями.
Когда вы пытаетесь удалить или вставить директиву #Если с помощью #Удаление / #Вставка, система расширений не понимает эти инструкции как часть "кода", который можно модифицировать таким способом. Она ожидает получить строки обычного кода, а не другие директивы препроцессора. Это приводит к синтаксическим ошибкам, поскольку расширение не может корректно интерпретировать и применить такие изменения.
Другими словами, директивы препроцессора в основном модуле не переносятся в расширение. Таким образом, расширение не видит и не может напрямую управлять этими инструкциями.
Поскольку прямое изменение директив препроцессора невозможно, программистам приходится искать обходные пути. Один из наиболее распространенных способов, хотя и не идеальный с точки зрения чистоты кода, – это дублирование кода с новой логикой. Мы можем использовать директиву #Вставка, чтобы добавить новый блок кода с собственной директивой #Если, которая будет содержать измененную логику. При этом оригинальный код, который мы хотели изменить, остается нетронутым в базовой конфигурации, но его выполнение может быть изменено благодаря вставке.
Идентифицируем участок кода в основном модуле, который находится под действием директивы #Если ВебКлиент Тогда и который мы хотим изменить.
В расширении, в том же месте модуля, где находился оригинальный код, мы используем директиву #Вставка.
Внутри блока #Вставка мы добавляем новый блок с собственной директивой препроцессора, например, #Если НЕ ВебКлиент или Клиент Тогда, и дублируем или адаптируем необходимый код.
Рассмотрим пример, который обсуждался на форуме. Если мы хотим изменить логику, которая активируется только для ВебКлиент, чтобы она работала и для Клиент, мы можем сделать следующее:
// Оригинальный код в базовой конфигурации мог выглядеть так:
// #Если ВебКлиент Тогда
// // Некоторый код, специфичный для ВебКлиента
// ВыполнитьДействиеДляВебКлиента();
// #КонецЕсли
// В расширении мы добавляем (или замещаем) логику следующим образом:
#Вставка
#Если НЕ ВебКлиент или Клиент Тогда
// Здесь размещаем весь код, который изначально был предназначен для ВебКлиента
// или измененный код, который теперь должен выполняться для ВебКлиента ИЛИ Клиента.
ВыполнитьДействиеДляВебКлиентаИлиКлиента(); // Пример измененной или дублированной логики
#КонецЕсли
#КонецВставки
В этом случае, если изначальный код был внутри блока #Если ВебКлиент Тогда, мы фактически добавляем условие, которое перекрывает или дополняет его. Если мы хотим, чтобы оригинальный код не выполнялся при новом условии, нам, возможно, придется использовать более сложные конструкции или даже прибегнуть к аннотации &ИзменениеИКонтроль для более точечной замены частей логики.
Дублирование кода: Это самый очевидный недостаток. Мы получаем две копии практически одинакового или очень похожего кода: одну в базовой конфигурации, другую — в расширении. Это усложняет сопровождение, так как при изменении логики в одном месте придется не забыть обновить и второе.
Усложнение анализа: Читаемость и понимание кода снижаются, так как становится не сразу очевидно, какой именно блок кода будет выполняться при определенных условиях.
Потенциальные конфликты: При значительных изменениях в типовой конфигурации, особенно в тех участках, где мы дублировали код, могут возникнуть сложности с обновлением расширения.
Несмотря на эти недостатки, на практике в 1С иногда приходится прибегать к дублированию, особенно если модифицируемый участок кода невелик, и риск будущих изменений минимален. Некоторые разработчики считают, что такое дублирование может быть оправдано, если оно значительно упрощает процесс обновления и снижает вероятность проблем совместимости.
Важно понимать, что директива #Вставка предназначена для добавления именно исполняемого кода, а не для манипуляции директивами препроцессора. Рассмотрим пример, где #Вставка успешно используется для изменения логики выполнения, внедряя новую условную конструкцию Если...Тогда...Иначе...КонецЕсли.
// Исходный код в базовой конфигурации:
// ОтчетОПродажахККМ.Чеки.Добавить(ЧекККМ);
// Изменение с помощью расширения:
#Вставка
Если Чек.ТипОперацииККМ = "Возврат" Тогда
ВозвратТоваровККМ.Чеки.Добавить(ЧекККМ);
Иначе
#КонецВставка
ОтчетОПродажахККМ.Чеки.Добавить(ЧекККМ);
#Вставка
КонецЕсли;
#КонецВставка
В этом примере мы видим, как с помощью двух блоков #Вставка мы "оборачиваем" существующую строку кода ОтчетОПродажахККМ.Чеки.Добавить(ЧекККМ); в условную конструкцию. Первый блок #Вставка добавляет начало условия (Если...Тогда...Иначе), а второй блок #Вставка добавляет завершение условия (КонецЕсли). Это позволяет изменить поведение программы, добавляя новый функционал (обработка возвратов), сохраняя при этом исходную строку кода, но меняя условия ее выполнения.
Данный пример демонстрирует, что #Вставка эффективно работает, когда мы добавляем или изменяем именно логику выполнения, операторы, переменные и вызовы функций, а не пытаемся воздействовать на инструкции, которые обрабатываются до фазы "компиляции" расширения.
Для более контролируемого и безопасного изменения кода в расширениях, особенно в объемных процедурах и функциях, платформа 1С предлагает использовать аннотацию &ИзменениеИКонтроль. Эта аннотация появилась с версии 8.3.15 и позволяет делать точечные вставки в код типовой конфигурации, при этом платформа контролирует неизменность оригинального кода в вынесенной в расширение процедуре.
При применении &ИзменениеИКонтроль система проверяет соответствие текста расширяемого метода в основной конфигурации и в расширении. Если эта проверка не пройдена (например, из-за изменения сигнатуры метода или его внутреннего содержимого в базовой конфигурации при обновлении), расширение все равно применится, но метод, в котором обнаружена ошибка применимости, не будет расширен. Это означает, что изменения, сделанные в расширении для данного метода, не будут применены, и будет использоваться оригинальная версия метода из типовой конфигурации.
На форуме был рассмотрен случай, когда при обновлении конфигурации возникла ошибка применения расширения с сообщением "Ошибка применения модуля ... Текст модуля для метода ... изменился".
// Оригинальный метод в базовой конфигурации
Процедура ПолучитьСодержаниеТекстовыхФайлов(ИменаФайлов, ОписаниеОповещенияПриЗавершении, Кодировка = Неопределено) Экспорт
// Метод в расширении (с аннотацией)
&ИзменениеИКонтроль("ПолучитьСодержаниеТекстовыхФайлов")
Процедура дор_ПолучитьСодержаниеТекстовыхФайлов(ИменаФайлов, ОписаниеОповещенияПриЗавершении, Кодировка) Экспорт
Причиной таких ошибок часто является изменение сигнатуры метода (количества, типов или порядка параметров) в базовой конфигурации. Например, если в оригинальный метод добавили новый параметр, изменили значение по умолчанию, или поменяли порядок параметров, то метод в расширении, помеченный &ИзменениеИКонтроль, перестанет быть совместимым с оригиналом.
Несмотря на то, что ошибка платформы может быть сформулирована как "Текст модуля ... изменился", что звучит довольно обобщенно, на самом деле причиной может быть именно изменение сигнатуры метода. Платформа 1С должна отлавливать такие изменения, и сообщение об ошибке является индикатором, что требуется корректировка расширения.
Что делать в такой ситуации:
Сравнить сигнатуры: Откройте исходный модуль в обновленной конфигурации и сравните сигнатуру метода (имя, параметры) с сигнатурой в вашем расширении.
Актуализировать расширение: Приведите сигнатуру метода в расширении в соответствие с новой сигнатурой из типовой конфигурации. Возможно, потребуется перенести или адаптировать часть измененной логики.
Повторить применение: После корректировки расширения попробуйте применить его снова.
Использование &ИзменениеИКонтроль требует внимательности при обновлениях, но обеспечивает более безопасный и контролируемый процесс доработки, помогая избежать неожиданных конфликтов и ошибок в работе системы.
При работе с расширениями существуют и другие нюансы, которые мы должны учитывать:
Работа с запросами: Попытки внести изменения в текст запроса, используя #Вставка для условного добавления частей запроса, могут быть неочевидными и приводить к неожиданным результатам, как было показано в одном из примеров на форуме. Часто более надежным способом является формирование запроса динамически в коде 1С, а не попытка модифицировать строковые константы запроса с помощью препроцессора расширения.
// Пример с форума, который демонстрирует сложности
ТекстЗапроса =
"ВЫБРАТЬ
#Вставка
" + ?(ТаблицаТоваров.Колонки.Найти("КодКассира") = Неопределено, "", "
| Товары.КодКассира КАК КодКассира,
| Товары.НомерЧека КАК НомерЧека,
| //Номер смены в УТ 11.5 поддерживаемся
| //Товары.НомерСмены КАК НомерСмены,") + "
#КонецВставка
Минимизация заимствованных объектов: Мы должны стараться заимствовать в расширение только те объекты, которые абсолютно необходимы для реализации нашей доработки. Чем меньше объектов заимствовано, тем проще будет поддерживать расширение и тем меньше вероятность возникновения конфликтов при обновлении типовой конфигурации.
Избегание &Вместо для полного переопределения: Директива &Вместо позволяет полностью переопределить существующий код процедуры или функции. Однако ее использование в расширениях не рекомендуется для большинства сценариев, поскольку это может привести к серьезным проблемам при обновлениях и конфликтам, если несколько расширений пытаются переопределить один и тот же метод.
Ограничения на изменение метаданных: Расширения имеют свои ограничения. Например, они не поддерживают изменение структуры регистров всех видов, а также добавление реквизитов и табличных частей для планов видов характеристик, планов счетов и планов видов расчетов. Для таких глубоких изменений иногда приходится модифицировать основную конфигурацию или искать другие архитектурные решения.
Типы расширений: Мы должны помнить, что расширения могут иметь разное назначение: "Исправление" (для устранения ошибок), "Адаптация" (для подстройки под условия клиента) и "Дополнение" (для реализации новых возможностей). Четкое понимание цели расширения помогает выбрать правильный подход к разработке.
В заключение, хотя прямая манипуляция директивами препроцессора в расширениях 1С не поддерживается, существуют рабочие обходные пути, такие как дублирование кода или использование аннотации &ИзменениеИКонтроль для более структурированных изменений. Важно глубоко понимать принципы работы механизма расширений и его ограничения, чтобы создавать надежные и легко поддерживаемые доработки.