1C-CodeОсновы 1С → Ошибки и исключения

Ошибки и исключения

Попытка, Исключение, ВызватьИсключение. Когда ловить ошибку, когда дать ей упасть и почему пустой блок Исключение — худшее, что можно написать.

Глава 35 из 36 · чтение примерно 24 минут

Код падает. Файла нет, сервис не ответил, в строке вместо числа буквы. Вопрос не в том, как этого избежать, — избежать нельзя, — а в том, что делать, когда случилось.

Как это выглядит

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

Пустой Исключение — худшее, что можно написать

Попытка … Исключение КонецПопытки; без единой строки внутри означает: «что бы ни случилось, молчи и делай вид, что всё хорошо».

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

Если ошибку действительно можно пропустить — напишите почему комментарием. Пустой блок без объяснения читается как забытый.

Когда ловить, а когда не ловить

Ловят там, где знают, что делать. Не ответил сервис — записать в журнал и попробовать позже. Не разобралась строка — пропустить эту строку файла и продолжить с остальными.

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

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

Своё исключение

ВызватьИсключение "Не заполнен склад" — способ прервать работу со своим текстом.

Текст пишут для того, кто будет разбираться, а не для машины. «Ошибка» — плохой текст. «Не удалось загрузить прайс: в строке 42 вместо цены текст» — хороший: по нему сразу видно, что чинить.

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

На чём спотыкаются

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

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

Ловят ошибку и всё равно продолжают. Если после исключения нельзя работать дальше, надо выйти, а не идти по коду с пустыми данными.

Используют Попытку вместо проверки. Проверить, заполнено ли поле, дешевле и понятнее, чем поймать падение. Исключения — для непредсказуемого, а не для известного заранее.

Упражнение 1. Число или ноль

Функция ВЧисло(Текст) превращает строку в число. Если не получается — возвращает 0, а не падает. Используйте Попытка … Исключение … КонецПопытки.

Решается в тренажёре: код запускается и проверяется сразу.

Что происходит, когда код падает

Исключение не останавливает программу на месте. Оно выбрасывает выполнение вверх по цепочке вызовов, пока не найдёт ближайшую Попытку.

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

Куда попадёт ошибка

Процедура Загрузить()
	Попытка
		РазобратьФайл();      // <- падает где-то внутри
		Сообщить("Готово");   // <- НЕ выполнится
	Исключение
		Сообщить("Не загрузилось");
	КонецПопытки;
КонецПроцедуры
ЧастьЧто означает
РазобратьФайл()внутри может быть ещё десять вызовов — ошибка из любого прилетит сюда
Сообщить("Готово")строка после упавшего вызова не выполняется: выполнение уже ушло в Исключение
Исключениеближайший блок вверх по цепочке; если бы его не было, искали бы выше
ни одной Попыткиесли её нет нигде — падает вся операция, и это иногда правильно

Транзакции: место, где это важнее всего

Транзакция — «или всё, или ничего»: несколько записей в базу, которые должны применяться вместе. Проведение документа, групповая обработка, загрузка.

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

Каркас, который пишут всегда одинаково

НачатьТранзакцию();
Попытка
	ЗаписатьВсёЧтоНадо();
	ЗафиксироватьТранзакцию();
Исключение
	ОтменитьТранзакцию();
	ЗаписьЖурналаРегистрации("Загрузка",
		УровеньЖурналаРегистрации.Ошибка, , , ОписаниеОшибки());
	ВызватьИсключение;
КонецПопытки;
ЧастьЧто означает
НачатьТранзакцию()СНАРУЖИ Попытки: если не начали, отменять нечего
ЗафиксироватьТранзакцию()последняя строка удачного пути, внутри Попытки
ОтменитьТранзакцию()первая строка неудачного — до всего остального
ВызватьИсключениебез параметров — переброс той же ошибки дальше: записали в журнал, но решать не нам

«В данной транзакции уже происходили ошибки»

Самая узнаваемая ошибка новичка в 1С. Означает она вот что: внутри транзакции что-то упало, исключение поймали, ОтменитьТранзакцию не вызвали — и попытались работать с базой дальше.

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

Отсюда правило: поймали исключение внутри транзакции — транзакцию отменяем, а не пробуем починиться на месте.

Блока «в любом случае» в 1С нет

