В среде разработчиков «1С:Предприятие» не утихают споры о чистоте кода, правильном проектировании и целесообразности использования тех или иных синтаксических конструкций. Один из наиболее глубоких методологических вопросов касается разделения методов на процедуры и функции. В некоторых командах принимается негласный или даже закрепленный в стандартах регламент: использовать исключительно функции, полностью отказавшись от процедур, независимо от того, требуется ли возвращать результат работы метода.
Давайте подробно разберем эту ситуацию, проанализируем аргументы сторонников и противников функционального подхода, рассмотрим практические примеры кода, шаблоны проектирования и выясним, как это влияет на поддержку, отладку и производительность конфигураций.
Для начала вспомним классическое определение синтаксиса встроенного языка «1С»:
Возврат.Приверженцы «чистого функционального подхода» утверждают, что разделение на процедуры и функции является искусственным и устаревшим наследием ранних языков программирования. В современных языках (таких как Kotlin, Scala, Haskell или Go) любое объявление метода фактически является функцией, которая возвращает значение (даже если это пустой тип void или кортеж из нескольких элементов). Посмотрим, какие реальные практические выгоды и проблемы несет этот подход применительно к платформе «1С:Предприятие».
Один из самых весомых практических аргументов в пользу функций — это поведение платформы в режиме отладки. Проанализируем типичную ситуацию:
Вы остановились на точке останова внутри сложного алгоритма и хотите проверить, как отработает сопутствующий метод. Если этот метод объявлен как Процедура, вы не сможете вызвать его в табло отладки (сочетание клавиш Shift+F9), поскольку процедура не возвращает значения, и платформа выдаст ошибку вычисления выражения. Если же метод объявлен как Функция, вы можете легко запустить его в табло, передать нужные параметры и сразу же визуально оценить результат вычислений (или возвращаемую структуру ошибок), не прерывая сеанс отладки.
Рассмотрим жизненный цикл разработки. Очень часто метод пишется как простое действие:
Процедура РассчитатьЗадолженность(Контрагент)
// Вычисления и запись в базу данных...
КонецПроцедуры
Через некоторое время бизнес-требования меняются. Теперь вызывающему коду необходимо знать, успешно ли прошел расчет задолженности, возникли ли ошибки блокировки данных или какова итоговая сумма. Если мы использовали процедуру, у нас есть два пути:
Отказ или Результат и менять его внутри процедуры. Это ухудшает читаемость кода, так как вызывающий код вынужден инициализировать переменные перед вызовом.Процедура на Функция, добавить оператор Возврат и обойти все места вызова этого метода в конфигурации, чтобы скорректировать синтаксис.Если бы мы изначально спроектировали этот метод как функцию, мы бы просто дописали оператор Возврат в конце алгоритма. При этом старые вызовы метода, где возвращаемое значение игнорировалось, продолжили бы работать без каких-либо изменений:
// Старый вызов продолжает работать:
РассчитатьЗадолженность(ТекущийКонтрагент);
// Новый вызов использует возвращаемое значение:
Если Не РассчитатьЗадолженность(ТекущийКонтрагент) Тогда
ВызватьИсключение "Ошибка расчета!";
КонецЕсли;
Вместо хаотичного возврата простых типов (Истина/Ложь, Неопределено) профессиональные разработчики используют стандартизированные структуры возврата. Давайте разберем по шагам создание такого универсального шаблона.
Ниже представлен пример функции, выполняющей технологическую операцию с гарантированным возвратом детальной информации о результате:
Функция ВыполнитьТехнологическуюОперацию(ПараметрыОперации) Экспорт
// Инициализируем стандартную структуру ответа
РезультатФункции = Новый Структура;
РезультатФункции.Вставить("ВыполненоУспешно", Истина);
РезультатФункции.Вставить("МассивОшибок", Новый Массив);
РезультатФункции.Вставить("ДанныеОперации", Неопределено);
Попытка
// Основная бизнес-логика метода
РезультатОперации = ВыполнитьЗаписьВБазу(ПараметрыОперации);
РезультатФункции.ДанныеОперации = РезультатОперации;
Исключение
// В случае падения перехватываем ошибку, чтобы код не "упал" у пользователя
РезультатФункции.ВыполненоУспешно = Ложь;
ТекстОшибки = КраткоеОписаниеОшибки(ИнформацияОбОшибке());
РезультатФункции.МассивОшибок.Добавить(ТекстОшибки);
// Дополнительно можно логировать ошибку в Журнал Регистрации
ЗаписатьЛогОшибки(ТекстОшибки);
КонецПопытки;
Возврат РезультатФункции;
КонецФункции
Преимущества такого шаблона очевидны:
Несмотря на технологическое удобство функций, повсеместный отказ от процедур противоречит классической архитектурной концепции CQS (Разделение запросов и команд), а также официальным стандартам разработки фирмы «1С».
Согласно принципу CQS, метод должен быть либо Командой (выполнять действие, менять состояние системы и ничего не возвращать — то есть быть Процедурой), либо Запросом (возвращать вычисленное значение без изменения состояния базы данных — то есть быть чистой Функцией). Смешивание этих подходов («функции-мутанты», которые пишут данные в регистры и одновременно возвращают отчетные структуры) затрудняет понимание логики программы и усложняет модульное тестирование.
Стандарты именования «1С» также строго разграничивают эти понятия:
ЗаполнитьТабличнуюЧасть(), ПровестиДокументСервер().ЗадолженностьПартнера(), ТаблицаОстатковТоваров().Если мы называем метод глаголом (например, РассчитатьСкидки()), но объявляем его как функцию, мы заставляем будущего разработчика гадать: выполняет ли этот метод действие, возвращает ли он значение или делает и то, и другое одновременно?
Рассмотрим еще один важный аспект совместной работы методов — валидацию данных и генерацию исключений. Некоторые программисты считают использование конструкций ВызватьИсключение избыточным, предпочитая везде возвращать структуру ошибки. Проанализируем пример «правильной» валидации с контролируемым падением:
Функция ГлавноеСвойствоОбъекта(Объект) Экспорт
Результат = Неопределено;
Если Объект.Пол = Перечисления.ПолФизическихЛиц.Мужской Тогда
Результат = "Сила";
ИначеЕсли Объект.Пол = Перечисления.ПолФизическихЛиц.Женский Тогда
Результат = "Красота";
Иначе
// Если тип данных некорректен, "замалчивать" ошибку вредно.
// Вызываем явное исключение для прерывания неверного алгоритма.
ВызватьИсключение "Неизвестный или незаполненный пол объекта: " + Строка(Объект);
КонецЕсли;
Возврат Результат;
КонецФункции
Этот подход гарантирует, что если в систему попадут некорректные данные, алгоритм упадет на этапе тестирования, а не запишет в базу данных искаженную информацию. Исключения незаменимы, когда ошибка является фатальной и продолжать выполнение алгоритма не имеет физического смысла.
Это один из самых критичных технических нюансов, который часто упускают разработчики при проектировании архитектуры конфигурации.
В современной разработке на платформе «1С:Предприятие 8.3» активно используются расширения конфигурации для адаптации типовых решений без снятия их с поддержки. То, как вы объявили исходный метод (как процедуру или как функцию), напрямую влияет на удобство его доработки в расширении:
&Перед и &После. Разработчик расширения может выполнить свой код до или после основной процедуры, не переписывая ее логику целиком. Риск конфликтов при обновлении конфигурации минимален.&После для изменения возвращаемого значения невозможно (так как управление уже вернулось в вызывающий контекст). Разработчик расширения вынужден использовать аннотацию &Вместо и вызывать метод ПродолжитьВызов(). Это усложняет обновление, так как логика перехвата становится более хрупкой и зависимой от сигнатуры исходного метода.Мы проанализировали обе методологии и можем прийти к следующим выводам:
Тотальный отказ от процедур в пользу функций является избыточной мерой («серебряной пулей»), которая решает одни проблемы (удобство отладки и рефакторинга), но порождает другие (нарушение стандартов именования, усложнение разработки расширений, ухудшение читаемости кода). Наиболее эффективным является сбалансированный подход.
Рекомендуется придерживаться следующих правил:
Возврат. Да, платформа «1С» не выдаст ошибку компиляции и вернет Неопределено по умолчанию, но инструменты статического анализа кода (такие как EDT или SonarQube) будут ругаться на такой код, снижая показатели качества вашего проекта.