Искусственный сбой в коде 1С 8.3: проверяем, как программа переживает ошибки

· 9 мин

Содержание

Вызвать ошибку в 1С проще всего оператором ВызватьИсключение "Текст ошибки"; — выполнение кода прерывается, и платформа ведёт себя так же, как при настоящем сбое. Для отладки и тестирования этого почти всегда достаточно: так проверяют, срабатывает ли Попытка…Исключение, откатывается ли транзакция и что видит пользователь.

Сложнее, когда нужен не «любой» сбой, а конкретный: конфликт блокировок, таймаут внешнего сервиса, падение на сервере, а не на клиенте. Для каждого такого случая есть свой приём, и весь код из примеров можно вставить во внешнюю обработку и запустить в тестовой базе.

Как платформа 1С обрабатывает исключения

Любая ошибка времени выполнения в 1С — это исключение. Деление на ноль, обращение к несуществующему полю, отказ записи объекта, ошибка запроса — всё это прерывает текущую процедуру и поднимается вверх по стеку вызовов.

Дальше два варианта:

  • ✅ где-то выше есть блок Попытка…Исключение — управление переходит в ветку Исключение, код продолжает работать;
  • ❌ такого блока нет — выполнение обрывается, пользователь видит окно с текстом ошибки, а платформа пишет событие в журнал регистрации.

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

Отличайте исключение от отказа. В обработчиках ПередЗаписью, ОбработкаПроверкиЗаполнения, ОбработкаПроведения есть параметр Отказ. Установка Отказ = Истина тоже останавливает запись, но это штатный сценарий: объект не записан, исключения в вызывающем коде при интерактивной записи нет. Если же объект записывается программно через Записать(), отказ превратится в исключение — его и ловят в тестах.

Оператор ВызватьИсключение: синтаксис и примеры

Базовая форма — оператор с текстом:

Процедура ПроверитьСумму(Сумма) Экспорт
	Если Сумма <= 0 Тогда
		ВызватьИсключение СтрШаблон(НСтр("ru = 'Сумма должна быть больше нуля, получено: %1'"), Сумма);
	КонецЕсли;
КонецПроцедуры

Текст лучше оборачивать в НСтр(): так сообщение будет переводиться в многоязычных конфигурациях. Для подстановки значений используйте СтрШаблон, а не склейку через + — числа без Формат() склеиваются с разделителями разрядов.

Повторный вызов внутри ветки «Исключение»

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

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

Это главный приём для отладки: вы фиксируете ошибку и при этом не «глотаете» её. Пустая ветка Исключение без записи в журнал — частая причина, по которой баг ищут неделями.

Расширенная форма в новых версиях платформы

Начиная с версии платформы 8.3.21 у оператора появилась форма с параметрами: кроме текста можно передать категорию ошибки, код, дополнительную информацию и причину. Код ошибки удобно проверять в автотестах вместо сравнения текстов. Точную сигнатуру смотрите в синтакс-помощнике своей версии: Справка → Синтакс-помощник, поиск по слову «ВызватьИсключение».

Быстрые способы сломать код без оператора

Иногда нужен «настоящий» платформенный сбой, а не свой текст:

Что написатьКакая ошибка возникнет
А = 1 / 0;Деление на 0
Значение = Неопределено.Поле;обращение к полю у значения неопределённого типа
Новый COMОбъект("Нет.Такого.Объекта");ошибка создания COM-объекта (только Windows)
Новый ЧтениеТекста("C:\нет\файла.txt");ошибка открытия файла
Массив = Новый Массив; Х = Массив[5];индекс находится за границами диапазона

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

Перехват ошибок конструкцией Попытка…Исключение

Конструкция ловит любое исключение внутри блока Попытка, включая ошибки во вложенных вызовах:

Попытка
	Результат = РассчитатьНаценку(Цена, Себестоимость);