В других языках есть третья часть: try … catch … finally, где finally выполняется всегда. В 1С такого нет. Есть только Попытка и Исключение.

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

Поймать можно всё, разобрать — почти ничего

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

Из этого следует неприятное: узнать, что именно случилось, почти невозможно. Есть текст ошибки — и разбирать его поиском подстроки очень плохая идея: текст зависит от версии платформы и от языка интерфейса, и такая проверка однажды перестаёт работать молча.

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

ИнформацияОбОшибке

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

Из него делают два разных текста: КраткоеПредставлениеОшибки — человеку, ПодробноеПредставлениеОшибки — в журнал. Это и есть правильный ответ на вопрос «что показать пользователю».

Наш браузерный движок этот объект не изображает — в упражнениях используется ОписаниеОшибки(). Знать про ИнформацияОбОшибке всё равно нужно: в рабочем коде пишут именно так.

Упражнение 2. Разобрать, что получится

Напишите СколькоРазобралось(Строки): массив текстов из загружаемого файла. Посчитайте, сколько из них удалось превратить в число. Упасть на первом же плохом нельзя. Проверка передаёт «120», «абв», «45», «ноль» и ждёт 2.

Решается в тренажёре: код запускается и проверяется сразу.

Не падать на первой строке — и не молчать

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

Правильная схема — копить ошибки, а не падать и не молчать: пройти всё до конца, собрать список проблемных строк и показать его одним сообщением. Одно понятное сообщение про пятнадцать строк лучше, чем пятнадцать всплывающих окон или одно «не удалось».

Упражнение 3. Список плохих строк

Напишите ПлохиеСтроки(Строки): верните номера строк, которые не разобрались в число, через запятую с пробелом. Нумерация с единицы — человеку показывают номер строки файла, а не индекс массива. На тех же данных ответ — «2, 4».

Решается в тренажёре: код запускается и проверяется сразу.

Отказ вместо исключения

Есть места, где ВызватьИсключение — неправильный инструмент, хотя и работает. Это обработчики событий: ПередЗаписью, ОбработкаПроведения, ОбработкаЗаполнения.

У них есть параметр Отказ. Поставить Отказ = Истина — значит сказать платформе «эту операцию делать не надо». Платформа сама откатит транзакцию, сама вернёт форму в рабочее состояние и сама покажет накопленные сообщения.

Исключение в том же месте сделает то же самое грубее: человек увидит техническое окно вместо понятного сообщения у поля.

✓ Так — исключение

Процедура ОбработкаПроведения(Отказ, Режим)
	Если Не ЗначениеЗаполнено(Склад) Тогда
		ВызватьИсключение "Не заполнен склад";
	КонецЕсли;
КонецПроцедуры

Работает, но человек получает окно с технической ошибкой и не понимает, какое поле заполнять.

✕ Так — отказ с сообщением

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

Проведение не состоится, а человек увидит подсказку прямо у нужного поля. Проверок может сработать сразу несколько — он увидит все.

Почему сообщений может быть несколько

Исключение прерывает выполнение на первой же ошибке. Отказ — нет: проверки идут дальше, и человек получает сразу весь список незаполненного.

Разница видна на документе с двадцатью реквизитами: в первом случае он будет заполнять и пробовать двадцать раз, во втором — один.

Упражнение 4. Своя ошибка с понятным текстом

Напишите Проверить(Склад): если склад — пустая строка, вызовите исключение с текстом Не заполнен склад, поймайте его тут же и верните текст ошибки. Если склад заполнен — верните Ок. Проверка передаёт пустую строку.

Решается в тренажёре: код запускается и проверяется сразу.

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

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

Бесполезны записи вида «Ошибка» и «Что-то пошло не так»: они говорят только то, что и так известно — что-то сломалось.

Короткий свод

Ловят там, где знают, что делать; не ловят там, где продолжать нельзя. Пустой блок Исключение — способ спрятать поломку и встретить её позже в другом месте. Оборачивают минимальный кусок, а не весь код. Человеку — понятный текст, в журнал — ОписаниеОшибки(). Там, где можно проверить заранее, проверяют, а не ловят.

Как закрепить

Читать про код и писать код — разные умения. В тренажёре к этой главе идут упражнения: вы пишете решение, оно запускается на настоящем интерпретаторе 1С и проверяется сразу, без установки платформы.

Открыть эту главу в тренажёре

Таблица значений
Отладка: как искать причину