При разработке конфигураций на платформе 1С:Предприятие 8.3 каждый программист неизбежно сталкивается с дилеммой: в каком модуле разместить программный код? Начинающие разработчики часто совершают ошибку, помещая всю логику в модуль формы, так как это кажется наиболее простым и быстрым решением. Однако по мере роста системы, внедрения интеграций и автоматизации процессов такой подход превращается в технический долг, усложняя поддержку и снижая производительность решения.
В этой статье мы подробно разберем стандарты разработки фирмы 1С, рассмотрим влияние архитектурных решений на быстродействие системы и сформулируем четкие правила распределения кода между модулем формы, модулем объекта, модулем менеджера и общими модулями.
Для понимания того, где должен располагаться код, обратимся к классической концепции проектирования программного обеспечения. Любое современное приложение состоит как минимум из двух слоев: интерфейса пользователя (UI) и бизнес-логики (Domain Logic).
В контексте платформы 1С это разделение выглядит следующим образом:
Проанализируем известную цитату Стива Макконнелла из книги «Совершенный код», которая отлично описывает последствия нарушения этого правила: если логику проверки корректности данных или расчетов поместить в форму, то со временем для программного выполнения этих действий нам придется программно открывать и держать скрытыми формы, либо дублировать код в другие места системы. В среде 1С это правило работает на 100%.
Основное назначение модуля формы — управление элементами управления на экране, реагирование на действия пользователя (нажатие кнопок, изменение реквизитов) и обеспечение юзабилити.
Что нужно писать в модуле формы:
Чего категорически не должно быть в модуле формы:
Посмотрим на классический антипаттерн, когда разработчик пытается вызвать экспортный метод формы программно:
// Пример плохого тона в разработке
ФормаДокумента = ПолучитьФорму("Документ.РеализацияТоваровУслуг.Форма.ФормаДокумента");
ФормаДокумента.РассчитатьСкидкиНаСервере();
Такой подход делает систему крайне хрупкой. Во-первых, метод ПолучитьФорму доступен только в клиентском контексте, что делает невозможным выполнение этого кода в фоновых или регламентных заданиях. Во-вторых, создание формы — это ресурсоемкая операция, требующая инициализации интерфейсных механизмов, которые совершенно не нужны при программной обработке данных.
Модуль объекта предназначен для реализации свойств и методов конкретного экземпляра прикладного объекта (например, конкретного документа Реализация товаров и услуг №001 или элемента справочника Контрагенты - Ромашка).
Код в модуле объекта оперирует свойствами через ключевое слово ЭтотОбъект или напрямую обращается к реквизитам и табличным частям, которые находятся в оперативной памяти сервера.
Что нужно писать в модуле объекта:
ПередЗаписью, ПриЗаписи, ОбработкаПроведения, ОбработкаЗаполнения, ПередУдалением.Разберем пример правильного размещения кода. Если нам необходимо автоматически рассчитывать сумму строки при изменении количества или цены, то сама реакция на изменение на форме должна лишь вызывать процедуру пересчета, которая декларирована в модуле объекта (или общем модуле):
// В модуле формы документа
&НаКлиенте
Процедура ТоварыКоличествоПриИзменении(Элемент)
СтрокаТабличнойЧасти = Элементы.Товары.ТекущиеДанные;
// Вызываем серверный расчет, передав строку
РассчитатьСуммуСтрокиНаСервере(СтрокаТабличнойЧасти);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура РассчитатьСуммуСтрокиНаСервере(СтрокаТабличнойЧасти)
// Бизнес-логика расчета вынесена за пределы контекста формы
СтрокаТабличнойЧасти.Сумма = СтрокаТабличнойЧасти.Количество * СтрокаТабличнойЧасти.Цена;
КонецПроцедуры
Вызов методов модуля объекта из управляемой формы сопряжен с накладными расходами. Данные на клиенте хранятся в специальном "легком" формате (тип ДанныеФормыСтруктура). При вызове серверного метода формы, если нам требуется обратиться к методам объекта, нам приходится выполнять конвертацию:
// В модуле формы
&НаСервере
Процедура ВыполнитьДействиеНаСервере()
ДокументОбъект = РеквизитФормыВЗначение("Объект");
ДокументОбъект.ВыполнитьБизнесАлгоритм();
ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
КонецПроцедуры
Проанализируем, что происходит "под капотом" платформы в этот момент. Платформа берет данные формы, сериализует их, отправляет на сервер, десериализует, создает полноценный "тяжелый" объект ДокументОбъект со всеми его внутренними механизмами, выполняет алгоритм, а затем проделывает обратный путь для обновления данных на форме. Если документ содержит тысячи строк в табличных частях, частые вызовы РеквизитФормыВЗначение приведут к серьезным просадкам производительности и перерасходу памяти сервера.
Решение: для оптимизации работы интерфейса расчетные алгоритмы, не требующие записи объекта в базу данных, лучше выносить в неконтекстные серверные методы формы (&НаСервереБезКонтекста) или в общие модули, передавая туда только необходимые для расчета структуры или массивы данных, а не весь объект целиком.
Модуль менеджера управляет таблицей базы данных в целом, а не конкретной строкой (объектом). Его главное преимущество — он доступен без создания тяжелого объекта в памяти сервера. Для вызова его методов достаточно иметь только ссылку на объект или передать параметры.
Что нужно писать в модуле менеджера:
Пример вызова метода печати из модуля менеджера:
// Вызов из любого места системы по ссылке на документ
Документы.РеализацияТоваровУслуг.Печать(МассивСсылок, ПараметрыПечати, КоллекцияПечатныхФорм);
Этот код отработает мгновенно, так как платформе не нужно инициализировать структуру самого документа в оперативной памяти — менеджер работает напрямую с базой данных через запросы.
Рассмотрим ситуацию с точки зрения развития бизнеса. Рано или поздно любая компания приходит к необходимости автоматизации рутинных процессов. Это могут быть регламентные задания (например, автоматическое закрытие месяца, автоматическое выставление счетов по окончании периода) или интеграция с внешними системами (API, интернет-магазины, EDI-системы).
В таких сценариях инициатором действий выступает не человек, кликающий по кнопкам формы, а программный код (робот). Роботу абсолютно недоступен интерфейс управляемой формы. Если вся бизнес-логика заполнения и расчета документа написана в модуле формы, то робот просто не сможет корректно создать и провести этот документ. Ему придется эмулировать открытие формы, что приведет к ошибкам выполнения в фоновом задании.
Правильно спроектированный документ должен легко создаваться и заполняться программно всего несколькими строками кода:
НовыйДокумент = Документы.ЗаказПокупателя.СоздатьДокумент();
НовыйДокумент.ЗаполнитьПоУмолчанию(); // Метод в модуле объекта
НовыйДокумент.Контрагент = НайденыйКонтрагент;
НовыйДокумент.ЗаполнитьТоварыПоСоглашению(); // Бизнес-логика в модуле объекта
НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
Если этот стандарт соблюден, то система готова к любым видам интеграции, масштабированию и автоматизации процессов.
Для закрепления материала систематизируем правила распределения кода в виде следующей схемы выбора:
ОбработкаЗаполнения.ПередЗаписью или ПриЗаписи.Соблюдение этих простых архитектурных правил гарантирует высокое быстродействие вашей системы, простоту ее поддержки, легкую интеграцию с внешним миром и готовность к покрытию кода автоматическими Unit-тестами.