Исключение
	Результат = 0;
	Сообщить(КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;

Три правила, которые избавляют от сюрпризов:

  1. 🎯 Оборачивайте минимальный участок кода. Блок на 200 строк скрывает, где именно упало.
  2. 🎯 Не ловите исключения «на всякий случай». Если вы не знаете, что делать с ошибкой, пусть она дойдёт до пользователя.
  3. 🎯 Внутри транзакции после пойманного исключения транзакцию нужно отменить, а не продолжать.

Транзакции: самая частая ловушка

Если ошибку поймать внутри транзакции и пойти дальше, следующая запись упадёт с текстом В данной транзакции уже происходили ошибки!. Транзакция после исключения помечена как сбойная, и зафиксировать её уже нельзя.

Правильный шаблон:

НачатьТранзакцию();
Попытка
	ЗаписатьДанныеОбмена(Пакет);
	ЗафиксироватьТранзакцию();
Исключение
	ОтменитьТранзакцию();
	ВызватьИсключение;
КонецПопытки;

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

Хороший тест для такого кода — вставить ВызватьИсключение "Тест отката"; сразу перед ЗафиксироватьТранзакцию() и проверить, что в базе не осталось частично записанных данных.

Разбор ошибки: ИнформацияОбОшибке и стек вызовов

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

СвойствоЧто содержит
Описаниетекст ошибки
ИмяМодулямодуль, где возникла ошибка
НомерСтрокиномер строки в модуле
ИсходнаяСтрокатекст строки кода
Причинавложенная ошибка (тоже ИнформацияОбОшибке) или Неопределено

Ошибки бывают вложенными: например, запись документа упала, потому что упала подписка на событие. Цепочку причин разбирают циклом:

Инфо = ИнформацияОбОшибке();
Пока Инфо <> Неопределено Цикл
	Сообщить(СтрШаблон("%1 (модуль %2, строка %3)",
		Инфо.Описание, Инфо.ИмяМодуля, Формат(Инфо.НомерСтроки, "ЧГ=0")));
	Инфо = Инфо.Причина;
КонецЦикла;

Краткое и подробное представление

Для готового текста есть две функции:

  • 📝 КраткоеПредставлениеОшибки(Инфо) — только описание, подходит для сообщения пользователю;
  • 📝 ПодробноеПредставлениеОшибки(Инфо) — описание вместе с модулем, строкой и стеком, подходит для журнала.

В новых версиях платформы (8.3.17 и выше) рекомендован объект ОбработкаОшибок с аналогичными методами: ОбработкаОшибок.КраткоеПредставлениеОшибки() и ОбработкаОшибок.ПодробноеПредставлениеОшибки(). Через него же работают настройки отображения ошибок пользователю.

Стек вызовов в отладчике

Чтобы увидеть, какая цепочка процедур привела к ошибке, запустите конфигурацию в режиме отладки (F5 в Конфигураторе) и включите остановку по ошибке: Отладка → Остановка по ошибке. В этом окне отметьте флажок «Останавливаться по ошибке». Там же можно задать фильтр по тексту, чтобы отладчик реагировал только на нужную ошибку.

Когда отладчик остановится, откройте Отладка → Стек вызовов (Ctrl+Alt+C). Двойной щелчок по строке стека переводит к нужной процедуре, а в табло можно посмотреть значения переменных на каждом уровне.

Остановка по ошибке срабатывает и на исключениях, которые потом будут пойманы в Попытка. В типовых конфигурациях таких мест много, поэтому без фильтра отладчик будет останавливаться очень часто.

Для серверного кода в клиент-серверной базе отладка на сервере должна быть разрешена: служба сервера запускается с ключом -debug. Включение ключа требует перезапуска службы — все сеансы пользователей оборвутся, поэтому делайте это на тестовом сервере или вне рабочего времени.

Имитация сбоев базы данных и внешних сервисов

Свой текст в ВызватьИсключение проверяет логику обработки, но не поведение платформы. Для реальных сценариев сбой нужно воспроизвести честно.

Какой сбой нужно проверить?

Конфликт управляемых блокировок

Создайте внешнюю обработку с серверной процедурой, которая берёт исключительную блокировку и держит её заданное время:

&НаСервереБезКонтекста
Процедура ДержатьБлокировку(Секунд)
	НачатьТранзакцию();
	Попытка
		Блокировка = Новый БлокировкаДанных;
		Элемент = Блокировка.Добавить("РегистрНакопления.ТоварыНаСкладах"); // свой регистр
		Элемент.Режим = РежимБлокировкиДанных.Исключительный;
		Блокировка.Заблокировать();
		Конец = ТекущаяУниверсальнаяДатаВМиллисекундах() + Секунд * 1000;
		Пока ТекущаяУниверсальнаяДатаВМиллисекундах() < Конец Цикл
			// ждём, удерживая блокировку
		КонецЦикла;
		ОтменитьТранзакцию();
	Исключение
		ОтменитьТранзакцию();
		ВызватьИсключение;
	КонецПопытки;
КонецПроцедуры

Запустите её в первом сеансе с параметром 60, а во втором проведите документ, который пишет в тот же регистр. Второй сеанс подождёт и получит ошибку Конфликт блокировок при выполнении транзакции: Превышено максимальное время ожидания предоставления блокировки. Так проверяют, что код обмена или регламентного задания корректно повторяет попытку.

Цикл ожидания загружает процессор, а блокировка остановит работу всех, кто пишет в этот регистр. Только тестовая база.

Недоступный HTTP-сервис

Для таймаута подключитесь к адресу из диапазона для документации 192.0.2.1 — там гарантированно никто не ответит:

Соединение = Новый HTTPСоединение("192.0.2.1", 80,,,, 5); // таймаут 5 секунд
Запрос = Новый HTTPЗапрос("/api/orders");
Попытка
	Ответ = Соединение.Получить(Запрос);
Исключение
	ЗаписьЖурналаРегистрации("Интеграция.Заказы", УровеньЖурналаРегистрации.Ошибка,,,
		ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;

Здесь есть ловушка, которую многие пропускают: ответ с кодом 500 или 404 не вызывает исключения. Получить() отработает успешно, а ошибку нужно проверять по Ответ.КодСостояния. Поэтому тестируйте оба случая отдельно.

Чтобы получить управляемый ответ с ошибкой, опубликуйте в тестовой базе HTTP-сервис и в его обработчике верните Новый HTTPСервисОтвет(500). Так можно проверить и код 500, и неожиданное тело ответа, и долгую обработку.

Ошибки записи и проверки данных

Для сбоя при записи объекта есть простой вариант без правки типового кода: расширение конфигурации. Добавьте в расширение обработчик ПередЗаписью нужного документа, установите в нём Отказ = Истина и программно запишите документ. Получите честное исключение, а основную конфигурацию трогать не придётся. После теста расширение отключите.

Журнал регистрации и сообщения пользователю

Искусственная ошибка полезна, только если её последствия видно. Проверяйте две вещи: что записалось в журнал и что увидел пользователь.

Запись в журнал регистрации

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

Имя события задавайте с точкой и префиксом подсистемы — по нему потом фильтруют журнал. В БП 3.0 журнал открывается через Администрирование → Поддержка и обслуживание → Журнал регистрации, в Конфигураторе — Администрирование → Журнал регистрации.

Уровни выбирайте честно:

  • 🔴 Ошибка — операция не выполнена;
  • 🟡 Предупреждение — выполнена, но с отклонениями;
  • 🔵 Информация — штатное событие, например начало обмена.

Сообщение пользователю

Пользователю показывайте краткое описание, без стека и номеров строк:

Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
Сообщение.Поле = "Объект.Контрагент"; // подсветит поле формы
Сообщение.Сообщить();

Свойство Поле привязывает сообщение к реквизиту формы: при щелчке по сообщению курсор встанет в нужное поле. В конфигурациях на БСП то же делает ОбщегоНазначения.СообщитьПользователю().

На клиенте для показа полного окна ошибки есть ПоказатьИнформациюОбОшибке(ИнформацияОбОшибке()). Пользователь увидит то же окно, что и при необработанной ошибке, и сможет скопировать подробности для поддержки.

Автоматизация: ошибки в тестах

Ручная вставка ВызватьИсключение подходит для разовой проверки. Если сценарий нужно гонять после каждого изменения, оформите его тестом.

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

Процедура Тест_ОтрицательнаяСуммаВызываетОшибку() Экспорт
	ОшибкаБыла = Ложь;
	Попытка
		ПроверитьСумму(-100);
	Исключение
		ОшибкаБыла = СтрНайти(КраткоеПредставлениеОшибки(ИнформацияОбОшибке()), "больше нуля") > 0;
	КонецПопытки;
	Если Не ОшибкаБыла Тогда
		ВызватьИсключение "Ожидалась ошибка для отрицательной суммы";
	КонецЕсли;
КонецПроцедуры

Для системного подхода используют открытые фреймворки модульного и сценарного тестирования (например, Vanessa-ADD) и встроенное в платформу сценарное тестирование через клиент тестирования. Там же удобно хранить тесты на откат транзакций и таймауты.

Частые вопросы

Чем ВызватьИсключение отличается от Отказ = Истина?
Отказ штатно отменяет запись в обработчике события. ВызватьИсключение прерывает выполнение в любом месте кода и поднимает ошибку вверх по стеку.
Можно ли вызвать ошибку в запросе?
Да: синтаксическая ошибка в тексте запроса или обращение к несуществующему полю вызовет исключение при Выполнить(). Для проверки логики удобнее свой текст через ВызватьИсключение.
Почему ошибка пропала и ничего не случилось?
Скорее всего, выше по стеку стоит Попытка…Исключение с пустой веткой или без повторного вызова. Включите остановку по ошибке в отладчике и найдите место перехвата по стеку вызовов.
Попадает ли пойманное исключение в журнал регистрации?
Нет. Платформа пишет в журнал только необработанные ошибки. Пойманную ошибку записывайте сами через ЗаписьЖурналаРегистрации.
Работает ли ВызватьИсключение в фоновом задании?
Да. Фоновое задание завершится аварийно, а текст ошибки будет в свойстве ИнформацияОбОшибке объекта фонового задания и в журнале регистрации.