Skip to content
1c-toolingPublic

About

Легковесная библиотека для 1С:Предприятие, реализующая паттерн Result (монада Either)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Exodus (Исход) 🕊️

Exodus — это легковесная клиент-серверная библиотека для 1С:Предприятие, реализующая паттерн Result (монада Either).

Она решает главную проблему процедурного кода — неявную обработку ошибок и "пирамиды смерти" (глубокие вложенности Если...Тогда). Паттерн помогает писать надежный, читаемый код, в котором сложно "забыть" проверить ошибку, а бизнес-логика визуально отделена от обработки сбоев.

1. Зачем нужен этот модуль?

Проблема типового подхода 1С

Обычно разработчики возвращают Неопределено или Ложь, а текст ошибки передают через параметр по ссылке: Функция СоздатьДокумент(Данные, ТекстОшибки = ""):

  • Неявные контракты: По сигнатуре метода неочевидно, может ли он вернуть ошибку.
  • Стрелочный код: Постоянные проверки Если Данные = Неопределено Тогда... сдвигают полезный код глубоко вправо.
  • Сайд-эффекты: Изменение переменных по ссылке усложняет чтение кода.
  • Риск NullReference: Если забыть проверить результат, система упадет с ошибкой "Значение не является значением объектного типа".

Решение: Паттерн Result

Модуль инкапсулирует результат выполнения любой операции в единый стандартизированный объект. Функция всегда возвращает структуру, которая находится строго в одном из двух состояний:

  1. Успех (Ок): содержит полезные данные.
  2. Провал (Ошибка): содержит понятное описание ошибки и технические детали.

2. Установка и настройка

  1. Создайте новый общий модуль с именем Исход.
  2. В свойствах модуля установите флаги:
    • ✅ Сервер (здесь выполняется основная логика конвейеров).
    • ✅ Клиент (управляемое приложение) (позволяет проверять статус на стороне фронтенда).
    • ✅ Внешнее соединение (для интеграций).
    • ❌ Вызов сервера (снимите флаг, архитектурно он здесь не нужен).
  3. Скопируйте содержимое файла src/CommonModules/Исход/Module.bsl в модуль. Готово!

3. Базовое использование: Было / Стало

Задача: Загрузить данные из внешнего API, создать документ Заказ, рассчитать по нему скидку и провести.

Как БЫЛО (Типовой код)

ТекстОшибки = "";
СырыеДанные = ПолучитьДанныеAPI(ИД, ТекстОшибки);
Если СырыеДанные <> Неопределено Тогда
    ДокументСсылка = СоздатьЗаказ(СырыеДанные, ТекстОшибки);
    Если ЗначениеЗаполнено(ДокументСсылка) Тогда
        УспехСкидки = РассчитатьСкидки(ДокументСсылка, 10, ТекстОшибки);
        Если УспехСкидки Тогда
            ПровестиДокумент(ДокументСсылка, ТекстОшибки);
            // ... и так далее
        КонецЕсли;
    КонецЕсли;
КонецЕсли;

Если ЗначениеЗаполнено(ТекстОшибки) Тогда
    Сообщить(ТекстОшибки);
КонецЕсли;

Как СТАЛО (С использованием Исход)

Код становится линейным конвейером. Если на шаге СоздатьЗаказ произойдет сбой, следующие шаги (РассчитатьСкидки) просто проигнорируются, а ошибка "пролетит" в самый конец:

// Передаем доп. параметр "10" (процент скидки) массивом или значением
Рез = Исход.Ок(ИД);
Рез = Исход.СвязатьДинамически(Рез, "Интеграция.ПолучитьДанныеAPI");
Рез = Исход.СвязатьДинамически(Рез, "ЗаказыКлиентов.СоздатьЗаказ");
Рез = Исход.СвязатьДинамически(Рез, "Скидки.РассчитатьСкидки", 10);
Рез = Исход.СвязатьДинамически(Рез, "ЗаказыКлиентов.ПровестиДокумент");

Если Исход.ЭтоОшибка(Рез) Тогда
    Сообщить(Исход.ТекстОшибки(Рез));
Иначе
    Сообщить("Заказ успешно проведен! Ссылка: " + Исход.Извлечь(Рез));
КонецЕсли;

4. Как возвращать значения

Вместо возврата произвольных значений, ваши функции бизнес-слоя теперь должны возвращать вызовы Исход.Ок() или Исход.Ошибка():

