error-handling | Skill Performance & Reviews | TopRankSkills

TopRank Skills

Home / Skills / tools / error-handling

error-handling

maintained by SteelMorgan

star 22 account_tree 2 verified_user MIT License
bolt View GitHub

name: error-handling description: Обработка ошибок, транзакции и блокировки. Этот навык учит агента правильно обрабатывать ошибки, управлять транзакциями и блокировками в 1С.

Обработка ошибок, транзакции и блокировки

Назначение

Этот навык учит агента правильно обрабатывать ошибки, управлять транзакциями и блокировками в 1С. Ошибки в этих областях — самые опасные: они приводят к потере данных, зависшим блокировкам (deadlock), невозможности работы пользователей и трудновоспроизводимым багам. В отличие от ошибок производительности (тормозит, но работает), ошибки транзакций и блокировок могут полностью остановить работу предприятия.

Ключевой принцип: В 1С нет автоматического управления транзакциями (как в некоторых фреймворках). Разработчик вручную управляет началом, фиксацией и откатом транзакций. Каждая незакрытая транзакция — потенциальная катастрофа.

Источники:


Сводка правил

# Правило Обоснование
1 Всегда логировать в Исключение Диагностика, аудит
2 Канонический паттерн транзакций Предотвращение зависших транзакций
3 ТранзакцияАктивна() — защитно, не заместительно Корректное вложение транзакций
4 Минимизация времени транзакции Снижение конкуренции
5 БлокировкаДанных перед чтением Предотвращение race conditions
6 ЗаблокироватьДанныеДляРедактирования Предотвращение lost update
7 ЗаписьЖурналаРегистрации по стандарту Структурированная диагностика
8 Понятные сообщения пользователю UX при ошибках
9 ВызватьИсключение без/с параметром Сохранение стека vs понятность
10 Поэлементная обработка массовых операций Одна ошибка не блокирует все
11 Единый порядок блокировки Предотвращение deadlock
12 Попытка для внешних вызовов Внешние системы ненадёжны

Правило 1: Попытка/Исключение — всегда логируйте, никогда не глотайте

Почему: Проглоченное исключение — самый опасный антипаттерн. Система выглядит работающей, но:

  • Данные не записаны / не обработаны
  • Пользователь не знает об ошибке
  • В журнале регистрации нет следов
  • Отладка невозможна — воспроизвести проблему невозможно без логов

Стандарт ИТС: «В обработчике исключения обязательно фиксировать информацию об ошибке в журнале регистрации».

Канонический паттерн обработки исключений

Попытка
    // Опасная операция
    ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
    // 1. Получаем полную информацию об ошибке
    ИнформацияОбОшибке = ИнформацияОбОшибке();
    
    // 2. Логируем в журнал регистрации
    ЗаписьЖурналаРегистрации(
        НСтр("ru = 'Проведение документа'"),                     // Имя события
        УровеньЖурналаРегистрации.Ошибка,                        // Уровень
        ДокументОбъект.Метаданные(),                              // Метаданные объекта
        ДокументОбъект.Ссылка,                                    // Ссылка на объект
        ПодробноеПредставлениеОшибки(ИнформацияОбОшибке));       // Полный стек ошибки
    
    // 3. Решаем: пробросить или обработать
    ВызватьИсключение;  // Пробрасываем — пусть вызывающий код обработает
КонецПопытки;

Два представления ошибки

Функция Когда использовать Что содержит
КраткоеПредставлениеОшибки() Для отображения пользователю Понятный текст без технических деталей
ПодробноеПредставлениеОшибки() Для журнала регистрации Полный стек вызовов, номера строк, вложенные ошибки

Пример: разные уровни обработки

// Нижний уровень — логирование + проброс
Функция ЗаписатьДокумент(ДокументОбъект)
    Попытка
        ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
        Возврат Истина;
    Исключение
        ЗаписьЖурналаРегистрации(
            НСтр("ru = 'Проведение документа'"),
            УровеньЖурналаРегистрации.Ошибка,
            ДокументОбъект.Метаданные(),
            ДокументОбъект.Ссылка,
            ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
        ВызватьИсключение;
    КонецПопытки;
КонецФункции

// Верхний уровень (обработчик формы) — показ пользователю
&НаКлиенте
Процедура ЗаписатьДокумент(Команда)
    Попытка
        ЗаписатьДокументНаСервере();
    Исключение
        // Пользователю — краткое и понятное сообщение
        ПоказатьПредупреждение(,
            НСтр("ru = 'Не удалось записать документ. Обратитесь к администратору.'"));
    КонецПопытки;
КонецПроцедуры

Правило 2: Канонический паттерн транзакций

Почему: Транзакция в 1С должна быть:

  • Атомарной — либо все операции выполнены, либо ни одна
  • Обязательно закрытаЗафиксироватьТранзакцию() или ОтменитьТранзакцию()
  • Минимальной по длительности — чем дольше транзакция, тем дольше блокируются данные

Незакрытая транзакция блокирует записи в СУБД. Другие пользователи, обращающиеся к тем же данным, будут ждать (таймаут обычно 20 сек) и получат ошибку «Превышено время ожидания блокировки».

Стандарт ИТС: «Транзакции: правила использования» — НачатьТранзакцию() ВСЕГДА должна быть непосредственно перед Попытка.

Канонический паттерн (ОБЯЗАТЕЛЬНЫЙ)

НачатьТранзакцию();
Попытка
    
    // === Блок операций внутри транзакции ===
    
    // 1. Блокировка данных (если нужно — см. правило 5)
    Блокировка = Новый БлокировкаДанных;
    ЭлементБлокировки = Блокировка.Добавить("Документ.РеализацияТоваровУслуг");
    ЭлементБлокировки.УстановитьЗначение("Ссылка", ДокументСсылка);
    Блокировка.Заблокировать();
    
    // 2. Чтение и модификация данных
    ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
    ДокументОбъект.Статус = Перечисления.СтатусыДокументов.Согласован;
    
    // 3. Запись
    ДокументОбъект.Записать();
    
    // === Фиксация — ПОСЛЕДНЯЯ операция перед Исключение ===
    ЗафиксироватьТранзакцию();
    
Исключение
    // === Откат — ПЕРВАЯ операция в блоке Исключение ===
    ОтменитьТранзакцию();
    
    // Логирование ПОСЛЕ отката (запись в ЖР тоже может быть в транзакции!)
    ЗаписьЖурналаРегистрации(
        НСтр("ru = 'Согласование документа'"),
        УровеньЖурналаРегистрации.Ошибка,
        Метаданные.Документы.РеализацияТоваровУслуг,
        ДокументСсылка,
        ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
    
    ВызватьИсключение;
КонецПопытки;

Критические правила порядка

Требование Почему
НачатьТранзакцию() непосредственно перед Попытка Если между ними ошибка — транзакция не закроется
ЗафиксироватьТранзакцию() — последний оператор перед Исключение Любая операция после фиксации, но до конца Попытка, при ошибке не откатится
ОтменитьТранзакцию() — первый оператор в Исключение Логирование тоже может вызвать ошибку; если откат ещё не сделан — каскадная проблема
ЗаписьЖурналаРегистрации() — ПОСЛЕ ОтменитьТранзакцию() Запись в ЖР внутри отменённой транзакции будет потеряна

Неправильные варианты

// ❌ ПЛОХО: НачатьТранзакцию до Попытка с промежуточным кодом
НачатьТранзакцию();
ПодготовитьДанные(); // Если здесь ошибка — транзакция зависнет!
Попытка
    // ...
КонецПопытки;

// ❌ ПЛОХО: ЗаписьЖурнала ДО ОтменитьТранзакцию
Исключение
    ЗаписьЖурналаРегистрации(...); // Может быть потеряна при откате!
    ОтменитьТранзакцию();
КонецПопытки;

// ❌ ПЛОХО: код после ЗафиксироватьТранзакцию, но до конца Попытка
    ЗафиксироватьТранзакцию();
    ОтправитьОповещение(); // Если ошибка здесь — транзакция уже зафиксирована, но блок Исключение выполнится!
Исключение
    ОтменитьТранзакцию(); // Ошибка! Транзакция уже зафиксирована!
КонецПопытки;

Правило 3: Вложенные транзакции — проверяйте ТранзакцияАктивна()

Почему: В 1С транзакции счётчиковые: вложенный НачатьТранзакцию() не создаёт новую транзакцию, а увеличивает счётчик. ОтменитьТранзакцию() помечает транзакцию как «отменённая», и любой последующий ЗафиксироватьТранзакцию() (даже на внешнем уровне) вызовет исключение.

Это значит: если вложенная процедура откатила транзакцию, а внешняя процедура не знает об этом и пытается зафиксировать — ошибка.

Правильно — проверка перед операциями с транзакцией

// Процедура, которая может вызываться как самостоятельно, так и внутри внешней транзакции
Процедура ЗаписатьДанные(ДанныеДляЗаписи)
    
    НачатьТранзакцию();
    Попытка
        
        // ... операции записи ...
        ДокументОбъект.Записать();
        
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        
        ЗаписьЖурналаРегистрации(
            НСтр("ru = 'Запись данных'"),
            УровеньЖурналаРегистрации.Ошибка,,,
            ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
        
        ВызватьИсключение; // ОБЯЗАТЕЛЬНО пробрасываем — внешний код должен знать
    КонецПопытки;
    
КонецПроцедуры

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

Проверка ТранзакцияАктивна() для защитного программирования

// Защитная проверка — полезна в коде, который не знает, был ли откат
Если ТранзакцияАктивна() Тогда
    ОтменитьТранзакцию();
КонецЕсли;

Правило: НЕ используйте ТранзакцияАктивна() как замену правильному паттерну

// ❌ ПЛОХО: ТранзакцияАктивна() как «защита» от дублирования отката
Попытка
    НачатьТранзакцию();
    // ...
    ЗафиксироватьТранзакцию();
Исключение
    Если ТранзакцияАктивна() Тогда  // Маскирует ошибку в структуре кода!
        ОтменитьТранзакцию();
    КонецЕсли;
КонецПопытки;

// ✅ ПРАВИЛЬНО: корректная структура делает проверку ненужной
НачатьТранзакцию();
Попытка
    // ...
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();  // Всегда вызывается ровно один раз
    ВызватьИсключение;
КонецПопытки;

Правило 4: Минимизация времени транзакции

Почему: Пока транзакция открыта, изменённые данные заблокированы в СУБД. Другие сеансы, обращающиеся к тем же данным, ждут завершения транзакции. Таймаут ожидания — обычно 20 секунд, после чего — ошибка.

Длинная транзакция = каскадные блокировки = пользователи не могут работать.

Правильно — минимальный блок транзакции

// Подготовка данных — ВНЕ транзакции
МассивДанных = ПодготовитьДанные();      // Запросы, вычисления — долго, но безопасно
ПроверитьКорректность(МассивДанных);      // Валидация — тоже вне транзакции

// Транзакция — только запись
НачатьТранзакцию();
Попытка
    // Только быстрые операции записи
    Для Каждого ДанныеСтроки Из МассивДанных Цикл
        ЗаписатьСтроку(ДанныеСтроки);
    КонецЦикла;
    
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    ЗаписьЖурналаРегистрации(...);
    ВызватьИсключение;
КонецПопытки;

Неправильно — долгие операции внутри транзакции

// ❌ ПЛОХО: запрос + обработка + внешний вызов внутри транзакции
НачатьТранзакцию();
Попытка
    
    // Долгий запрос — блокирует данные на время выполнения
    Результат = ВыполнитьСложныйЗапрос(); // 5 секунд
    
    // Обработка — ещё время
    Для Каждого Строка Из Результат Цикл
        ОбработатьСтроку(Строка); // Каждая строка — запись в БД
    КонецЦикла;
    
    // Внешний вызов — КАТАСТРОФА в транзакции!
    ОтправитьHTTPЗапрос(Данные); // Таймаут HTTP = 30 сек → блокировка 30 сек!
    
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
КонецПопытки;

Правило 5: Управляемые блокировки — БлокировкаДанных

Почему: Автоматические блокировки 1С (режим «Автоматический») блокируют целые таблицы, что вызывает конфликты при параллельной работе пользователей. Управляемые блокировки позволяют блокировать только нужные записи, радикально снижая конкуренцию.

Стандарт ИТС: «Управляемые блокировки» — использовать БлокировкаДанных для гранулярной блокировки перед чтением-изменением.

Паттерн: прочитать-заблокировать-изменить-записать

НачатьТранзакцию();
Попытка
    
    // 1. СНАЧАЛА блокируем — чтобы между чтением и записью никто не изменил данные
    Блокировка = Новый БлокировкаДанных;
    ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ТоварыНаСкладах");
    ЭлементБлокировки.УстановитьЗначение("Номенклатура", НоменклатураСсылка);
    ЭлементБлокировки.УстановитьЗначение("Склад", СкладСсылка);
    ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
    Блокировка.Заблокировать();
    
    // 2. Теперь читаем — гарантированно актуальные данные
    Запрос = Новый Запрос;
    Запрос.Текст =
    "ВЫБРАТЬ
    |   Остатки.КоличествоОстаток КАК Остаток
    |ИЗ
    |   РегистрНакопления.ТоварыНаСкладах.Остатки(,
    |       Номенклатура = &Номенклатура И Склад = &Склад) КАК Остатки";
    Запрос.УстановитьПараметр("Номенклатура", НоменклатураСсылка);
    Запрос.УстановитьПараметр("Склад", СкладСсылка);
    
    Результат = Запрос.Выполнить();
    Если Результат.Пустой() Тогда
        ВызватьИсключение НСтр("ru = 'Нет остатков на складе.'");
    КонецЕсли;
    
    Выборка = Результат.Выбрать();
    Выборка.Следующий();
    
    // 3. Проверяем и модифицируем
    Если Выборка.Остаток < ТребуемоеКоличество Тогда
        ВызватьИсключение СтрШаблон(
            НСтр("ru = 'Недостаточно остатков. На складе: %1, требуется: %2.'"),
            Выборка.Остаток, ТребуемоеКоличество);
    КонецЕсли;
    
    // 4. Записываем
    // ... запись движений ...
    
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    ЗаписьЖурналаРегистрации(...);
    ВызватьИсключение;
КонецПопытки;

Почему блокировка ПЕРЕД чтением

Без блокировки (race condition):
  Сеанс A: Читает остаток = 10        | Сеанс B: Читает остаток = 10
  Сеанс A: 10 >= 8? Да, списываем 8   | Сеанс B: 10 >= 7? Да, списываем 7
  Итого: списано 15 единиц при остатке 10 → отрицательный остаток!

С блокировкой:
  Сеанс A: Блокирует → Читает 10 → Списывает 8 → Фиксирует → Разблокирует
  Сеанс B: Ждёт блокировку → Читает 2 → 2 < 7 → Ошибка (корректная!)

Правило 6: ЗаблокироватьДанныеДляРедактирования — пессимистичная блокировка объектов

Почему: Когда два пользователя одновременно открывают один документ, оба редактируют и записывают — данные второго перезатрут данные первого (lost update). ЗаблокироватьДанныеДляРедактирования() предотвращает это: второй пользователь получит ошибку «Объект заблокирован пользователем X».

Правильное использование в модуле формы

// Платформа автоматически блокирует объект при открытии формы объекта.
// Ручная блокировка нужна только при программном изменении.

// Программное изменение — с явной блокировкой
Процедура ИзменитьСтатусДокумента(ДокументСсылка, НовыйСтатус)
    
    НачатьТранзакцию();
    Попытка
        
        // Блокируем объект от параллельного редактирования
        ЗаблокироватьДанныеДляРедактирования(ДокументСсылка);
        
        ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
        ДокументОбъект.Статус = НовыйСтатус;
        ДокументОбъект.Записать();
        
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        ЗаписьЖурналаРегистрации(
            НСтр("ru = 'Изменение статуса документа'"),
            УровеньЖурналаРегистрации.Ошибка,,,
            ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
        ВызватьИсключение;
    КонецПопытки;
    
КонецПроцедуры

Разница между блокировками

Тип блокировки Механизм Уровень Когда использовать
БлокировкаДанных СУБД (управляемая) Записи регистров/таблиц Контроль остатков, атомарные операции
ЗаблокироватьДанныеДляРедактирования Сервер 1С (пессимистичная) Объект целиком Предотвращение lost update при редактировании
Автоматическая блокировка СУБД (при записи) Зависит от режима Всегда работает, но грубая

Правило 7: ЗаписьЖурналаРегистрации — правильное логирование

Почему: Журнал регистрации (ЖР) — основной инструмент диагностики проблем в продуктиве. Без структурированных логов разбор инцидентов превращается в гадание. Правильное логирование:

  • Позволяет найти проблему за минуты, а не за часы
  • Даёт контекст: что произошло, с каким объектом, кто инициировал
  • Фильтруется по уровню, событию, объекту, пользователю

Полный формат записи

ЗаписьЖурналаРегистрации(
    ИмяСобытия,         // Строка — иерархическое имя (через точку)
    УровеньСобытия,     // УровеньЖурналаРегистрации — Ошибка/Предупреждение/Информация/Примечание
    МетаданныеОбъекта,  // Объект метаданных — для фильтрации по типу
    Данные,             // Ссылка на объект — для навигации из ЖР
    Комментарий);       // Строка — подробное описание (до 1024 символов)

Уровни и когда их использовать

Уровень Когда Пример
Ошибка Операция не выполнена, данные потеряны или некорректны Ошибка записи, откат транзакции
Предупреждение Операция выполнена, но с ограничениями Документ записан без проведения, данные неполные
Информация Значимые события для аудита Пользователь удалил 100 элементов, обмен данными завершён
Примечание Диагностическая информация Длительность операции, параметры вызова

Пример: структурированное логирование

// Имя события — иерархическое, для фильтрации
// Формат: Подсистема.Событие или Объект.Действие
ИмяСобытия = НСтр("ru = 'ОбменДанными.ОтправкаДанных.Ошибка'");

// Комментарий — максимум информации для диагностики
Комментарий = СтрШаблон(
    НСтр("ru = 'Ошибка при отправке данных в узел ""%1"".
    |Количество объектов: %2.
    |Текст ошибки:
    |%3'"),
    Строка(УзелОбмена),
    КоличествоОбъектов,
    ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));

ЗаписьЖурналаРегистрации(
    ИмяСобытия,
    УровеньЖурналаРегистрации.Ошибка,
    Метаданные.ПланыОбмена.ОбменСКонтрагентами,
    УзелОбмена,
    Комментарий);

Правило 8: Формирование сообщений об ошибках для пользователя

Почему: Пользователь — не разработчик. Сообщение «{ОбщийМодуль.МодульОбработкиДанных.Модуль(123)}: Ошибка при вызове метода контекста» — бесполезно. Пользователю нужно знать:

  1. Что произошло (простыми словами)
  2. Что делать (повторить, обратиться к администратору, проверить данные)
  3. Куда обратиться за помощью

Правильно — человекопонятные сообщения

Попытка
    ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
    // В журнал — техническую информацию
    ЗаписьЖурналаРегистрации(
        НСтр("ru = 'Проведение документа'"),
        УровеньЖурналаРегистрации.Ошибка,
        ДокументОбъект.Метаданные(),
        ДокументОбъект.Ссылка,
        ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
    
    // Пользователю — понятное сообщение
    ТекстДляПользователя = СтрШаблон(
        НСтр("ru = 'Не удалось провести документ ""%1"".
        |Попробуйте повторить операцию. Если ошибка повторяется, обратитесь к администратору.
        |
        |Техническая информация: %2'"),
        ДокументОбъект,
        КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
    
    ВызватьИсключение ТекстДляПользователя;
КонецПопытки;

Неправильно — технический стек вместо объяснения

// ❌ ПЛОХО: проброс технической ошибки пользователю
Попытка
    ДокументОбъект.Записать();
Исключение
    ВызватьИсключение; // Пользователь увидит стек вызовов с номерами строк
КонецПопытки;

Правило 9: Правильный проброс исключений — ВызватьИсключение

Почему: В BSL есть два способа пробросить исключение:

  1. ВызватьИсключение; (без параметра) — пробрасывает оригинальное исключение с полным стеком вызовов
  2. ВызватьИсключение ТекстСообщения; (со строкой) — создаёт новое исключение, оригинальный стек теряется

Когда что использовать

Способ Когда Почему
ВызватьИсключение; В промежуточном коде (модуль объекта, общий модуль) Сохраняет оригинальный стек — легче диагностировать
ВызватьИсключение "Текст"; В обработчиках формы, на границе с пользователем Заменяет технический стек на понятное сообщение

Пример

// Промежуточный слой — пробрасываем оригинал
Процедура ОбработатьДанные(Данные)
    Попытка
        ЗаписатьДанные(Данные);
    Исключение
        ЗаписьЖурналаРегистрации(...);
        ВызватьИсключение; // Оригинальный стек сохранён
    КонецПопытки;
КонецПроцедуры

// Граница с пользователем — формируем понятное сообщение
&НаСервере
Процедура ОбработатьНаСервере()
    Попытка
        ОбработатьДанные(ДанныеФормы);
    Исключение
        ВызватьИсключение СтрШаблон(
            НСтр("ru = 'Ошибка обработки данных: %1'"),
            КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
    КонецПопытки;
КонецПроцедуры

Правило 10: Обработка ошибок при массовых операциях

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

Паттерн: поэлементная обработка с накоплением ошибок

Процедура ПровестиДокументыПакетно(МассивДокументов)
    
    МассивОшибок = Новый Массив;
    
    Для Каждого ДокументСсылка Из МассивДокументов Цикл
        
        НачатьТранзакцию();
        Попытка
            
            ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
            ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
            
            ЗафиксироватьТранзакцию();
        Исключение
            ОтменитьТранзакцию();
            
            ИнфоОшибки = ИнформацияОбОшибке();
            
            // Логируем каждую ошибку отдельно
            ЗаписьЖурналаРегистрации(
                НСтр("ru = 'Пакетное проведение'"),
                УровеньЖурналаРегистрации.Ошибка,
                ДокументСсылка.Метаданные(),
                ДокументСсылка,
                ПодробноеПредставлениеОшибки(ИнфоОшибки));
            
            // Накапливаем для итогового отчёта
            МассивОшибок.Добавить(Новый Структура("Документ, Ошибка",
                ДокументСсылка,
                КраткоеПредставлениеОшибки(ИнфоОшибки)));
            
            // НЕ прерываем цикл — продолжаем с остальными документами
        КонецПопытки;
        
    КонецЦикла;
    
    // Итоговый отчёт
    Если МассивОшибок.Количество() > 0 Тогда
        ТекстИтога = СтрШаблон(
            НСтр("ru = 'Обработано документов: %1. Ошибок: %2.'"),
            МассивДокументов.Количество(),
            МассивОшибок.Количество());
        
        // Выводим ошибки пользователю
        Для Каждого ОписаниеОшибки Из МассивОшибок Цикл
            ОбщегоНазначения.СообщитьПользователю(
                СтрШаблон(НСтр("ru = 'Документ %1: %2'"),
                    ОписаниеОшибки.Документ, ОписаниеОшибки.Ошибка));
        КонецЦикла;
    КонецЕсли;
    
КонецПроцедуры

Правило 11: Таймауты блокировок и deadlock

Почему: Когда два сеанса блокируют данные в разном порядке, возникает deadlock (взаимная блокировка):

  • Сеанс A заблокировал запись 1, ждёт запись 2
  • Сеанс B заблокировал запись 2, ждёт запись 1
  • Оба ждут бесконечно

СУБД обнаруживает deadlock и откатывает одну из транзакций. Это нормально, но нужно обработать.

Правильно — единый порядок блокировки

// ПРАВИЛО: всегда блокируйте ресурсы в одном порядке (например, по ссылке)
МассивСсылок = ОбщегоНазначенияКлиентСервер.СвернутьМассив(МассивДокументов);
МассивСсылок.СортироватьПоЗначению(); // Фиксированный порядок — предотвращает deadlock

Для Каждого Ссылка Из МассивСсылок Цикл
    ЗаблокироватьДанныеДляРедактирования(Ссылка);
КонецЦикла;

Обработка ошибки блокировки

НачатьТранзакцию();
Попытка
    
    Блокировка = Новый БлокировкаДанных;
    ЭлементБлокировки = Блокировка.Добавить("Справочник.Номенклатура");
    ЭлементБлокировки.УстановитьЗначение("Ссылка", НоменклатураСсылка);
    
    Попытка
        Блокировка.Заблокировать();
    Исключение
        // Не удалось заблокировать — данные заняты другим пользователем
        ОтменитьТранзакцию();
        ВызватьИсключение СтрШаблон(
            НСтр("ru = 'Не удалось заблокировать ""%1"". Данные редактируются другим пользователем. Повторите попытку позже.'"),
            НоменклатураСсылка);
    КонецПопытки;
    
    // Данные заблокированы — безопасно работаем
    НоменклатураОбъект = НоменклатураСсылка.ПолучитьОбъект();
    // ...
    НоменклатураОбъект.Записать();
    
    ЗафиксироватьТранзакцию();
Исключение
    Если ТранзакцияАктивна() Тогда
        ОтменитьТранзакцию();
    КонецЕсли;
    ЗаписьЖурналаРегистрации(...);
    ВызватьИсключение;
КонецПопытки;

Правило 12: Попытка/Исключение для внешних вызовов

Почему: Вызовы внешних систем (HTTP, почта, файловые операции) ненадёжны по определению: сеть может быть недоступна, файл — заблокирован, сервис — перегружен. Такие вызовы всегда оборачиваются в Попытка/Исключение с осмысленной обработкой.

Паттерн: HTTP-вызов с повторными попытками

Функция ОтправитьДанныеВоВнешнююСистему(Данные)
    
    МаксимумПопыток = 3;
    
    Для НомерПопытки = 1 По МаксимумПопыток Цикл
        
        Попытка
            
            HTTPСоединение = Новый HTTPСоединение("api.example.com",,,,, 30); // таймаут 30 сек
            Запрос = Новый HTTPЗапрос("/api/data");
            Запрос.УстановитьТелоИзСтроки(Данные);
            
            Ответ = HTTPСоединение.ОтправитьДляОбработки(Запрос);
            
            Если Ответ.КодСостояния = 200 Тогда
                Возврат Истина;
            Иначе
                ВызватьИсключение СтрШаблон(
                    НСтр("ru = 'Сервер вернул код %1: %2'"),
                    Ответ.КодСостояния,
                    Ответ.ПолучитьТелоКакСтроку());
            КонецЕсли;
            
        Исключение
            
            ЗаписьЖурналаРегистрации(
                НСтр("ru = 'Интеграция.ОтправкаДанных'"),
                ?(НомерПопытки < МаксимумПопыток,
                    УровеньЖурналаРегистрации.Предупреждение,
                    УровеньЖурналаРегистрации.Ошибка),,,
                СтрШаблон(НСтр("ru = 'Попытка %1 из %2. Ошибка: %3'"),
                    НомерПопытки, МаксимумПопыток,
                    ПодробноеПредставлениеОшибки(ИнформацияОбОшибке())));
            
            Если НомерПопытки = МаксимумПопыток Тогда
                ВызватьИсключение;
            КонецЕсли;
            
            // Пауза перед повторной попыткой (exponential backoff)
            // В 1С нет встроенного Sleep, но можно использовать обработчик ожидания
            
        КонецПопытки;
        
    КонецЦикла;
    
    Возврат Ложь;
    
КонецФункции

depends_on: []

chat Comments (0)

chat_bubble_outline

No comments yet. Be the first to share your thoughts!

Skill Details

GitHub Stars 22
GitHub Forks 2
Created Mar 2026
Last Updated 3个月前
tools tools system admin

Related Skills

docker-expert
chevron_right
telnyx-network
chevron_right
plex

plex

openclaw
star 2.4k
chevron_right
discord-governance
chevron_right
hetzner-provisioner
chevron_right

Build your own?

Join 12,000+ developers contributing to the Claude ecosystem.