Почему не создается элемент справочника при переносе кода в функцию в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

В чем заключается проблема? Разбор исходной ситуации

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


Функция ПолучитьПодразделениеСсылка(ПодразделениеИмя, Организация)
    ПодразделениеСсылка = Справочники.Подразделения.НайтиПоНаименованию(ПодразделениеИмя, , , Организация);
    Если ПодразделениеСсылка = Справочники.Подразделения.ПустаяСсылка() Тогда
        Подразделение = Справочники.Подразделения.СоздатьЭлемент();
        Подразделение.Владелец = Организация;
        Подразделение.Наименование = ПодразделениеИмя;
        Подразделение.Записать();
        ПодразделениеСсылка = Подразделение.Ссылка;
    КонецЕсли;
    Возврат (ПодразделениеСсылка);
КонецФункции

На первый взгляд, код написан логично: сначала система пытается найти подразделение по наименованию в контексте определенной организации (владельца). Если элемент не найден (возвращена пустая ссылка), то вызывается метод СоздатьЭлемент(), заполняются обязательные реквизиты, объект записывается, и функция возвращает ссылку на созданный элемент. Тем не менее на практике этот код перестает работать, если функция объявлена внутри модуля формы. Выясним скрытую причину такого поведения системы.

Причина №1: Неявный конфликт имен (Область видимости в модулях форм)

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

  1. При написании строки Подразделение = Справочники.Подразделения.СоздатьЭлемент(); платформа не объявляет новую локальную переменную. Вместо этого она обращается напрямую к реквизиту формы с именем Подразделение.
  2. Платформа пытается записать созданный объект типа СправочникОбъект.Подразделения в реквизит формы, тип которого может быть жестко ограничен (например, типом Строка).
  3. Далее, при обращении к свойствам Подразделение.Владелец и Подразделение.Наименование, система работает не с чистым локальным объектом, а с реквизитом формы, значение которого могло исказиться или не обновиться из-за несоответствия типов. В результате запись объекта либо вообще не происходит, либо завершается неявной ошибкой, которая может замалчиваться платформой в зависимости от контекста выполнения.

Для решения этой проблемы необходимо строго следовать стандартам разработки фирмы «1С». Для всех создаваемых в коде прикладных объектов всегда следует использовать специализированный суффикс «Объект». Переименование переменной Подразделение в ПодразделениеОбъект полностью изолирует локальные вычисления от реквизитов формы.

Причина №2: Неточный поиск по наименованию

Проанализируем строку поиска элемента: Справочники.Подразделения.НайтиПоНаименованию(ПодразделениеИмя, , , Организация);. В данном вызове пропущен второй параметр метода — ТочноеСоответствие. По умолчанию этот параметр принимает значение Ложь.

Рассмотрим, чем это грозит на практике. Если параметр ТочноеСоответствие равен Ложь, система выполняет поиск по началу строки (подобно оператору ПОДОБНО "ИскомоеИмя%" в СУБД). Если в вашей базе данных уже существует подразделение с наименованием «Отдел продаж и маркетинга», а вы программно пытаетесь найти или создать подразделение «Отдел продаж», метод НайтиПоНаименованию() найдет более длинное наименование, вернет его ссылку, а условие создания нового элемента не выполнится. Новое подразделение «Отдел продаж» никогда не будет создано. Для точного поиска всегда явно передавайте значение Истина во второй параметр метода.

Причина №3: Избыточное сравнение с пустой ссылкой

В исходном коде используется конструкция Если ПодразделениеСсылка = Справочники.Подразделения.ПустаяСсылка() Тогда. Хотя данный подход работоспособен, он считается устаревшим и менее надежным. Существует более лаконичная и универсальная системная функция ЗначениеЗаполнено().

Применение конструкции Если Не ЗначениеЗаполнено(ПодразделениеСсылка) Тогда предпочтительнее по нескольким причинам. Функция ЗначениеЗаполнено() корректно обрабатывает не только пустые ссылки конкретного типа справочника, но и значения типов Неопределено или Null, которые могут возникнуть при различных сбоях в базе данных или при получении данных запросом. Такой код легче читать и поддерживать.

Причина №4: Избыточный контекст вызова функции

Если функция ПолучитьПодразделениеСсылка объявлена в модуле управляемой формы без директивы компиляции или с директивой &НаСервере, она выполняется в контексте формы. Это означает, что при каждом вызове функции с клиента на сервер передаются все данные формы (так называемый «контекст формы»), что снижает общую производительность системы.

Поскольку данная функция выполняет исключительно серверные операции (поиск в базе данных и создание нового элемента) и не использует напрямую другие реквизиты формы, ее крайне рекомендуется объявлять с директивой &НаСервереБезКонтекста. При бесконтекстном вызове контекст формы на сервер не передается, а любые попытки случайно обратиться к реквизитам формы (например, к нашему проблемному реквизиту Подразделение) вызовут явную ошибку компиляции еще на этапе разработки, что сразу укажет программисту на конфликт имен.

Пошаговое руководство по исправлению и рефакторингу функции

Проанализировав все слабые места исходного алгоритма, разработаем надежный, быстрый и безопасный вариант функции, полностью соответствующий стандартам разработки «1С»:


&НаСервереБезКонтекста
Функция ПолучитьПодразделениеСсылка(Знач ПодразделениеИмя, Знач Организация)
    
    // Инициализируем возвращаемое значение пустой ссылкой
    ПодразделениеСсылка = Справочники.Подразделения.ПустаяСсылка();
    
    // 1. Поиск элемента с обязательным указанием точного соответствия (второй параметр - Истина)
    ПодразделениеСсылка = Справочники.Подразделения.НайтиПоНаименованию(ПодразделениеИмя, Истина, , Организация);
    
    // 2. Универсальная и безопасная проверка на заполненность полученного значения
    Если Не ЗначениеЗаполнено(ПодразделениеСсылка) Тогда
        
        // 3. Создаем новый объект с использованием суффикса "Объект" для исключения конфликта имен
        ПодразделениеОбъект = Справочники.Подразделения.СоздатьЭлемент();
        ПодразделениеОбъект.Владелец     = Организация;
        ПодразделениеОбъект.Наименование = ПодразделениеИмя;
        
        // 4. Безопасная запись объекта с обработкой возможных исключений
        Попытка
            ПодразделениеОбъект.Записать();
            ПодразделениеСсылка = ПодразделениеОбъект.Ссылка;
        Исключение
            // В случае ошибки записи (например, блокировки базы или срабатывания подписок) фиксируем инцидент
            ЗаписьЖурналаРегистрации(
                "Справочники.Подразделения.СозданиеЭлемента",
                УровеньЖурналаРегистрации.Ошибка,
                ,
                ,
                ПодробноеПредставлениеОшибки(ИнформацияОбОшибке())
            );
            ВызватьИсключение "Не удалось создать новое подразделение. Обратитесь к администратору.";
        КонецПопытки;
        
    КонецЕсли;
    
    Возврат ПодразделениеСсылка;
    
КонецФункции

Подведем итоги нашего рефакторинга. Использование директивы &НаСервереБезКонтекста существенно увеличило скорость работы функции и полностью изолировало код от влияния интерфейсных реквизитов. Четкое следование стандартам именования локальных переменных (использование ПодразделениеОбъект) гарантирует отсутствие скрытых конфликтов типов. Наконец, добавление конструкции Попытка...Исключение с логированием в журнал регистрации защищает систему от аварийного завершения работы пользователей в случае непредвиденных ошибок СУБД или наложения транзакционных блокировок.

← На главную