// Общий модуль: СкладскойУчет
Функция ЗарезервироватьТовар(Знач Товар, Знач Количество) Экспорт
    
    Остаток = ПолучитьОстаток(Товар);
    Если Остаток < Количество Тогда
        // Возвращаем ошибку + доп. данные для логов
        ДопДанные = Новый Структура("Товар, Остаток", Товар, Остаток);
        Возврат Исход.Ошибка("Недостаточно товара на складе", ДопДанные);
    КонецЕсли;
    
    // Выполняем логику резервирования...
    
    // Возвращаем успешный результат
    Возврат Исход.Ок(Количество);
    
КонецФункции

5. Руководство по методам (API)

5.1. Проверка состояния и получение текста ошибки

Используется на самом верхнем уровне (например, в обработчике нажатия кнопки на форме):

  • ЭтоУспех(Рез) / ЭтоОшибка(Рез)
  • ТекстОшибки(Рез) (Краткий текст для UI)
  • ПодробныйТекстОшибки(Рез) (Детальный текст для логов)
Рез = СкладскойУчет.ЗарезервироватьТовар(Номенклатура, 5);

Если Исход.ЭтоОшибка(Рез) Тогда
    // Для пользователя: "Недостаточно товара на складе"
    Сообщить(Исход.ТекстОшибки(Рез)); 
    
    // В журнал регистрации упадет и текст, и структура (Товар, Остаток)
    ЗаписьЖурналаРегистрации("Склад", УровеньЖурналаРегистрации.Ошибка,,, Исход.ПодробныйТекстОшибки(Рез));
    Возврат;
КонецЕсли;

5.2. Извлечение данных

Как получить само значение из успешной монады:

  • ИзвлечьИли(Рез, ЗначениеПоУмолчанию): Безопасное извлечение.
  • Извлечь(Рез): Жесткое извлечение (вызовет Исключение 1С при ошибке).
  • РазвернутьИлиВызватьИсключение(Рез): Процедура для прерывания транзакции.
Рез = Ценообразование.ПолучитьЦену(Товар);

// Если цену получить не удалось, берем 0
Цена = Исход.ИзвлечьИли(Рез, 0); 

// Если мы уверены, что цена ДОЛЖНА быть, иначе база неконсистентна:
ТочнаяЦена = Исход.Извлечь(Рез); // Упадет, если Рез = Ошибка

// Идеально перед Записать() внутри транзакции:
Исход.РазвернутьИлиВызватьИсключение(Рез);

5.3. Монадические операции (Создание конвейеров)

Это ядро библиотеки. Важно понимать разницу между методами:

  • СвязатьДинамически — вызывает функцию, которая САМА возвращает Исход.Ок или Исход.Ошибка.
  • ОтобразитьДинамически — вызывает обычную функцию 1С (возвращающую строку, число и т.д.) и автоматически заворачивает её ответ в Исход.Ок.

(Примечание: Параметр ДопПараметры может быть массивом, если функция ожидает несколько аргументов).

ПараметрыСкидки = Новый Массив;
ПараметрыСкидки.Добавить(ТекущаяДатаСеанса()); // Параметр 2
ПараметрыСкидки.Добавить(Истина);              // Параметр 3

Рез = Исход.Ок(ДокументСсылка); // В трубу залетает ссылка (Параметр 1)

// Функция сама вернет Исход:
Рез = Исход.СвязатьДинамически(Рез, "Скидки.ПрименитьСкидку", ПараметрыСкидки);

// Типовая функция возвращает просто Строку. Оборачиваем ее:
Рез = Исход.ОтобразитьДинамически(Рез, "ОбщегоНазначения.ПолучитьПредставлениеОбъекта");

⚠️ Важно: Производительность и Golang-style Функции с суффиксом ...Динамически используют платформенные вызовы Вычислить() и Выполнить(). В 1С такой код компилируется "на лету" (JIT-компиляция) при каждом вызове. Для разовых операций (проведение документа, вызов API) задержка микроскопична и незаметна.

Однако, не рекомендуется использовать методы Динамически в тяжелых циклах (на тысячи итераций) или в очень длинных цепочках вызовов, так как это замедлит выполнение. В таких высоконагруженных участках используйте паттерн раннего возврата в стиле Golang:

// Golang-style для максимальной производительности в циклах/длинных цепочках:
Рез = Интеграция.ПолучитьДанныеAPI(ИД);
Если Исход.ЭтоОшибка(Рез) Тогда 
    Возврат Рез; // Прерываем цепочку и отдаем ошибку наверх
КонецЕсли;

Рез = ЗаказыКлиентов.СоздатьЗаказ(Рез.Значение);
Если Исход.ЭтоОшибка(Рез) Тогда 
    Возврат Рез; 
КонецЕсли;

// И так далее... Код работает максимально быстро без динамической компиляции.

Либо:

Рез = ПолучитьДанныеAPI(Исход.Извлечь(Рез));

