В разработке на платформе 1С иногда встречаются "неожиданные эффекты", когда, казалось бы, простой и логичный код приводит к ошибке. Рассмотрим типичную ситуацию: есть серверная процедура с необязательным параметром, и при определенных условиях внутри этой процедуры параметру присваивается табличная часть документа. Это действие внезапно вызывает ошибку передачи данных между клиентом и сервером. Давайте разберемся, почему так происходит и как этого избежать.
Проанализируем исходный код, который приводит к проблеме. На клиенте вызывается серверная процедура, в которую передается только один обязательный параметр — ссылка на документ.
&НаКлиенте
Процедура ОбработатьДокумент(Док)
ОбработатьДокументНаСервере(Док);
КонецПроцедуры
&НаСервере
Процедура ОбработатьДокументНаСервере(Док, ТабЧасть = Неопределено)
Если ТабЧасть = Неопределено Тогда
ТабЧасть = Док.ТабличнаяЧасть;
КонецЕсли;
// тут можно что-то обработать
КонецПроцедуры
При выполнении этого кода система выдает ошибку, текст которой может выглядеть примерно так:
{Форма.Форма.Форма(161)}: Ошибка при вызове метода контекста (ОбработатьДокументНаСервере) ОбработатьДокументНаСервере(Док); по причине: Ошибка передачи данных между клиентом и сервером. Значение недопустимого типа. по причине: Ошибка преобразования данных XDTO: ... Отсутствует отображение для типа 'ДокументТабличнаяЧасть.Инвентаризация.Товары'
На первый взгляд, все логично: если второй параметр ТабЧасть не передан, мы получаем его из документа на сервере. Почему же возникает ошибка? Фокус кроется в механизме передачи параметров между клиентом и сервером по умолчанию.
По умолчанию параметры в 1С передаются по ссылке. В контексте клиент-серверного взаимодействия это означает, что платформа не только передает значение параметра с клиента на сервер, но и пытается вернуть его измененное значение с сервера обратно на клиент после завершения процедуры.
Рассмотрим по шагам, что происходит:
ОбработатьДокументНаСервере(Док) — это, по сути, "синтаксический сахар". Под капотом платформа выполняет вызов с передачей значения по умолчанию для второго параметра: ОбработатьДокументНаСервере(Док, Неопределено).Док (типа ДокументСсылка) и ТабЧасть (со значением Неопределено).Если ТабЧасть = Неопределено Тогда.ТабЧасть присваивается новое значение: Док.ТабличнаяЧасть. Теперь тип этой переменной — ДокументТабличнаяЧасть.ТабЧасть был передан без специального указания (т.е. по ссылке), платформа пытается вернуть его новое значение обратно на клиент.ДокументТабличнаяЧасть является "серверным" объектом. Он не может быть сериализован (преобразован в формат для передачи по сети) и отправлен на клиент. Платформа не знает, как "отобразить" этот тип на клиенте.Таким образом, "неожиданный эффект" заключается в том, что платформа пытается вернуть на клиент параметр, который мы даже не передавали явно, но тип которого изменился на сервере на непередаваемый.
Самый простой, правильный и эффективный способ решить эту проблему — использовать ключевое слово Знач при объявлении параметра в серверной процедуре.
Ключевое слово Знач (передача по значению) явно указывает платформе, что значение этого параметра нужно передать только в одну сторону — с клиента на сервер. Возвращать его обратно не нужно. Любые изменения этого параметра на сервере останутся только на сервере и не повлияют на клиентский контекст.
Исправленный код будет выглядеть так:
&НаСервере
Процедура ОбработатьДокументНаСервере(Док, Знач ТабЧасть = Неопределено)
Если ТабЧасть = Неопределено Тогда
ТабЧасть = Док.ТабличнаяЧасть;
КонецЕсли;
// тут можно что-то обработать
КонецПроцедуры
Почему это лучшее решение:
Важный совет: возьмите за правило всегда использовать Знач для параметров серверных процедур, вызываемых с клиента, если вам не требуется возвращать их измененное значение. Это убережет от множества подобных скрытых ошибок.
Хотя использование Знач является каноническим решением, на форуме были предложены и другие рабочие варианты. Их стоит знать, чтобы понимать разные подходы, но в данной ситуации они избыточны.
Идея этого способа — перед завершением процедуры вернуть параметру значение, которое можно передать на клиент (например, Неопределено).
&НаСервере
Процедура ОбработатьДокументНаСервере(Док, ТабЧасть = Неопределено)
Если ТабЧасть = Неопределено Тогда
ТабЧасть = Док.ТабличнаяЧасть;
КонецЕсли;
// тут можно что-то обработать
// Возвращаем параметру исходный тип перед выходом
ТабЧасть = Неопределено;
КонецПроцедуры
Этот метод работает, но он является "костылем". Он усложняет логику, заставляет писать лишнюю строку кода и не решает проблему производительности, так как передача данных обратно на клиент все равно инициируется (просто передается "безопасное" значение).
Еще один вариант — создать промежуточную серверную процедуру-обертку, которая будет вызывать основную процедуру уже полностью в серверном контексте.
&НаКлиенте
Процедура ОбработатьДокумент(Док)
ОбработатьТолькоДокументНаСервере(Док);
КонецПроцедуры
&НаСервере
Процедура ОбработатьТолькоДокументНаСервере(Док)
// Вызов происходит уже в контексте сервера,
// поэтому проблемы с передачей данных на клиент нет
ОбработатьДокументНаСервере(Док);
КонецПроцедуры
&НаСервере
Процедура ОбработатьДокументНаСервере(Док, ТабЧасть = Неопределено)
Если ТабЧасть = Неопределено Тогда
ТабЧасть = Док.ТабличнаяЧасть;
КонецЕсли;
// тут можно что-то обработать
КонецПроцедуры
Этот способ тоже работает, так как проблемный вызов с параметром по умолчанию происходит между двумя серверными процедурами, где нет ограничений на передачу серверных типов. Однако он неоправданно усложняет структуру кода, создавая лишнюю сущность.
Рассмотренная проблема — отличный пример того, как важно понимать тонкости работы клиент-серверной архитектуры в 1С. Главный урок, который мы можем извлечь:
Используйте Знач для всех входных параметров серверных вызовов, которые не должны возвращать значение обратно на клиент.
Это не только предотвратит "неожиданные" ошибки передачи несериализуемых типов, но и сделает ваш код чище, понятнее и производительнее.