В одной из недавних версий Библиотеки стандартных подсистем (БСП), начиная примерно с релиза 3.1.10.357, многие разработчики столкнулись с неожиданным поведением. Привычные методы ОбщегоНазначения.ВычислитьВБезопасномРежиме и ОбщегоНазначения.ВыполнитьВБезопасномРежиме перестали сохранять изменения в базе данных. Код, который годами работал, особенно в разработках для сервиса "1С:Фреш", внезапно перестал выполнять свою основную функцию — модификацию данных. В этой статье мы подробно разберем, почему так произошло, что это не ошибка, а осознанное изменение со стороны "1С", и как правильно адаптировать свой код к новым реалиям.
Давайте проанализируем ситуацию. Ранее функции безопасного выполнения кода использовались некоторыми разработчиками как способ обойти ограничения аудита в "1С:Фреш". Можно было поместить код, изменяющий данные (например, создающий или проводящий документы), внутрь вызова ВычислитьВБезопасномРежиме, и такая обработка успешно проходила проверку, так как формально вызов "безопасной" функции не считался нарушением.
Фирма "1С" решила закрыть эту уязвимость, чтобы повысить надежность и безопасность облачного сервиса. Теперь логика этих функций изменилась кардинально:
НачатьТранзакцию().ОтменитьТранзакцию().Результат: любые изменения, которые ваш код пытался внести в базу данных — создание объектов, запись, проведение — гарантированно откатываются. Функция стала полностью соответствовать своему названию: она выполняет вычисления в режиме, который безопасен для данных, то есть не позволяет их изменять.
Это изменение вовсе не означает, что теперь невозможно изменять данные в обработках для "1С:Фреш". Оно означает, что теперь разработчик должен делать это явно и осознанно, беря на себя ответственность за управление транзакциями. Ключ к решению проблемы кроется в поведении самой функции: если она вызывается внутри уже активной транзакции, она не создает свою и, соответственно, не пытается ее отменить.
Рассмотрим по шагам, как правильно организовать код сейчас.
Этот подход подходит, когда вся операция должна быть атомарной: либо все изменения успешно сохраняются, либо все откатываются в случае ошибки.
Шаг 1: Перед выполнением кода, который будет менять данные, необходимо явно начать транзакцию.
Шаг 2: Весь блок кода следует обернуть в конструкцию Попытка ... Исключение ... КонецПопытки для корректной обработки возможных ошибок.
Шаг 3: Внутри блока Попытка вызывайте ОбщегоНазначения.ВычислитьВБезопасномРежиме.
Шаг 4: Если код выполнился без ошибок, зафиксируйте транзакцию с помощью ЗафиксироватьТранзакцию().
Шаг 5: В блоке Исключение обязательно отмените транзакцию с помощью ОтменитьТранзакцию(). Это критически важный шаг для поддержания целостности данных.
Посмотрим на пример. Предположим, у нас есть код, который создает и проводит документ реализации.
Старый код (больше не работает):
// Этот код создавал документ, но теперь все изменения будут отменены
СтрокаКода = "
|НовыйДокумент = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
|НовыйДокумент.Дата = ТекущаяДата();
|// ...заполнение документа...
|НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
|";
ОбщегоНазначения.ВычислитьВБезопасномРежиме(СтрокаКода);
Новый, правильный код с явным управлением транзакцией:
НачатьТранзакцию();
Попытка
// Тот же самый код, который будет выполняться
СтрокаКода = "
|НовыйДокумент = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
|НовыйДокумент.Дата = ТекущаяДата();
|// ...заполнение документа...
|НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
|";
// Вызываем функцию внутри нашей транзакции
ОбщегоНазначения.ВычислитьВБезопасномРежиме(СтрокаКода);
// Если ошибок не было, фиксируем изменения
ЗафиксироватьТранзакцию();
Сообщить("Документ успешно создан и проведен!");
Исключение
// В случае любой ошибки откатываем все изменения
ОтменитьТранзакцию();
Сообщить("Произошла ошибка. Все изменения отменены.");
// Здесь можно добавить логирование или вывод подробной информации об ошибке
ВызватьИсключение ОписаниеОшибки();
КонецПопытки;
Таким образом, мы явно показываем системе, что берем управление транзакцией на себя. Функция ВычислитьВБезопасномРежиме "видит" нашу активную транзакцию и не вмешивается в ее жизненный цикл.
Часто возникает задача обработать большой массив данных, например, загрузить 100 документов из Excel. В этом случае откат всей операции из-за ошибки в одном документе нежелателен. Если 99 документов корректны, а один — нет, логично сохранить 99 и сообщить об ошибке в одном.
Для этого мы можем создавать отдельную, "маленькую" транзакцию для обработки каждого элемента в цикле.
Проанализируем пример:
МассивДанныхДляЗагрузки = ПолучитьДанныеИзВнешнегоИсточника();
Для Каждого ЭлементДанных Из МассивДанныхДляЗагрузки Цикл
// Начинаем транзакцию для одного элемента
НачатьТранзакцию();
Попытка
// Формируем код для выполнения на основе данных текущего элемента
СтрокаКода = СформироватьКодДляСозданияДокумента(ЭлементДанных);
ОбщегоНазначения.ВычислитьВБезопасномРежиме(СтрокаКода);
// Фиксируем транзакцию только для этого успешного элемента
ЗафиксироватьТранзакцию();
Сообщить("Элемент '" + ЭлементДанных.Представление + "' успешно обработан.");
Исключение
// Откатываем транзакцию только для сбойного элемента
ОтменитьТранзакцию();
Сообщить("Ошибка при обработке элемента '" + ЭлементДанных.Представление + "'. Элемент пропущен.");
КонецПопытки;
КонецЦикла;
Этот подход делает вашу обработку более устойчивой к ошибкам в исходных данных. Ошибка в одной строке не приведет к отмене всей многочасовой загрузки.
В заключение отметим, что данное изменение — это не "баг", а "фича". Это шаг в сторону более безопасного и предсказуемого кода. "1С" заставляет разработчиков следовать лучшим практикам, явно управляя операциями изменения данных. Хотя это и требует адаптации существующих разработок, в долгосрочной перспективе это повышает надежность и качество программных решений, особенно в критически важной облачной среде.