// Шаги (выполняются только если Рез - это успех)
Если Исход.Успех(Рез) Тогда 
	Рез = СоздатьЗаказ(Исход.Извлечь(Рез)); 
КонецЕсли;

Если Исход.Успех(Рез) Тогда 
	Рез = РассчитатьСкидки(Исход.Извлечь(Рез), 10); 
КонецЕсли;

Если Исход.Успех(Рез) Тогда 
	Рез = ПровестиДокумент(Исход.Извлечь(Рез)); 
КонецЕсли;

// Финал: Обработка результата (Early return или ветвление)
Если Исход.Ошибка(Рез) Тогда
    Возврат Рез; // Пробрасываем ошибку выше (как в Go!)
КонецЕсли;

Возврат Исход.Ок(Исход.Извлечь(Рез));

5.4. Перехват системных исключений

Суффикс Попытаться... означает, что вызываемый метод будет обернут в Попытка... Исключение. Ошибки платформы (красные окна) превратятся в штатный Исход.Ошибка:

  • ПопытатьсяСвязатьДинамически(Рез, Метод)
  • ПопытатьсяОтобразитьДинамически(Рез, Метод)
// Работа с нестабильным внешним кодом или файлами
Рез = Исход.Ок(ПутьКФайлу);

// Если файл заблокирован, 1С выкинет исключение. Мы его перехватим:
Рез = Исход.ПопытатьсяОтобразитьДинамически(Рез, "ФайловаяСистема.ПрочитатьТекст");

Если Исход.ЭтоОшибка(Рез) Тогда
    // Мы спокойно обработали падение системы!
    Сообщить("Сбой чтения файла: " + Исход.ТекстОшибки(Рез));
КонецЕсли;

5.5. Побочные эффекты (Side-effects)

Используются для вызова ПРОЦЕДУР (которые ничего не возвращают) прямо в середине конвейера:

  • ПриУспехеДинамически(Рез, Процедура)
  • ПриОшибкеДинамически(Рез, Процедура)
Рез = Исход.Ок(ЗаказСсылка);
Рез = Исход.СвязатьДинамически(Рез, "Интеграция.ОтправитьВCRM");

// Вызовется, только если отправка удалась
Рез = Исход.ПриУспехеДинамически(Рез, "Логирование.ЗаписатьУспешнуюОтправку");

// Вызовется, только если произошел сбой
Рез = Исход.ПриОшибкеДинамически(Рез, "Оповещения.ОтправитьПисьмоАдмину");

5.6. Работа с коллекциями и контекстом

  • СобратьМассив(МассивИсходов): Превращает массив результатов в один результат. Если упал хоть один — падает всё.
  • ОбернутьОшибку(Рез, Префикс): Добавляет контекст к ошибке по мере её всплытия.
// 1. Обогащение контекста
Рез = Исход.Ошибка("Таймаут соединения");
Рез = Исход.ОбернутьОшибку(Рез, "Сбой загрузки прайса");
// Итог: "Сбой загрузки прайса: Таймаут соединения"

// 2. Сборка массива (обработка табличной части)
МассивРезультатов = Новый Массив;
Для Каждого Строка Из ТабличнаяЧасть Цикл
    МассивРезультатов.Добавить( СкладскойУчет.ПроверитьСтроку(Строка) );
КонецЦикла;

// Если хоть одна строка с ошибкой, ИтоговыйРез будет содержать эту ошибку.
// Если все ок - ИтоговыйРез.Значение будет содержать массив результатов проверок.
ИтоговыйРез = Исход.СобратьМассив(МассивРезультатов);

5.7. Служебные функции

  • Версия(): Возвращает версию модуля (SemVer), полезно для проверки совместимости в сложных решениях.

6. Строгие правила работы с модулем

  • Используйте только API модуля. Строго запрещено напрямую обращаться к полям структуры (например, писать Если Рез.Успех Тогда). Используйте Если Исход.ЭтоУспех(Рез) Тогда. Это защитит ваш код от любых будущих изменений под капотом библиотеки.
  • Исключения — для системы, Исход — для бизнеса. Если упал SQL-сервер — это системный сбой (оборачиваем через ПопытатьсяСвязатьДинамически). Если у клиента не заполнен ИНН — это штатное бизнес-событие, возвращайте Исход.Ошибка() напрямую, без ВызватьИсключение.
  • Требования к функциям конвейера. Функции, вызываемые через динамические методы (СвязатьДинамически и др.), должны быть Экспортными и располагаться на сервере в общих модулях или модулях менеджера. Для тяжелых вычислений отдавайте предпочтение прямым вызовам в стиле Golang (с проверкой ЭтоОшибка()).

About

Легковесная библиотека для 1С:Предприятие, реализующая паттерн Result (монада Either)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages