framework_eng/skills/bsl-practices/error-handling/SKILL.md
MUST be used WHEN handling exceptions or controlling transactions in BSL. Provides the canonical Try/Except pattern, transaction rollback rules, and data locking management.
npx skillsauth add steelmorgan/1c-agent-based-dev-framework error-handlingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Key principle: In 1C there is no automatic transaction management. The developer manually controls start, commit, and rollback. Every unclosed transaction is a potential disaster.
A swallowed exception is the most dangerous anti-pattern: data is not written, the user does not know about the error, there is no trace in the registration log, and debugging is impossible.
ITS standard: "In an exception handler, error information must be recorded in the registration log."
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
ИнформацияОбОшибке = ИнформацияОбОшибке();
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Проведение документа'"),
УровеньЖурналаРегистрации.Ошибка,
ДокументОбъект.Метаданные(),
ДокументОбъект.Ссылка,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке));
ВызватьИсключение;
КонецПопытки;
| Function | When to use | What it contains |
|---------|-------------------|-------------|
| КраткоеПредставлениеОшибки() | For displaying to the user | Clear text without technical details |
| ПодробноеПредставлениеОшибки() | For the registration log | Full call stack, line numbers, nested errors |
// Нижний уровень — логирование + проброс
Функция ЗаписатьДокумент(ДокументОбъект)
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Возврат Истина;
Исключение
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Проведение документа'"),
УровеньЖурналаРегистрации.Ошибка,
ДокументОбъект.Метаданные(),
ДокументОбъект.Ссылка,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
КонецФункции
// Верхний уровень (форма) — показ пользователю
&НаКлиенте
Процедура ЗаписатьДокумент(Команда)
Попытка
ЗаписатьДокументНаСервере();
Исключение
ПоказатьПредупреждение(,
НСтр("ru = 'Не удалось записать документ. Обратитесь к администратору.'"));
КонецПопытки;
КонецПроцедуры
An unclosed transaction blocks writes in the DBMS. Other sessions wait (timeout ~20 sec) and receive an error.
ITS standard: "Transactions: usage rules" - НачатьТранзакцию() is ALWAYS placed immediately before Попытка.
НачатьТранзакцию();
Попытка
// 1. Блокировка данных (если нужно — см. правило 5)
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Документ.РеализацияТоваровУслуг");
ЭлементБлокировки.УстановитьЗначение("Ссылка", ДокументСсылка);
Блокировка.Заблокировать();
// 2. Чтение и модификация данных
ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
ДокументОбъект.Статус = Перечисления.СтатусыДокументов.Согласован;
// 3. Запись
ДокументОбъект.Записать();
// === Фиксация — ПОСЛЕДНЯЯ операция перед Исключение ===
ЗафиксироватьТранзакцию();
Исключение
// === Откат — ПЕРВАЯ операция в блоке Исключение ===
ОтменитьТранзакцию();
// Логирование ПОСЛЕ отката (запись в ЖР внутри отменённой транзакции будет потеряна!)
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Согласование документа'"),
УровеньЖурналаРегистрации.Ошибка,
Метаданные.Документы.РеализацияТоваровУслуг,
ДокументСсылка,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
| Requirement | Why |
|------------|--------|
| НачатьТранзакцию() immediately before Попытка | If an error occurs between them, the transaction will not close |
| ЗафиксироватьТранзакцию() is the last statement before Исключение | An operation after commit will not roll back if it fails |
| ОтменитьТранзакцию() is the first statement in Исключение | Logging can also fail; if rollback has not happened yet, it becomes a cascading problem |
| ЗаписьЖурналаРегистрации() is AFTER ОтменитьТранзакцию() | An entry in the registration log inside a rolled-back transaction will be lost |
// ПЛОХО: код между НачатьТранзакцию и Попытка
НачатьТранзакцию();
ПодготовитьДанные(); // Если здесь ошибка — транзакция зависнет!
Попытка
// ...
КонецПопытки;
// ПЛОХО: ЗаписьЖурнала ДО ОтменитьТранзакцию
Исключение
ЗаписьЖурналаРегистрации(...); // Может быть потеряна при откате!
ОтменитьТранзакцию();
КонецПопытки;
// ПЛОХО: код после ЗафиксироватьТранзакцию, но до конца Попытка
ЗафиксироватьТранзакцию();
ОтправитьОповещение(); // Ошибка здесь — транзакция уже зафиксирована, но Исключение выполнится!
Исключение
ОтменитьТранзакцию(); // Ошибка! Транзакция уже зафиксирована!
КонецПопытки;
In 1C, a nested НачатьТранзакцию() does not create a new transaction; it increments a counter. ОтменитьТранзакцию() marks the transaction as "rolled back", and any subsequent ЗафиксироватьТранзакцию() (even at the outer level) will raise an exception.
Процедура ЗаписатьДанные(ДанныеДляЗаписи)
НачатьТранзакцию();
Попытка
ДокументОбъект.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Запись данных'"),
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение; // ОБЯЗАТЕЛЬНО пробрасываем — внешний код должен знать
КонецПопытки;
КонецПроцедуры
ТранзакцияАктивна() as a substitute for the correct pattern// ПЛОХО: ТранзакцияАктивна() маскирует ошибку в структуре кода
Попытка
НачатьТранзакцию();
// ...
ЗафиксироватьТранзакцию();
Исключение
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
КонецПопытки;
// ПРАВИЛЬНО: корректная структура делает проверку ненужной
НачатьТранзакцию();
Попытка
// ...
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
While a transaction is open, modified data is locked in the DBMS. A long transaction = cascading locks = users cannot work.
// Подготовка данных — ВНЕ транзакции
МассивДанных = ПодготовитьДанные();
ПроверитьКорректность(МассивДанных);
// Транзакция — только быстрые операции записи
НачатьТранзакцию();
Попытка
Для Каждого ДанныеСтроки Из МассивДанных Цикл
ЗаписатьСтроку(ДанныеСтроки);
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации(...);
ВызватьИсключение;
КонецПопытки;
External HTTP calls inside a transaction are a disaster: HTTP timeout = 30 sec = 30 sec lock.
БлокировкаДанныхWithout granular locking before read-modify, a race condition occurs: two sessions read the same value, both modify it - one update is lost.
ITS standard: "Managed locks".
НачатьТранзакцию();
Попытка
// 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 → Ошибка (корректная!)
ЗаблокироватьДанныеДляРедактирования - pessimistic object lockingPrevents lost update: the second user will get the error "Object locked by user X".
Процедура ИзменитьСтатусДокумента(ДокументСсылка, НовыйСтатус)
НачатьТранзакцию();
Попытка
ЗаблокироватьДанныеДляРедактирования(ДокументСсылка);
ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
ДокументОбъект.Статус = НовыйСтатус;
ДокументОбъект.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Изменение статуса документа'"),
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
| Lock type | Mechanism | When to use |
|----------------|----------|-------------------|
| БлокировкаДанных | DBMS (managed), row level | Balance control, atomic operations |
| ЗаблокироватьДанныеДляРедактирования | 1C server (pessimistic), whole object | Preventing lost update |
ЗаписьЖурналаРегистрации - proper loggingЗаписьЖурналаРегистрации(
ИмяСобытия, // Строка — иерархическое имя (через точку)
УровеньСобытия, // УровеньЖурналаРегистрации — Ошибка/Предупреждение/Информация/Примечание
МетаданныеОбъекта, // Объект метаданных — для фильтрации по типу
Данные, // Ссылка на объект — для навигации из ЖР
Комментарий); // Строка — подробное описание (до 1024 символов)
| Level | When |
|---------|-------|
| Ошибка | The operation did not complete, data is lost or invalid |
| Предупреждение | The operation completed, but with limitations |
| Информация | Significant events for audit |
| Примечание | Diagnostic information |
ИмяСобытия = НСтр("ru = 'ОбменДанными.ОтправкаДанных.Ошибка'");
Комментарий = СтрШаблон(
НСтр("ru = 'Ошибка при отправке данных в узел ""%1"".
|Количество объектов: %2.
|Текст ошибки:
|%3'"),
Строка(УзелОбмена),
КоличествоОбъектов,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ЗаписьЖурналаРегистрации(
ИмяСобытия,
УровеньЖурналаРегистрации.Ошибка,
Метаданные.ПланыОбмена.ОбменСКонтрагентами,
УзелОбмена,
Комментарий);
For the user - what happened and what to do. In the registration log - technical information.
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Проведение документа'"),
УровеньЖурналаРегистрации.Ошибка,
ДокументОбъект.Метаданные(),
ДокументОбъект.Ссылка,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ТекстДляПользователя = СтрШаблон(
НСтр("ru = 'Не удалось провести документ ""%1"".
|Попробуйте повторить операцию. Если ошибка повторяется, обратитесь к администратору.
|
|Техническая информация: %2'"),
ДокументОбъект,
КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение ТекстДляПользователя;
КонецПопытки;
ВызватьИсключение| Method | When | Why |
|--------|-------|--------|
| ВызватьИсключение; | In intermediate code (object module, common module) | Preserves the original stack |
| ВызватьИсключение "Text"; | At the user boundary (form) | Replaces the technical stack with a clear message |
// Промежуточный слой — пробрасываем оригинал
Процедура ОбработатьДанные(Данные)
Попытка
ЗаписатьДанные(Данные);
Исключение
ЗаписьЖурналаРегистрации(...);
ВызватьИсключение; // Оригинальный стек сохранён
КонецПопытки;
КонецПроцедуры
// Граница с пользователем
&НаСервере
Процедура ОбработатьНаСервере()
Попытка
ОбработатьДанные(ДанныеФормы);
Исключение
ВызватьИсключение СтрШаблон(
НСтр("ru = 'Ошибка обработки данных: %1'"),
КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецПроцедуры
An error in one document must not stop the processing of the others. Each transaction is item-by-item.
Процедура ПровестиДокументыПакетно(МассивДокументов)
МассивОшибок = Новый Массив;
Для Каждого ДокументСсылка Из МассивДокументов Цикл
НачатьТранзакцию();
Попытка
ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ИнфоОшибки = ИнформацияОбОшибке();
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Пакетное проведение'"),
УровеньЖурналаРегистрации.Ошибка,
ДокументСсылка.Метаданные(),
ДокументСсылка,
ПодробноеПредставлениеОшибки(ИнфоОшибки));
МассивОшибок.Добавить(Новый Структура("Документ, Ошибка",
ДокументСсылка,
КраткоеПредставлениеОшибки(ИнфоОшибки)));
КонецПопытки;
КонецЦикла;
Если МассивОшибок.Количество() > 0 Тогда
Для Каждого ОписаниеОшибки Из МассивОшибок Цикл
ОбщегоНазначения.СообщитьПользователю(
СтрШаблон(НСтр("ru = 'Документ %1: %2'"),
ОписаниеОшибки.Документ, ОписаниеОшибки.Ошибка));
КонецЦикла;
КонецЕсли;
КонецПроцедуры
When two sessions lock data in different orders - deadlock. The DBMS rolls back one of the transactions.
// Всегда блокируйте ресурсы в фиксированном порядке (по ссылке)
МассивСсылок = ОбщегоНазначенияКлиентСервер.СвернутьМассив(МассивДокументов);
МассивСсылок.СортироватьПоЗначению();
Для Каждого Ссылка Из МассивСсылок Цикл
ЗаблокироватьДанныеДляРедактирования(Ссылка);
КонецЦикла;
НачатьТранзакцию();
Попытка
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Справочник.Номенклатура");
ЭлементБлокировки.УстановитьЗначение("Ссылка", НоменклатураСсылка);
Попытка
Блокировка.Заблокировать();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение СтрШаблон(
НСтр("ru = 'Не удалось заблокировать ""%1"". Данные редактируются другим пользователем.'"),
НоменклатураСсылка);
КонецПопытки;
НоменклатураОбъект = НоменклатураСсылка.ПолучитьОбъект();
НоменклатураОбъект.Записать();
ЗафиксироватьТранзакцию();
Исключение
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
ЗаписьЖурналаРегистрации(...);
ВызватьИсключение;
КонецПопытки;
External system calls are unreliable. Always wrap them in Try/Except.
Функция ОтправитьДанныеВоВнешнююСистему(Данные)
МаксимумПопыток = 3;
Для НомерПопытки = 1 По МаксимумПопыток Цикл
Попытка
HTTPСоединение = Новый HTTPСоединение("api.example.com",,,,, 30);
Запрос = Новый HTTPЗапрос("/api/data");
Запрос.УстановитьТелоИзСтроки(Данные);
Ответ = HTTPСоединение.ОтправитьДляОбработки(Запрос);
Если Ответ.КодСостояния = 200 Тогда
Возврат Истина;
Иначе
ВызватьИсключение СтрШаблон(
НСтр("ru = 'Сервер вернул код %1: %2'"),
Ответ.КодСостояния,
Ответ.ПолучитьТелоКакСтроку());
КонецЕсли;
Исключение
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Интеграция.ОтправкаДанных'"),
?(НомерПопытки < МаксимумПопыток,
УровеньЖурналаРегистрации.Предупреждение,
УровеньЖурналаРегистрации.Ошибка),,,
СтрШаблон(НСтр("ru = 'Попытка %1 из %2. Ошибка: %3'"),
НомерПопытки, МаксимумПопыток,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке())));
Если НомерПопытки = МаксимумПопыток Тогда
ВызватьИсключение;
КонецЕсли;
КонецПопытки;
КонецЦикла;
Возврат Ложь;
КонецФункции
testing
MUST use BEFORE making a judgment about the cause of a conflict, a test failure, or an artifact dispute. Defines the end-to-end verification method L1→L6 and the classification of the first broken link.
development
MUST use AFTER a work cycle with ≥2 iterations (wrote → error → fixed → success). Provides the retrospective procedure and the format for recording practice/anti-patterns in references/learned-patterns.md or {project}/.context/learned-patterns.md.
tools
MUST use WHEN you are writing reusable knowledge into RLM (pattern / architectural decision / stable domain fact) OR reading it before a non-trivial task/solution in the domain. Provides the breakdown of native-push vs RLM-pull, tools for writing and reading RLM, H-MEM levels, and hygiene.
testing
MUST use WHEN the task is classified as simple (< 20 lines, 1 file, no new metadata objects, no architectural decisions). Provides a short cycle of 3 steps with a guard on the self path and mandatory verify.