一键导入
error-handling
For BSL exceptions, transactions, rollbacks, and locks
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
For BSL exceptions, transactions, rollbacks, and locks
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
When writing or reviewing BSL, apply 1C standards
При написании или ревью BSL применять стандарты 1С
Orchestrator: routing work and agent phases
Оркестратор: маршрутизация работы и фаз агентов
BSL LSP navigation: definitions, refs, call graph
Rules for using RLM tools for project search and navigation in 1C/BSL
| name | error-handling |
| description | For BSL exceptions, transactions, rollbacks, and locks |
| alwaysApply | false |
Key principle: In 1C there is no automatic transaction management. The developer manually controls the start, commit, and rollback. Every unclosed transaction is a potential catastrophe.
A swallowed exception is the most dangerous anti-pattern: data is not written, the user does not know about the error, there are no traces in the registration log, debugging is impossible.
ITS standard: "In the exception handler, error information must be recorded in the registration log".
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
ИнформацияОбОшибке = ИнформацияОбОшибке();
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Проведение документа'"),
УровеньЖурналаРегистрации.Ошибка,
ДокументОбъект.Метаданные(),
ДокументОбъект.Ссылка,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке));
ВызватьИсключение;
КонецПопытки;
| Function | When to use | What it contains |
|---|---|---|
КраткоеПредставлениеОшибки() | For display to the user | Clear text without technical details |
ПодробноеПредставлениеОшибки() | For the registration log | Full call stack, line numbers, nested errors |
// Lower level — logging + rethrow
Функция ЗаписатьДокумент(ДокументОбъект)
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Возврат Истина;
Исключение
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Проведение документа'"),
УровеньЖурналаРегистрации.Ошибка,
ДокументОбъект.Метаданные(),
ДокументОбъект.Ссылка,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
КонецФункции
// Upper level (form) — show to the user
&НаКлиенте
Процедура ЗаписатьДокумент(Команда)
Попытка
ЗаписатьДокументНаСервере();
Исключение
ПоказатьПредупреждение(,
НСтр("ru = 'Не удалось записать документ. Обратитесь к администратору.'"));
КонецПопытки;
КонецПроцедуры
An unclosed transaction blocks writes in the DBMS. Other sessions wait (timeout ~20 sec) and get an error.
ITS standard: “Transactions: usage rules” — НачатьТранзакцию() is ALWAYS immediately before Попытка.
НачатьТранзакцию();
Попытка
// 1. Data locking (if needed — see rule 5)
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Документ.РеализацияТоваровУслуг");
ЭлементБлокировки.УстановитьЗначение("Ссылка", ДокументСсылка);
Блокировка.Заблокировать();
// 2. Reading and modifying data
ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
ДокументОбъект.Статус = Перечисления.СтатусыДокументов.Согласован;
// 3. Writing
ДокументОбъект.Записать();
// === Commit — the LAST operation before Исключение ===
ЗафиксироватьТранзакцию();
Исключение
// === Rollback — the FIRST operation in the Исключение block ===
ОтменитьТранзакцию();
// Logging AFTER rollback (a write to the event log inside a rolled-back transaction will be lost!)
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Согласование документа'"),
УровеньЖурналаРегистрации.Ошибка,
Метаданные.Документы.РеализацияТоваровУслуг,
ДокументСсылка,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
| Requirement | Why |
|---|---|
НачатьТранзакцию() immediately before Попытка | If an error occurs between them, the transaction will not be closed |
ЗафиксироватьТранзакцию() is the last statement before Исключение | An operation after commit will not be rolled back if it fails |
ОтменитьТранзакцию() is the first statement in Исключение | Logging can also fail; if rollback has not been performed yet, that creates a cascading problem |
ЗаписьЖурналаРегистрации() is AFTER ОтменитьТранзакцию() | A write to the event log inside a rolled-back transaction will be lost |
// ПЛОХО: code between НачатьТранзакцию and Попытка
НачатьТранзакцию();
ПодготовитьДанные(); // If there is an error here, the transaction will hang!
Попытка
// ...
КонецПопытки;
// ПЛОХО: ЗаписьЖурнала BEFORE ОтменитьТранзакцию
Исключение
ЗаписьЖурналаРегистрации(...); // May be lost during rollback!
ОтменитьТранзакцию();
КонецПопытки;
// ПЛОХО: code after ЗафиксироватьТранзакцию, but before the end of Попытка
ЗафиксироватьТранзакцию();
ОтправитьОповещение(); // Error here - the transaction is already committed, but Исключение will run!
Исключение
ОтменитьТранзакцию(); // Error! The transaction is already committed!
КонецПопытки;
In 1С, a nested НачатьТранзакцию() does not create a new transaction, but increments the counter. ОтменитьТранзакцию() marks the transaction as "rolled back", and any subsequent ЗафиксироватьТранзакцию() (even at the outer level) will raise an exception.
Процедура ЗаписатьДанные(ДанныеДляЗаписи)
НачатьТранзакцию();
Попытка
ДокументОбъект.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Запись данных'"),
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение; // ОБЯЗАТЕЛЬНО пробрасываем — внешний код должен знать
КонецПопытки;
КонецПроцедуры
// ПЛОХО: ТранзакцияАктивна() masks an error in the code structure
Попытка
НачатьТранзакцию();
// ...
ЗафиксироватьТранзакцию();
Исключение
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
КонецПопытки;
// ПРАВИЛЬНО: correct structure makes the check unnecessary
НачатьТранзакцию();
Попытка
// ...
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
While a transaction is open, the modified data is locked in the DBMS. A long transaction = cascading locks = users cannot work.
// Data preparation — OUTSIDE the transaction
МассивДанных = ПодготовитьДанные();
ПроверитьКорректность(МассивДанных);
// Transaction — only fast write operations
НачатьТранзакцию();
Попытка
Для Каждого ДанныеСтроки Из МассивДанных Цикл
ЗаписатьСтроку(ДанныеСтроки);
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации(...);
ВызватьИсключение;
КонецПопытки;
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. FIRST we lock
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ТоварыНаСкладах");
ЭлементБлокировки.УстановитьЗначение("Номенклатура", НоменклатураСсылка);
ЭлементБлокировки.УстановитьЗначение("Склад", СкладСсылка);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
// 2. Read - guaranteed current data
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.КоличествоОстаток КАК Остаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(,
| Номенклатура = &Номенклатура И Склад = &Склад) КАК Остатки";
Запрос.УстановитьПараметр("Номенклатура", НоменклатураСсылка);
Запрос.УстановитьПараметр("Склад", СкладСсылка);
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
ВызватьИсключение НСтр("ru = 'Нет остатков на складе.'");
КонецЕсли;
Выборка = Результат.Выбрать();
Выборка.Следующий();
// 3. Validate
Если Выборка.Остаток < ТребуемоеКоличество Тогда
ВызватьИсключение СтрШаблон(
НСтр("ru = 'Недостаточно остатков. На складе: %1, требуется: %2.'"),
Выборка.Остаток, ТребуемоеКоличество);
КонецЕсли;
// 4. Write
// ... запись движений ...
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации(...);
ВызватьИсключение;
КонецПопытки;
Без блокировки (race condition):
Сеанс A: Читает остаток = 10 | Сеанс B: Читает остаток = 10
Сеанс A: 10 >= 8? Да, списываем 8 | Сеанс B: 10 >= 7? Да, списываем 7
Итого: списано 15 единиц при остатке 10 → отрицательный остаток!
С блокировкой:
Сеанс A: Блокирует → Читает 10 → Списывает 8 → Фиксирует → Разблокирует
Сеанс B: Ждёт блокировку → Читает 2 → 2 < 7 → Ошибка (корректная!)
Prevents lost update: the second user will receive the error "The object is locked by user X".
Процедура ИзменитьСтатусДокумента(ДокументСсылка, НовыйСтатус)
НачатьТранзакцию();
Попытка
ЗаблокироватьДанныеДляРедактирования(ДокументСсылка);
ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
ДокументОбъект.Статус = НовыйСтатус;
ДокументОбъект.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Изменение статуса документа'"),
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
| Lock type | Mechanism | When to use |
|---|---|---|
БлокировкаДанных | DBMS (managed), record level | Balance control, atomic operations |
ЗаблокироватьДанныеДляРедактирования | 1C server (pessimistic), whole object | Preventing lost update |
ЗаписьЖурналаРегистрации(
ИмяСобытия, // Строка — иерархическое имя (через точку)
УровеньСобытия, // УровеньЖурналаРегистрации — Ошибка/Предупреждение/Информация/Примечание
МетаданныеОбъекта, // Объект метаданных — для фильтрации по типу
Данные, // Ссылка на объект — для навигации из ЖР
Комментарий); // Строка — подробное описание (до 1024 символов)
| Level | When |
|---|---|
Error | Operation was not completed, data was lost or is invalid |
Warning | Operation completed, but with limitations |
Information | Significant events for auditing |
Note | Diagnostic information |
ИмяСобытия = НСтр("ru = 'ОбменДанными.ОтправкаДанных.Ошибка'");
Комментарий = СтрШаблон(
НСтр("ru = 'Ошибка при отправке данных в узел ""%1"".
|Количество объектов: %2.
|Текст ошибки:
|%3'"),
Строка(УзелОбмена),
КоличествоОбъектов,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ЗаписьЖурналаРегистрации(
ИмяСобытия,
УровеньЖурналаРегистрации.Ошибка,
Метаданные.ПланыОбмена.ОбменСКонтрагентами,
УзелОбмена,
Комментарий);
For the user - what happened and what to do. In the log - technical information.
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Проведение документа'"),
УровеньЖурналаРегистрации.Ошибка,
ДокументОбъект.Метаданные(),
ДокументОбъект.Ссылка,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ТекстДляПользователя = СтрШаблон(
НСтр("ru = 'Не удалось провести документ ""%1"".
|Попробуйте повторить операцию. Если ошибка повторяется, обратитесь к администратору.
|
|Техническая информация: %2'"),
ДокументОбъект,
КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение ТекстДляПользователя;
КонецПопытки;
| Method | When | Why |
|---|---|---|
ВызватьИсключение; | In intermediate code (object module, common module) | Preserves the original stack |
ВызватьИсключение "Текст"; | At the user boundary (form) | Replaces the technical stack with a clear message |
// Промежуточный слой — пробрасываем оригинал
Процедура ОбработатьДанные(Данные)
Попытка
ЗаписатьДанные(Данные);
Исключение
ЗаписьЖурналаРегистрации(...);
ВызватьИсключение; // Оригинальный стек сохранён
КонецПопытки;
КонецПроцедуры
// Граница с пользователем
&НаСервере
Процедура ОбработатьНаСервере()
Попытка
ОбработатьДанные(ДанныеФормы);
Исключение
ВызватьИсключение СтрШаблон(
НСтр("ru = 'Ошибка обработки данных: %1'"),
КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецПроцедуры
An error in one document must not stop processing 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"". Данные редактируются другим пользователем.'"),
НоменклатураСсылка);
КонецПопытки;
НоменклатураОбъект = НоменклатураСсылка.ПолучитьОбъект();
НоменклатураОбъект.Записать();
ЗафиксироватьТранзакцию();
Исключение
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
ЗаписьЖурналаРегистрации(...);
ВызватьИсключение;
КонецПопытки;
Calls to external systems are unreliable. Always wrap them in Попытка/Исключение.
Функция ОтправитьДанныеВоВнешнююСистему(Данные)
МаксимумПопыток = 3;
Для НомерПопытки = 1 По МаксимумПопыток Цикл
Попытка
HTTPСоединение = Новый HTTPСоединение("api.example.com",,,,, 30);
Запрос = Новый HTTPЗапрос("/api/data");
Запрос.УстановитьТелоИзСтроки(Данные);
Ответ = HTTPСоединение.ОтправитьДляОбработки(Запрос);
Если Ответ.КодСостояния = 200 Тогда
Возврат Истина;
Иначе
ВызватьИсключение СтрШаблон(
НСтр("ru = 'Сервер вернул код %1: %2'"),
Ответ.КодСостояния,
Ответ.ПолучитьТелоКакСтроку());
КонецЕсли;
Исключение
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Интеграция.ОтправкаДанных'"),
?(НомерПопытки < МаксимумПопыток,
УровеньЖурналаРегистрации.Предупреждение,
УровеньЖурналаРегистрации.Ошибка),,,
СтрШаблон(НСтр("ru = 'Попытка %1 из %2. Ошибка: %3'"),
НомерПопытки, МаксимумПопыток,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке())));
Если НомерПопытки = МаксимумПопыток Тогда
ВызватьИсключение;
КонецЕсли;
КонецПопытки;
КонецЦикла;
Возврат Ложь;
КонецФункции