При разработке ограничений доступа на уровне кода в конфигурациях 1С часто возникает задача разграничения прав для различных типов объектов. Типичный пример — когда пользователю разрешено создавать и редактировать элементы справочника, но категорически запрещено изменять или создавать группы (папки) в этом же справочнике. Ситуация усложняется, когда это условие накладывается на уже существующую громоздкую проверку прав (роли, параметры сеанса, дополнительные функции безопасности).
В этой статье мы подробно разберем, как правильно составить такое логическое условие, проанализируем ошибки, которые часто совершают разработчики при попытке уместить все проверки в одну строку, и рассмотрим лучшие практики оптимизации и рефакторинга кода в соответствии со стандартами фирмы «1С».
Рассмотрим практическую ситуацию. В обработчике ПередЗаписью модуля объекта справочника (или в аналогичном механизме подписки на события) проверяются права текущего пользователя на редактирование. Если у пользователя нет прав, переменной Отказ должно быть присвоено значение Истина.
Изначально у разработчика было следующее условие:
Отказ = НЕ (РольДоступна("ПолныеПрава")
ИЛИ ПараметрыСеанса.ТекущийПользователь = ОтветственныйРПСДляРедактирования
ИЛИ ВозможностьРедактированияУслуг(Ссылка)
ИЛИ Справочники.Параметры.НайтиПоНаименованию("ДоступКИзменениюУслуг_ТиповогоИнцидента").СписокОбъектов.НайтиСтроки(пСтруктура).Количество > 0 );
Перед разработчиком встала задача: добавить дополнительное ограничение. Функция ВозможностьРедактированияУслуг(Ссылка) должна давать право на изменение только для элементов справочника. Если записываемый объект является группой (ЭтотОбъект.ЭтоГруппа = Истина), то эта функция не должна разрешать запись. При этом все остальные суперправа (например, ПолныеПрава или равенство ответственному) должны продолжать действовать без изменений и позволять редактировать в том числе и группы.
В процессе обсуждения на форуме автор пытался решить задачу «в лоб», просто добавив новое условие через оператор ИЛИ, сохранив при этом старое:
// ПРИМЕР НЕПРАВИЛЬНОГО КОДА!
Отказ = НЕ ( РольДоступна("ПолныеПрава")
ИЛИ ПараметрыСеанса.ТекущийПользователь = ОтветственныйРПСДляРедактирования
ИЛИ ВозможностьРедактированияУслуг(Ссылка) // ОШИБКА: старое условие осталось активным!
ИЛИ (ЭтотОбъект.ЭтоГруппа <> Истина И ВозможностьРедактированияУслуг(Ссылка))
ИЛИ Справочники.Параметры.НайтиПоНаименованию("ДоступКИзменениюУслуг_ТиповогоИнцидента").СписокОбъектов.НайтиСтроки(пСтруктура).Количество > 0 );
Выясним причину, почему такой код не работал. В логике булевой алгебры выражение А ИЛИ (НЕ Б И А) всегда эквивалентно простому А. Поскольку в общем списке условий через оператор ИЛИ остался старый вызов ВозможностьРедактированияУслуг(Ссылка), он продолжал срабатывать как для элементов, так и для групп. Дополнительная конструкция в скобках просто потеряла всякий смысл, так как первое условие полностью перекрывало ее результат.
Для правильной работы необходимо полностью заменить старое простое условие проверки прав на услуги на его уточненную версию.
Чтобы ограничить редактирование групп, оставив возможность изменять элементы, нам нужно связать проверку группы и проверку функции ВозможностьРедактированияУслуг через логическое И. При этом проверка группы должна работать на отрицание (то есть объект не должен быть группой).
Посмотрим на пример правильного логического условия в одну строку:
Отказ = НЕ ( РольДоступна("ПолныеПрава")
ИЛИ ПараметрыСеанса.ТекущийПользователь = ОтветственныйРПСДляРедактирования
ИЛИ (НЕ ЭтотОбъект.ЭтоГруппа И ВозможностьРедактированияУслуг(Ссылка))
ИЛИ Справочники.Параметры.НайтиПоНаименованию("ДоступКИзменениюУслуг_ТиповогоИнцидента").СписокОбъектов.НайтиСтроки(пСтруктура).Количество > 0 );
Разберем, как это работает:
ПолныеПрава или он является ответственным, выражение в скобках сразу принимает значение Истина, общее отрицание НЕ превращает его в Ложь, и Отказ = Ложь (запись разрешена).(НЕ ЭтотОбъект.ЭтоГруппа И ВозможностьРедактированияУслуг(Ссылка)).ЭтоГруппа равно Истина, следовательно, НЕ ЭтотОбъект.ЭтоГруппа превращается в Ложь. По правилам логического И вся скобка сразу возвращает Ложь, даже если функция ВозможностьРедактированияУслуг вернула бы Истина. Доступ по этой ветке для группы будет закрыт.ЭтоГруппа равно Ложь, выражение НЕ ЭтотОбъект.ЭтоГруппа дает Истина, и результат ветки полностью зависит от того, что вернет функция ВозможностьРедактированияУслуг(Ссылка).Хотя однострочное решение полностью решает задачу, с точки зрения стандартов разработки фирмы «1С», чистоты кода и производительности у него есть критические недостатки. Проанализируем эти проблемы и выполним качественный рефакторинг.
В платформе 1С логические выражения вычисляются «ленивым» способом (Short-circuit evaluation). Если в цепочке условий, связанных оператором ИЛИ, система встречает значение Истина, дальнейшие проверки прекращаются, так как результат всего выражения уже определен.
В исходном коде самое тяжелое условие с поиском в справочнике по наименованию (НайтиПоНаименованию) и вызовом функции ВозможностьРедактированияУслуг стоят в конце, но их порядок все еще можно оптимизировать. Условия всегда нужно располагать строго в порядке увеличения вычислительной сложности (от самых легких в оперативной памяти к самым тяжелым с запросами к базе данных):
РольДоступна) — мгновенно из кэша сессии.ПараметрыСеанса и реквизиты объекта) — очень быстро.ЭтоГруппа — обращение к значению в оперативной памяти.ВозможностьРедактированияУслуг).НайтиПоНаименованию) — критически медленные операции.Использование конструкции Справочники.Параметры.НайтиПоНаименованию("ДоступКИзменениюУслуг_ТиповогоИнцидента") в коде логической проверки — это грубейшее нарушение стандартов «1С». Если администратор в режиме предприятия изменит наименование этого параметра (например, случайно добавит пробел или исправит опечатку), поиск вернет пустую ссылку, и логика проверки прав незаметно сломается. Программа начнет отказывать в доступе там, где должна разрешать.
Как правильно это исправить:
Справочники.Параметры.ДоступКИзменениюУслуг_ТиповогоИнцидента.Свойство ЭтоГруппа доступно только у иерархических справочников. Если этот код когда-либо будет скопирован в модуль объекта плоского справочника, система вызовет критическую ошибку при выполнении. Если пишется универсальный код, всегда стоит проверять метаданные объекта на наличие иерархии:
Если Метаданные().Иерархический Тогда
// Здесь безопасно проверять ЭтоГруппа
КонецЕсли;
Запись сложного условия в одну длинную строку делает код нечитаемым. Если в процессе работы возникнет ошибка, разработчик под отладчиком не сможет понять, какая именно часть выражения вернула неверный результат. Стандарты 1С рекомендуют разделять сложные логические выражения на простые блоки с использованием понятных промежуточных переменных.
Рассмотрим детально, как должен выглядеть этот код после профессионального рефакторинга:
// Сначала проверяем базовые "быстрые" права, которые хранятся в оперативной памяти
ДоступРазрешен = РольДоступна("ПолныеПрава")
ИЛИ ПараметрыСеанса.ТекущийПользователь = ОтветственныйРПСДляРедактирования;
// Если быстрые проверки не дали доступ, переходим к более сложным правилам
Если НЕ ДоступРазрешен Тогда
// Проверяем, поддерживает ли справочник иерархию
ЭтоИерархическийСправочник = Метаданные().Иерархический;
ЭтоГруппаСправочника = ЭтоИерархическийСправочник И ЭтотОбъект.ЭтоГруппа;
// Возможность редактирования услуг разрешена только для элементов (не для групп)
Если НЕ ЭтоГруппаСправочника Тогда
ДоступРазрешен = ВозможностьРедактированияУслуг(Ссылка);
КонецЕсли;
КонецЕсли;
// Если доступ все еще не получен, проверяем самую тяжелую ветку с параметрами системы
Если НЕ ДоступРазрешен Тогда
// Вместо поиска по наименованию используем безопасное обращение к предопределенному элементу
ПараметрДоступа = Справочники.Параметры.ДоступКИзменениюУслуг_ТиповогоИнцидента;
Если ЗначениеЗаполнено(ПараметрДоступа) Тогда
СтрокиПараметра = ПараметрДоступа.СписокОбъектов.НайтиСтроки(пСтруктура);
ДоступРазрешен = (СтрокиПараметра.Количество() > 0);
КонецЕсли;
КонецЕсли;
// Устанавливаем итоговый результат отказа в записи
Отказ = НЕ ДоступРазрешен;
Мы по шагам разобрали, как решить проблему разграничения прав для групп и элементов справочника в 1С. Мы ушли от ненадежного однострочного выражения к красивому, быстрому и безопасному коду, который полностью соответствует стандартам разработки фирмы «1С». Написание логических условий в структурированном виде не только повышает скорость работы системы за счет ленивых вычислений, но и существенно упрощает дальнейшую поддержку и отладку конфигурации вашими коллегами.