В среде разработчиков 1С часто возникает дискуссия: что важнее — быстро написать рабочий код («лишь бы работало») или сразу тратить время на архитектурную красоту? И что на самом деле скрывается за понятием «красота кода»? Это не просто эстетика, а конкретные метрики производительности, поддерживаемости и читаемости. Рассмотрим подробнее, какие технические приемы делают код «красивым» с точки зрения платформы 1С:Предприятие, разберем опасные антипаттерны и способы их устранения.
Рефакторинг — это процесс изменения внутренней структуры программы без изменения её внешнего поведения. Цель состоит не в исправлении ошибок, а в упрощении понимания кода и удешевлении его дальнейшей поддержки. В обсуждении мы выяснили, что часто под рефакторингом понимают превращение «спагетти-кода» в структурированные модули.
Рассмотрим ключевые аспекты, которые отличают плохой код от хорошего в контексте 1С:
Одной из самых горячих тем обсуждения стал пример кода, который на первый взгляд кажется простым и понятным, но таит в себе серьезные проблемы производительности.
Рассмотрим пример функции, которая возвращает страну происхождения из заказа:
Функция СтранаПроисхождения(ЗаказКлиента)
Возврат ЗаказКлиента.Контрагент.ГоловнойКонтрагент.ЮридическийАдрес.Страна;
КонецФункции
На первый взгляд код выглядит «красиво» и лаконично. Однако с технической точки зрения этот код является неоптимальным и потенциально опасным. Разберем почему.
Когда вы обращаетесь к реквизиту ссылочного типа через точку (например, ЗаказКлиента.Контрагент), платформа 1С считывает объект целиком (если он еще не в кэше). В приведенном примере происходит последовательное чтение четырех объектов:
ЗаказКлиента (если передана ссылка).Контрагенты.Контрагенты (Головной).ЮридическийАдрес (если это ссылочный тип).Ради получения одного значения (Страна) мы загружаем в память четыре тяжелых объекта со всеми их табличными частями. Это создает избыточную нагрузку на сервер приложений и сеть.
Если подобная конструкция встречается внутри текста запроса, ситуация становится еще хуже:
ВЫБРАТЬ
Заказ.Контрагент.ГоловнойКонтрагент.ЮридическийАдрес.Страна
ИЗ
Документ.ЗаказКлиента КАК Заказ
Каждая точка в полях запроса трансформируется СУБД (SQL Server или PostgreSQL) в левое соединение (LEFT JOIN). Цепочка из 4 точек создаст 4 вложенных соединения. На больших объемах данных это может привести к неоптимальному плану выполнения запроса и существенному замедлению работы системы.
Код вида Объект.А.Б.В крайне неустойчив. Если у контрагента не заполнен ГоловнойКонтрагент, то обращение к .ЮридическийАдрес вызовет ошибку времени выполнения (попытка получения свойства у Неопределено или пустой ссылки). В запросах это может привести к значению NULL, которое нужно обрабатывать отдельно.
Чтобы отрефакторить этот код, необходимо использовать прямой запрос к базе данных. Это позволит выбрать ровно одно поле, не поднимая в память лишние объекты, и корректно обработать пустые значения.
Рассмотрим вариант рефакторинга с использованием запроса:
Функция ПолучитьСтрануЗаказа(ЗаказКлиента) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЕСТЬNULL(ЗаказКлиента.Контрагент.ГоловнойКонтрагент.ЮридическийАдрес.Страна, ЗНАЧЕНИЕ(Справочник.СтраныМира.ПустаяСсылка)) как Страна
|ИЗ
| Документ.ЗаказКлиента КАК ЗаказКлиента
|ГДЕ
| ЗаказКлиента.Ссылка = &ЗаказКлиента";
Запрос.УстановитьПараметр("ЗаказКлиента", ЗаказКлиента);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Выборка.Следующий();
Возврат Выборка.Страна;
Иначе
// Обработка ситуации, когда заказ не найден или ссылка битая
Возврат Справочники.СтраныМира.ПустаяСсылка();
КонецЕсли;
КонецФункции
Важные замечания к этому коду:
ЕСТЬNULL, чтобы гарантированно вернуть значение правильного типа, даже если цепочка данных прервалась (например, нет головного контрагента).ЛЕВОЕ СОЕДИНЕНИЕ, что делает запрос более громоздким, но полностью контролируемым.Пример идеального запроса (максимальная оптимизация):
ВЫБРАТЬ
Адреса.Страна КАК Страна
ИЗ
Документ.ЗаказКлиента КАК Заказ
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Контрагент
ПО Заказ.Контрагент = Контрагент.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК ГоловнойКонтрагент
ПО Контрагент.ГоловнойКонтрагент = ГоловнойКонтрагент.Ссылка
// Предполагаем, что адрес хранится в контактной информации или реквизите
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Адреса КАК Адреса
ПО ГоловнойКонтрагент.ЮридическийАдрес = Адреса.Ссылка
ГДЕ
Заказ.Ссылка = &ЗаказКлиента
В ходе анализа мы затронули важный вопрос: нужно ли вообще вычислять страну «на лету» через такую длинную цепочку?
Денормализация в 1С: Архитектура 1С часто предполагает осознанное нарушение нормальных форм БД. Если страна происхождения важна для бизнес-логики заказа (например, от нее зависит маршрут логистики или налогообложение), то правильно хранить значение страны непосредственно в документе ЗаказКлиента.
Почему это правильно:
Таким образом, лучший рефакторинг в данном случае — это изменение структуры метаданных (добавление реквизита) и заполнение его при записи документа, а не написание сложной функции выборки.
Еще одной темой стало написание сложных алгоритмов, таких как склонение ФИО. Часто встречается код, написанный «в потоке», который трудно читать. Рассмотрим, как можно улучшить структуру кода.
Пример подхода к рефакторингу сложной функции (на примере рекурсивной обработки строк):
// Плохой пример (условно):
// Функция Падежик(z1,z2...) - непонятные названия переменных,
// отсутствие комментариев, сложная вложенность.
При рефакторинге такого кода мы должны:
z1 — ИсходнаяСтрока, вместо z2 — Падеж).Пример того, как может выглядеть структурированный подход к задаче склонения (упрощенно):
Функция СклонятьПолноеИмя(Знач ПолноеИмя, Знач Падеж) Экспорт
МассивСлов = СтрРазделить(ПолноеИмя, " ");
Результат = "";
Для Каждого Слово Из МассивСлов Цикл
// Выделяем логику склонения одного слова в отдельную функцию
СклоненноеСлово = СклонитьОдноСлово(Слово, Падеж);
Результат = Результат + СклоненноеСлово + " ";
КонецЦикла;
Возврат СокрЛП(Результат);
КонецФункции
Рефакторинг касается не только серверной логики, но и кода форм. Часто код выбора периода дублируется в разных командах. Рассмотрим пример использования паттерна передачи контекста через ДополнительныеПараметры.
Вместо того чтобы писать отдельный обработчик для каждой пары дат (период гарантии, период ответов), мы создаем универсальный механизм:
&НаКлиенте
Процедура ПоказатьВыборПериода(ИмяРеквизитаДатаС, ИмяРеквизитаДатаПо)
// Создаем структуру для передачи контекста (какие реквизиты заполнять)
ДополнительныеПараметры = Новый Структура;
ДополнительныеПараметры.Вставить("ДатаС", ИмяРеквизитаДатаС);
ДополнительныеПараметры.Вставить("ДатаПо", ИмяРеквизитаДатаПо);
// Инициализируем стандартный период текущими значениями
Период = Новый СтандартныйПериод;
Период.ДатаНачала = Объект[ИмяРеквизитаДатаС];
Период.ДатаОкончания = Объект[ИмяРеквизитаДатаПо];
Обработчик = Новый ОписаниеОповещения("ПериодВыборЗавершение", ЭтотОбъект, ДополнительныеПараметры);
Диалог = Новый ДиалогРедактированияСтандартногоПериода;
Диалог.Период = Период;
Диалог.Показать(Обработчик);
КонецПроцедуры
&НаКлиенте
Процедура ПериодВыборЗавершение(Результат, ДополнительныеПараметры) Экспорт
Если Результат = Неопределено Тогда
Возврат;
КонецЕсли;
// Используем переданный контекст для заполнения нужных полей
Объект[ДополнительныеПараметры.ДатаС] = Результат.ДатаНачала;
Объект[ДополнительныеПараметры.ДатаПо] = Результат.ДатаОкончания;
КонецПроцедуры
Преимущества такого подхода:
Анализируя обсуждение, можно сформулировать несколько правил качественного программирования в 1С:
А.Б.В.Г) ни в коде, ни в запросах. Это скрывает проблемы производительности и обработки NULL.Рефакторинг — это не разовая акция, а постоянная часть процесса разработки. Писать сразу «чисто» получается редко, поэтому возвращаться к коду для его улучшения — это признак профессионализма, а не ошибка.