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С и проверяется сразу, без установки платформы.