В процессе разработки в системе 1С:Предприятие 8 программисты часто сталкиваются с ситуацией, когда автоматизированные системы проверки кода (линтеры, такие как SonarQube, 1С:EDT или встроенные проверки конфигурации) начинают выдавать замечания на казалось бы простой код. Одной из самых частых претензий является избыточная цикломатическая и когнитивная сложность условий. Рассмотрим типичный пример: когда нам нужно проверить, входит ли ссылка в определенный набор элементов, и мы начинаем загромождать конструкцию Если многочисленными операторами ИЛИ в сочетании с методом НайтиПоКоду().
Разберем, почему такой подход считается плохим тоном («говнокодом»), чем он опасен для производительности и какие существуют правильные способы рефакторинга этой ситуации.
Проанализируем классический пример неоптимального условия:
Если ВидНеисправности = Справочники.ВидНеисправности.НайтиПоКоду("10464")
ИЛИ ВидНеисправности = Справочники.ВидНеисправности.НайтиПоКоду("10998")
ИЛИ ВидНеисправности = Справочники.ВидНеисправности.НайтиПоКоду("10999")
ИЛИ ВидНеисправности = Справочники.ВидНеисправности.НайтиПоКоду("10929")
ИЛИ ВидНеисправности = Справочники.ВидНеисправности.НайтиПоКоду("10467") Тогда
// Какая-то логика
КонецЕсли;
Рассмотрим причины, по которым линтер и опытные разработчики требуют исправить этот фрагмент:
НайтиПоКоду() — это отдельный SQL-запрос к СУБД. В худшем случае (если совпадение произойдет на последнем элементе или не произойдет вовсе), система выполнит 5 последовательных запросов там, где можно было бы обойтись нулем или одним.ИЛИ усложняет восприятие алгоритма.//Комментарий вместо // Комментарий), так как это нарушает стандарты оформления кода фирмы 1С, затрудняя автоматический разбор текстов модулей.Рассмотрим по шагам, как можно улучшить этот код, двигаясь от простых решений к архитектурно правильным.
Самый простой способ избавиться от множественных ИЛИ — собрать искомые значения в Массив и использовать метод Найти(). Однако, чтобы не делать лишних запросов к БД внутри условия, коды следует вынести за пределы проверки.
Проанализируем пример оптимизации:
СписокКодов = Новый Массив;
СписокКодов.Добавить("10464");
СписокКодов.Добавить("10998");
СписокКодов.Добавить("10999");
СписокКодов.Добавить("10929");
СписокКодов.Добавить("10467");
// Получаем код проверяемого объекта один раз, чтобы не обращаться к нему в цикле/условии
ТекущийКод = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(ВидНеисправности, "Код");
Если СписокКодов.Найти(ТекущийКод) <> Неопределено Тогда
// Логика обработки
КонецЕсли;
Преимущества: Код стал чище, а линтер перестал ругаться на сложность условия Если. Мы минимизировали количество запросов к базе данных.
Если элементов для сравнения много (не 5, а 50 или 100), поиск в массиве может замедлиться, так как метод Найти() выполняет линейный перебор. В этом случае эффективнее использовать объект Соответствие.
Выясним, почему это быстрее: поиск в Соответствие работает по принципу хеш-таблицы, и время поиска не зависит от количества элементов в списке (сложность $O(1)$ против $O(n)$ у массива).
КодыДляПроверки = Новый Соответствие;
КодыДляПроверки.Вставить("10464", Истина);
КодыДляПроверки.Вставить("10998", Истина);
// ... заполнение остальных
ТекущийКод = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(ВидНеисправности, "Код");
Если КодыДляПроверки[ТекущийКод] <> Неопределено Тогда
// Элемент найден в списке
КонецЕсли;
Иногда разработчики пытаются использовать Структура, так как метод Свойство() работает очень быстро. Но здесь есть ловушка: ключи структуры должны соответствовать правилам именования идентификаторов (не могут начинаться с цифр и содержать пробелы).
Рассмотрим способ обхода этого ограничения, предложенный участниками обсуждения:
// Добавляем префикс "К", чтобы превратить числовой код в валидный идентификатор
КодыСтр = Новый Структура("К10464, К10998, К10999", Истина, Истина, Истина);
Если КодыСтр.Свойство("К" + ВидНеисправности.Код) Тогда
// Обработка
КонецЕсли;
Важное замечание: Хотя этот метод быстр, он считается «хаком». Использование Соответствие или Массив является более стандартным подходом.
Все предыдущие решения не избавляют от главной проблемы — жесткого кодирования (hardcode). Если виды неисправностей завтра изменятся, программисту придется лезть в код. Проанализируем ситуацию: если группа элементов справочника требует особой обработки в коде, значит, у них есть какой-то общий признак.
Правильным архитектурным решением будет:
ВидНеисправности новый реквизит типа Булево (например, ТребуетОсобогоКонтроля) или реквизит с типом Перечисление.
Если ВидНеисправности.ТребуетОсобогоКонтроля Тогда
// Логика
КонецЕсли;
Почему это лучше:
Если логика программы жестко завязана на конкретные элементы справочника, которые не должны удаляться, лучше всего сделать их предопределенными в конфигураторе. Тогда обращение к ним будет выглядеть максимально лаконично:
Если ВидНеисправности = Справочники.ВидыНеисправностей.ПоломкаХолодильника
ИЛИ ВидНеисправности = Справочники.ВидыНеисправностей.КритическийСбой Тогда
// Логика
КонецЕсли;
При таком подходе платформа кэширует ссылки, и обращения к ним не вызывают лишних запросов к базе данных при каждом выполнении кода.
Подводя итог, проанализируем стратегию выбора решения:
И не забывайте о мелочах: всегда ставьте пробел после символов комментария // . Это не просто прихоть «робота», а стандарт, который делает код профессиональным и удобным для поддержки всей командой.