Как узнать ошибку проведения документа
Автор Sinsinmin, 06 мая 2011, 09:32
0 Пользователей и 1 гость просматривают эту тему.
программно пишу следующее
ЦитироватьПопытка
ТекстОшибки = «»;
ДокКадрПерем.Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Неоперативный);
Исключение
ТекстОшибки = ОписаниеОшибки();
ЗаписьОшибки(ТекстОшибки);
ДокКадрПерем.Записать(РежимЗаписиДокумента.Запись);
КонецПопытки;
Но получается что многие документы проводятся с противоречиями. Таким образом выполнение не заходит в исключение проведения. Каким образом можно узнать ошибку проведения документа?
Ошибки проведения обрабатываются в ОбработкеПроведения() документа и выводятся обычным Сообщить()…
- 1 пользователь сказал спасибо!
Получил помощь — скажи СПАСИБО.
Разобрался сам — расскажи другим.
Это код внешней обработки.
И мне нужно передать эти ошибки в текстовый документ, для логирования ошибок внешней обработки.
Ну а чем существубщий код не подходит?
Если ты имеешь ввиду мой код, то он выдаёт ошибку только при исключении. Тобишь когда проведение документа невозможно. А хотелось бы узнать ошибки при проведении документа.
Хм, а какие ошибки возможны при нормальном проведении документа? Документ либо провелся (ошибок нет), либо не провелся (ошибки есть). Или я не так понял?
Аааа, кажется вкурил. Ты имеешь ввиду ошибки, которые не отменяют при этом проведения документа?
В ОбработкеПроведения() там где сообщаете об ошибке через Сообщить() надо добавить код, который будет помещать ошибки еще куда нибудь, например в РегистрСведений…
Получил помощь — скажи СПАСИБО.
Разобрался сам — расскажи другим.
нельзя трогать код программы. Всё с помощью внешней обработки. Также нельзя делать регистры. Текстовый документ самое то.
|
|||
VigerV
17.09.12 — 18:16 |
Как программно получить ошибку при проведении документа, т.е. допустим программное проведение ряда документов и программное получение(подробное) , не строчку кода, а например «Не списано по партиям…». Все это выполняется в регламентном задании. ОписаниеОшибки() дает только номер строчки кода. |
||
Wobland
1 — 17.09.12 — 18:18 |
лог пиши |
||
Wobland
2 — 17.09.12 — 18:19 |
+(1) я про штатный вывод Сообщить в файл |
||
vmv
3 — 17.09.12 — 18:19 |
в критичные методы добавь параметр СтрокаСообщенияОбОшибке и накапливай эти НЕ пустые строки в контейнер — по другому хренушки |
||
undertaker
4 — 17.09.12 — 18:20 |
в журнал регистрации можно |
||
VigerV
5 — 17.09.12 — 18:25 |
Это и пишется в лог регламентного задания который после отсылается на мыло. Но ОписаниеОшибки() выдает не ту инфу. |
||
VigerV
6 — 17.09.12 — 18:25 |
(3) не совсем понял что ты имел в виду под «в критичные методы добавь параметр СтрокаСообщенияОбОшибке» |
||
shuhard
7 — 17.09.12 — 18:27 |
(5)я использую запуск обработки с ключиком /OutC:Robotdel_log.log -NoTruncate и файлик к письму присоединяю |
||
undertaker
8 — 17.09.12 — 18:27 |
(5) а ОписаниеОшибки() и не выдаст тебе что «Не списано по партиям» |
||
Wobland
9 — 17.09.12 — 18:28 |
+(2) *про штатное перенаправление вывода окно сообщений -> файл |
||
Wobland
10 — 17.09.12 — 18:28 |
(7) вот оно |
||
VigerV
11 — 17.09.12 — 18:51 |
В файл выводить нельзя, не из-за 1с а изза особенностей сервера, можно ли выводить куда-то внутри самой 1С? |
||
unregistered
12 — 17.09.12 — 18:53 |
(11) Что за особенность такая?… |
||
undertaker
13 — 17.09.12 — 18:53 |
(11) см. (4) |
||
unregistered
14 — 17.09.12 — 19:00 |
(13) (4) Как? |
||
undertaker
15 — 17.09.12 — 22:37 |
(14) ЗаписьЖурналаРегистрации("Имя события", УровеньЖурналаРегистрации.Ошибка, , , "Текст ошибки") потом по событию можно фильтры накладывать или выгружать ЖР и програмно обрабатывать |
||
rs_trade
16 — 17.09.12 — 23:00 |
(12) он просто не знает как. вот и вся особенность. |
||
unregistered
17 — 18.09.12 — 08:51 |
(15) Это и ёжику понятно. Я думал ты знаешь метод не связанный с изменениями конфигурации. Ты предлагаешь автору по быстренькому добавить во всю конфу в те места, где используется Сообщаить() при проведении воткнуть функцию записи в журнал. В лучшем случае всё ограничиться процедурой общего модуля ОбщегоНазначения.ОшибкаПриПроведении (в разных конфах может называться по разному). А если этого окажется мало? |
||
shuhard
18 — 18.09.12 — 08:52 |
(17) в типовой УПП это сделано на уровне константы =) |
||
unregistered
19 — 18.09.12 — 09:08 |
(18) Прикольно. Я с УПП не работаю, а потому не знал. |
||
shuhard 20 — 18.09.12 — 09:10 |
(19) кругозор расширил |
Ошибка? Это не ошибка, это системная функция. |
ВНИМАНИЕ! Если вы потеряли окно ввода сообщения, нажмите Ctrl-F5 или Ctrl-R или кнопку «Обновить» в браузере.
Тема не обновлялась длительное время, и была помечена как архивная. Добавление сообщений невозможно.
Но вы можете создать новую ветку и вам обязательно ответят!
Каждый час на Волшебном форуме бывает более 2000 человек.
Как в ЗУП 3.1 можно посмотреть описание ошибки? Например, когда не проводится документ Начисление зарплаты и взносов.
В ЗУП 3.1 есть 3 способа посмотреть описание ошибки:
- Непосредственно в самом документе, при проведении / записи которого выдается ошибка.
- В Журнале регистрации (Администрирование — Обслуживание — Журнал регистрации).
- Сформировать отчет об ошибке для отправки в 1С и посмотреть описание во вложенном файле отчета – способ доступен для ошибок с типом Возникла непредвиденная ситуация.
При появлении ошибки в документе, можно анализировать текст самого сообщения о проблеме.
Также текст ошибки можно увидеть в Журнале регистрации. Для этого в поле Критичность выберем значение Ошибка.
В настройках отбора можно убрать фильтр по периоду.
Подробный текст ошибки можно открыть двойным щелчком левой кнопки мыши по ее описанию.
При возникновении ошибки, связанной с непредвиденной ситуацией, для анализа проблемы можно воспользоваться ссылкой Сформировать отчет об ошибке.
В открывшейся форме по ссылке Подробный текст ошибки можно будет увидеть описание проблемы.
Подписывайтесь на наши YouTube и Telegram чтобы не пропустить
важные изменения 1С и законодательства
Помогла статья?
Получите еще секретный бонус и полный доступ к справочной системе БухЭксперт8 на 14 дней бесплатно
Оцените публикацию
(2 оценок, среднее: 5,00 из 5)
Загрузка…
Назад к статье
Отчеты, документы, обработки
Назад к статье
Новости и изменения
Назад к статье
Дополнительные материалы
Назад к статье
Топ-вопросов
Последовательность событий при записи нового документа из формы документа
Заливкой выделены события, выполняющиеся в транзакции записи.
Последовательность событий при проведении документа из формы документа (провести и закрыть)
Заливкой выделены события, выполняющиеся в транзакции записи.
Последовательность событий при отмене проведения документа из формы документа
Заливкой выделены события, выполняющиеся в транзакции записи.
Обработчики событий
ПередЗаписью(Отказ, ПараметрыЗаписи) // модуль формы клиент
Расширение управляемой формы для документа. Возникает на клиенте перед выполнением записи объекта из формы. Процедура-обработчик данного события вызывается перед вызовом процедуры-обработчика события ПередЗаписью
.
Отказ
, типБулево
. Признак отказа от записи. Если в теле процедуры-обработчика установить данному параметру значениеИстина
, запись выполнена не будет и будет вызвано исключение. Значение по умолчанию:Ложь
.ПараметрыЗаписи
, типСтруктура
. Структура, содержащая параметры записи. Для формы документа существует два предопределенных параметра —РежимЗаписи
иРежимПроведения
. Они добавляются автоматически при вызове команд «Провести», «Провести и закрыть», «Отмена проведения». ПараметрРежимПроведения
может отсутствовать, если режим проведения в документе не определен.
Примечание: В обработчике данного события нельзя использовать серверные методы формы с директивой компиляции &НаСервере
.
ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи) // модуль формы сервер
Расширение управляемой формы для документа. Вызывается перед записью объекта на сервере. Можно отменить запись. Выполняется вне транзакции.
Отказ
, типБулево
. Признак отказа от записи. Если в теле процедуры-обработчика установить данному параметру значениеИстина
, запись выполнена не будет и будет вызвано исключение. Значение по умолчанию:Ложь
.ТекущийОбъект
, типДокументОбъект.ИмяДокумента
. Записываемый объект.ПараметрыЗаписи
, типСтруктура
. Структура, содержащая параметры записи. Для формы документа существует два предопределенных параметра —РежимЗаписи
иРежимПроведения
. Они добавляются автоматически при вызове команд «Провести», «Провести и закрыть», «Отмена проведения».
ДокументОбъект.ИмяДокумента.ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения) // модуль объекта сервер
Возникает перед выполнением записи объекта. Процедура-обработчик вызывается после начала транзакции записи, но до начала записи документа.
Отказ
, типБулево
. Признак отказа от записи. Если в теле процедуры-обработчика установить данному параметру значениеИстина
, то запись выполнена не будет и будет вызвано исключение. Значение по умолчанию:Ложь
.РежимЗаписи
, типРежимЗаписиДокумента
. В параметр передается текущий режим записи документа. Позволяет определить в теле процедуры режим записи. Изменение значения параметра позволяет изменить режим записи.РежимПроведения
, типРежимПроведенияДокумента
. В данный параметр передается текущий режим проведения. Изменение значения параметра позволяет изменить режим проведения.
ДокументОбъект.ИмяДокумента.ПриЗаписи(Отказ) // модуль объекта сервер
Возникает при записи объекта. Процедура-обработчик вызывается после записи объекта в базу данных, но до окончания транзакции записи.
Отказ
, типБулево
. Признак отказа от записи документа. Если в теле процедуры-обработчика установить данному параметру значениеИстина
, то запись документа выполнена не будет. Значение по умолчанию:Ложь
.
ПриЗаписиНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи) // модуль формы сервер
Расширение управляемой формы для документа. Возникает на сервере при записи объекта из формы. Процедура-обработчик вызывается после записи объекта (после события ПриЗаписи
объекта) в базу данных, но до окончания транзакции записи.
Отказ
, типБулево
. Признак отказа от записи. Если в теле процедуры-обработчика установить данному параметру значениеИстина
, то запись произведена не будет. Значение по умолчанию:Ложь
.ТекущийОбъект
, типДокументОбъект.ИмяДокумента
. Записываемый документ.ПараметрыЗаписи
, типСтруктура
. Структура, содержащая параметры записи. Для формы документа существует два предопределенных параметра —РежимЗаписи
иРежимПроведения
. Они добавляются автоматически при вызове команд «Провести», «Провести и закрыть», «Отмена проведения».
ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи) // модуль формы сервер
Расширение управляемой формы для документа. Вызывается после записи объекта на сервере и после завершения транзакции.
ТекущийОбъект
, типДокументОбъект.ИмяДокумента
. Записываемый объект.ПараметрыЗаписи
, типСтруктура
. Структура, содержащая параметры записи. Для формы документа существует два предопределенных параметра —РежимЗаписи
иРежимПроведения
. Они добавляются автоматически при вызове команд «Провести», «Провести и закрыть», «Отмена проведения».
ПослеЗаписи(ПараметрыЗаписи) // модуль формы клиент
Расширение управляемой формы для документа. Возникает на сервере после записи объекта и после окончания транзакции записи, то есть к моменту вызова этой процедуры запись объекта полностью завершена.
ПараметрыЗаписи
, типСтруктура
. Структура, содержащая параметры записи. Для формы документа существует два предопределенных параметра —РежимЗаписи
иРежимПроведения
. Они добавляются автоматически при вызове команд «Провести», «Провести и закрыть», «Отмена проведения».
Примечание: В обработчике данного события нельзя использовать серверные методы формы с директивой компиляции &НаСервере
.
Поиск:
1С:Предприятие • Документ • Перед записью • Перед записью на сервере • После записи • После записи на сервере • При записи • При записи на сервере • Событие
Добрый день! При проведении документа осуществленна проверка, в случае проведения документа из формы и наличию ошибок при проведении необходимо отобразить подробный список ошибок, механизм СообщитьПользователю не подходит для этого. Подскажите, как лучше передать результат выполнения на клиентскую часть? Погуглил — рекомендуют разместить результат в параметрах сеанса и т.д., но мне это кажется диким, что лучше сделать?
не, не подходит такой вариант Нужна специализированная форма в которой будет в иерархическом виде отображаться ошибки, предлагаться расшифровки и горячие кнопки для исправления… необходимо где то сохранить результат, чтобы потом его вывести в отдельной форме «для ошибок», либо на форме документа отобразить элемент, по нажатию на который будет выведена форма «для ошибок».
почему с этим не справиться регистр сведений?
при откате транзакции, он тоже откатывается.
Чем временное хранилище не подходит?
пока решил сделать именно через временное хранилище, остался вопрос только с тем как кинуть адресом временного хранилища в форму и в составке данных, которые кидаю…
+100500 — почему то все советуют сделать именно через справочник
ТЗ/ДЗ, что может быть проще?
так то эти данные не отображаются клиенте+кидаться с сервером ДЗ вообще нельзя.
Самый хороший способ — через доп.данные объекта-документа. Добавить в доп.данные таблицу значений, в форме должно быть событие, которое происходит после записи о объекта и в котором доступен сам объект, т.е. не произошла еще конвертация в реквизит формы. там и прочитать доп.параметры, заполнив реквизит формы — таблицу значений с сообщениями, ну а дальше можно делать с ней что угодно. Сейчас под рукой нет 1с, проверить это не могу, но я бы действовал примерно так
— если транзакция откатывается — события такого нет. И вообще с доп. свойствами можно работать на форме перед записью на сервере, после записи у тек. объекта их уже нет — там другой экземпляр. Хотя если такой способ рабочий, то может с ним стоит еще покопаться. Я пока решил эту проблему по другому, но хочу вернуться к ней и сделать правильно.
Что бы проверить, что он рабочий — нужно в конфигураторе посидеть. Но у формы документа, если я не ошибаюсь, есть событие, в котором доступен объект, не реквизит формы Объект, а именно тот объект, который был записан и он должен передаваться в параметре события. Но, конечно, я могу ошибаться. Проверить смогу только на следующей неделе -сейчас в отпуске
а если его персонально в транзакцию обернуть?
в 1с нет автономных транзакций. Все, что выполняется в рамках транзакции, например, запись, является одной транзакцией и будет откачено при роллбеке. Как вариант, если использовать временное хранилище — можно использовать guid самого объекта. Затем на форме получить значение из временного хранилища по этому guid-у. Это не очень хорошее решение, но рабочее.
Объект.ДополнительныеСвойства — это структура которая существует пока существует сам объект, сувай туда.
Я тут поигрался, и вроде как в случае отказа поведения, никакой обработчик формы не вызывается. Поэтому видится вариант делать свою кнопку проведения в которой получать объект, проводить, и ловить ошибки в ДополнительныеСвойства.
Тэги: 1С 8
Комментарии доступны только авторизированным пользователям
Если при исполнении программы происходит ошибка, то 1С сообщает о ней пользователю.
Сообщение обычно производится в окне с красным крестом, откуда такие сообщения называют «поймать красный крест» или «не работает, есть красные ошибки».
Конструкция 1С Попытка-Исключение служит для обработки возможных ошибочных ситуаций.
Хорошим тоном считается обрабатывать ошибки с помощью конструкции 1С Попытка-Исключение, то есть предусматривать места, где они могут произойти и ставить обработчик, который запишет ошибку «в сообщения администратору», а пользователю или сообщит корректно или найдет способ отработать по-другому.
Переменная1 = "22";
Попытка
//код, который может вызвать ошибку
ЧислоСтрокой = Число(Переменная1);
Исключение
ТекстОшибки = ОписаниеОшибки();
КонецПопытки
Код, в котором может произойти ошибка, обрамляется оператором 1С Попытка-Исключение.
В случае, если ошибка происходит, срабатывает выполнение кода между Исключение и КонецПопытки. Если ошибка не происходит, тот код между Исключение и КонецПопытки не выполняется.
Чтобы получить расшифровку ошибки, необходимо получить текст ошибки с помощью функции ОписаниеОшибки() сразу после слова Исключение.
Также существует функция ИнформацияОбОшибке(), которую можно вызывать вместо ОписаниеОшибки(). Разница в том, что она возвращает информацию в структурированном виде, а не строкой.
Структура, возвращаемая функцией ИнформацияОбОшибке():
Проголосовать за этот пост:
Загрузка…
Posted in Язык 1С
Как узнать ошибку проведения документа
Автор Sinsinmin, 06 мая 2011, 09:32
0 Пользователей и 1 гость просматривают эту тему.
программно пишу следующее
ЦитироватьПопытка
ТекстОшибки = «»;
ДокКадрПерем.Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Неоперативный);
Исключение
ТекстОшибки = ОписаниеОшибки();
ЗаписьОшибки(ТекстОшибки);
ДокКадрПерем.Записать(РежимЗаписиДокумента.Запись);
КонецПопытки;
Но получается что многие документы проводятся с противоречиями. Таким образом выполнение не заходит в исключение проведения. Каким образом можно узнать ошибку проведения документа?
Ошибки проведения обрабатываются в ОбработкеПроведения() документа и выводятся обычным Сообщить()…
- 1 пользователь сказал спасибо!
Получил помощь — скажи СПАСИБО.
Разобрался сам — расскажи другим.
Это код внешней обработки.
И мне нужно передать эти ошибки в текстовый документ, для логирования ошибок внешней обработки.
Ну а чем существубщий код не подходит?
Если ты имеешь ввиду мой код, то он выдаёт ошибку только при исключении. Тобишь когда проведение документа невозможно. А хотелось бы узнать ошибки при проведении документа.
Хм, а какие ошибки возможны при нормальном проведении документа? Документ либо провелся (ошибок нет), либо не провелся (ошибки есть). Или я не так понял?
Аааа, кажется вкурил. Ты имеешь ввиду ошибки, которые не отменяют при этом проведения документа?
В ОбработкеПроведения() там где сообщаете об ошибке через Сообщить() надо добавить код, который будет помещать ошибки еще куда нибудь, например в РегистрСведений…
Получил помощь — скажи СПАСИБО.
Разобрался сам — расскажи другим.
нельзя трогать код программы. Всё с помощью внешней обработки. Также нельзя делать регистры. Текстовый документ самое то.
Как обработать ошибку проведения, если активна транзакция? |
Я |
27.10.20 — 14:28
[1c]НачатьТранзакцию();
Для Каждого Об Из Доки Цикл
Попытка
Об.Провести(); //вот тут иногда может быть ошибка, которая рушит всю транзакцию
Исключение
Об.Записать();
КонецПопытки;
КонецЦикла;
ЗафиксироватьТранзакцию();
[/1c]
Понимаю, что вопрос поднимался уже не раз. Когда уже придумают удобное решение?
Если только программно проверять документ перед проведение? Это же ужасно, не?
1 — 27.10.20 — 14:29
Что значит «обработать»?
2 — 27.10.20 — 14:29
Переписать проведение так, чтобы оно не вызывало исключения
3 — 27.10.20 — 14:34
(2) Не сможет. Остатки в минус уйдут)
4 — 27.10.20 — 14:36
НачатьТранзакцию();
Попытка
Для Каждого Об Из Доки Цикл
Об.Записать();
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение
КонецПопытки;
НачатьТранзакцию();
Попытка
Для Каждого Об Из Доки Цикл
Об.Провести();
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение
КонецПопытки;
5 — 27.10.20 — 14:37
(3) А зачем вызывать исключение, если остатка не хватает?
6 — 27.10.20 — 14:38
(5) Эмм. Ну типа отказ = истина в обработке проведения будет.
7 — 27.10.20 — 14:39
8 — 27.10.20 — 14:41
(4) Из-за одного документа может не провестись весь хвост массива
Наверн, логично делать запись в транзакции, а проведение уже после. Спасибо за идею
9 — 27.10.20 — 14:48
(6) Эммм. От этого будет исключение? Давно такие новшества?
10 — 27.10.20 — 14:49
(9) От этого будет исключение, да. Начиная с 8.0
11 — 27.10.20 — 14:50
https://its.1c.ru/db/metod8dev#content:2313:hdoc
Если восстановимая ошибка базы данных произошла в процессе выполнения транзакции, то, вне зависимости от того, было исключение, вызванное этой ошибкой, перехвачено и обработано или нет, транзакция уже не может быть продолжена или зафиксирована. Единственная операция с базой данных, которую можно произвести в такой ситуации — это отмена транзакции.
>>а проведение уже после
как вариант уже без транзакции и писать куда-то для «разбора полетов»
12 — 27.10.20 — 14:57
(0) А нафига у тебя вообще в транзакции?
Документы настолько «легкие», что дает эффект ускорения?
(8) А у тебя допустимо пропускать непроведенные? Тогда можно просто если в пачке произошла ошибка, делать откат транзакции и запускать эту пачку повторно на проведение, но уже без транзакции.
13 — 27.10.20 — 15:01
(12) >А нафига у тебя вообще в транзакции?
Чтобы этот кусок кода мог выполняться только в одном сеансе одновременно
Транзакция нужна, чтобы блокировку держать (мьютекс)
Ну и откатывать транзакцию с сотней документов из-за одного документа как-то не оч..
14 — 27.10.20 — 15:03
а 7.7 поддерживала вложенные транзакции…
15 — 27.10.20 — 15:03
Жесть какая
Для Каждого Об Из Доки Цикл
Попытка
Об.Провести(); //вот тут иногда может быть ошибка, которая рушит всю транзакцию
Исключение
ЗафиксироватьТранзакцию();
НачатьТранзацию();
Об.Записать();
КонецПопытки;
КонецЦикла;
ЗафиксироватьТранзакцию();
16 — 27.10.20 — 15:05
(15) Епстественно, это рушит основопологающий механизмь транзакций, но в свете подарка от 1С «в данной транзакции уже происходили ошибки» как то похер
17 — 27.10.20 — 15:06
(9) Добро пожаловать в новый чудный мир.
18 — 27.10.20 — 15:06
(12) Да, это дает офигенные ускорения.
19 — 27.10.20 — 15:06
(13) Суровые у тебя мьютексы
20 — 27.10.20 — 15:07
(13) А не пробовал мутексы по-нормальному делать? А не держать всю базу?
21 — 27.10.20 — 15:08
(19) У него вообще сурово. Он как будто откудато вылез и осваивает этот дивный мир. Ну хоть не 15 лет жил и не знал про генерацию исключения при отказе. Уже неплохо.
22 — 27.10.20 — 15:08
(14) А вложенные исключение?
23 — 27.10.20 — 15:09
Раз речь за мьютексы, значит ты параллелишь. Верно?
И в чем проблема распараллелить так, чтобы задачи не пересекались?
24 — 27.10.20 — 15:10
(20) Ну может надо человеку. Но меня не покидает ощущения велосипедостроительства.
Автор посмотри в сторону
Об.ДополнительныеПараметры.Вставить(«ЭтоФлагОтключенияВсехПроверокОтЕвгения»,Истина);
25 — 27.10.20 — 15:12
26 — 27.10.20 — 15:13
(13) > Ну и откатывать транзакцию с сотней документов из-за одного документа как-то не оч.
Если ориентироваться на оптимистический сценарий, то норм. А если это у тебя нормальная ситуация, тогда скорее всего архитектуру поправить надо.
27 — 27.10.20 — 15:15
(22) вложенные исключения есть и в 77 и 8
28 — 27.10.20 — 15:15
(27) Мы видим на примере этой темы насколько они «есть»
29 — 27.10.20 — 15:19
(14) Да ладно? Что, можно было откатить вложенную транзакцию с успешным завершением внешней?
(28) Вложенные транзакции и исключения СУБД — это козырная карта. Она перебивает А так — есть.
30 — 27.10.20 — 15:43
(7) Блин, нафига я это читал.. По делу ничего нет
(19) Самые обычные. Из запасных вариантов — только извращаться с регл. заданиями
(20) Какую всю базу? Как лучше мьютекс сделать? Тему я уже создавал
Самый простой способ создать мьютекс?
(23) Нет. Код может выполняться либо регл. заданием, либо по кнопке. Нужно, чтобы эти два сценария не пересеклись
(15) После исключения он даст зафиксировать транзацию?
Выглядит дико, плюс вариант не мой, т.к. не могу транзакцию разрывать
(24) Если так сделать, то это и будет велосипедостроительством
31 — 27.10.20 — 15:50
Если скорость не критична, то для проведения каждого документа запускаешь фоновое задание, которое в транзакции проводит документ и всегда откатывает транзакцию и возвращает результат в основной поток. Если фоновому удалось успешно провести, то в основном потоке проводим документ. Понятно что есть ненулевая вероятность получить плавающую ошибку проведения, если в базе работают другие сеансы, но эта вероятность будет значительно ниже чем в других схемах.
32 — 27.10.20 — 15:57
(31) А можно последовательно на каждый запустить пять фоновых… А лучше 10. Для гарантии.
33 — 27.10.20 — 16:20
(30) Можно по кнопке запускать фоновое от того же регламентного. Там в регламенте можно настроить, чтобы задания не пересекались. Типа если такое же уже выполняется, то регламент будет ждать. А по кнопке можно проверять — если регламент уже пашет, то так пользователю и говорить.
34 — 27.10.20 — 16:22
(30) «Если так сделать, то это и будет велосипедостроительством»
нет. Исключения, даже и обрабатываемые — это жопа. Ты это поймешь, когда будешь что-то отлаживать «по ошибке».
35 — 27.10.20 — 16:22
(30) » После исключения он даст зафиксировать транзацию?
Выглядит дико, плюс вариант не мой, т.к. не могу транзакцию разрывать »
Даст
36 — 27.10.20 — 16:22
Есть еще примитивнее способ внеочередного запуска регламента по кнопке. Тупо его «передернуть» (деактивировать-активировать).
37 — 27.10.20 — 16:25
(35) При ошибке вложенной транзакции зафиксировать внешнюю уже не даст. Об этом и речь. Попытки/исключения не помогут.
38 — 27.10.20 — 16:47
(0) Записать документы в транзакции, проводить потом уже без нее
DTX 4th
39 — 27.10.20 — 17:14
(35) (37) Так даст или нет?)
(38) Я пока на этом в (8) и остановился, но такой вариант не всегда может быть уместен
Исключения (исключительные ситуации, exceptions) являются механизмом обработки ошибок, который пришел на смену возврату кода ошибки функциями. Тем не менее, многие разработчики до сих пор используют возврат кода ошибки вместо исключений — в частности очень многие участники конференции C++ Russia 2015 говорили об этом, включая разработчиков Яндекса.
В заметке мы рассмотрим:
- обработку каких ошибок можно выполнять с использованием исключений;
- синтаксические конструкции, введенные в языки для обработки исключений (примерно одинаковые в разных языках программирования — Java, C++, C# и др.).;
- рекомендации по использованию исключений (в т.ч. по книгам о чистоте кода);
Несмотря на то, что примеры программ приводятся на С++, тут не приводятся особенности отдельных языков программирования, связанные с обработкой исключений, а описываются лишь принципы, общие для большинства языков.
Что следует понимать под ошибкой?
Исключения являются механизмом обработки ошибок, при этом под ошибкой имеется ввиду любая ситуация, в которой функция не может продолжить корректное выполнение из-за каких-либо внешних факторов:
- функция отправки данных по сети (через сокет) не может знать как в вашем конкретном приложении должен обрабатываться разрыв соединения;
- функция, выполняющая запрос к базе данных может вырабатывать исключение в случаях если база стала недоступна или в запросе допущена ошибка;
- было бы хорошо, если бы функция вычисления квадратного корня не завершала аварийно работу программы в случае если на вход подано отрицательное число, а вырабатывала бы исключение.
Способы обработки ошибок
Таким образом, под ошибкой надо понимать только те проблемы, которые функция не может решить локально. В этих случаях функция должна как-то известить того, кто ее вызвал о сложившейся проблеме, есть различные способы сделать это:
- возврат кода ошибки. Например, если при отправке сообщения по сети выяснится, что соединение разорвано, функция отправки может вернуть единицу, а при успешной отправке — ноль;
- вернуть заведомо некорректный результат. Например, функция
malloc
, выделяющая память в языке Си при ошибке (невозможности выделить память) возвращает ноль, а в остальных случаях — начальный адрес выделенного фрагмента; - вернуть любое допустимое значение и выставить глобальный флаг ошибки (в языке С++ для этого может использоваться глобальная переменная
errno
. - аварийно завершить работу (в С++ для этого используются функции
abort
илиexit
); - выработать исключение (подробнее написано ниже);
Конечно, помимо завершения работы, вы всегда можете записать описание сложившейся ситуации в log-файл (поток cerr
в С++) или вывести на экран.
Аварийное завершение работы — это худший способ обработки ошибки, т.к. программист фактически расписывается в своей неспособности что-то исправить.
С точки зрения последовательности выполнения команд, возврат кода ошибки ничем не отличается от возврата некорректного значения или выставления флага ошибки в глобальный объект. Во всех этих случаях функция, которая не знает как корректно обработать входные данные передает эти обязанности непосредственно коду, который ее вызвал. Чаще всего это работает хорошо, однако:
- если вызывающий код не обрабатывает код ошибки (он ведь не обязан это делать), то он продолжает вычисления, но уже с некорректными данными. Такая ситуация выявится не сразу, ведь вы можете получить некорректный результат или аварийный останов совсем с другом месте. Например, в библиотеке Qt есть функция преобразования строки в целое число:
int QString::toInt(bool * ok = 0, int base = 10) const
В случае ошибки она вернет ноль и присвоит значениеfalse
аргументуok
(который вообще не является обязательным). Если наш код вызывает эту функцию для строки, не являющейся целым число — то он продолжит выполнение, получив в качестве результата ноль (вполне корректное, но неверное значение); - далеко не всегда удается правильно обработать ошибку непосредственно в коде, вызвавшем функцию. В результате получается примерно следующая ситуация:
bool foo(QString str) { bool ok; string.toInt(&ok); if (false == ok) { return false; } // ... some actions } bool bar() { QString str; // ... some actions if (false == foo(str)) return fasle; } }
Что в этом плохого? — значительная часть кода делает лишь то, что доставляет код ошибки до точки, в которой его можно корректно обработать. Притом, ни одна из всех этих функций не обязана это делать, т.е. вы в любой момент можете потерять ошибку и приступить к долгой отладке.
В этом контексте, исключения можно рассматривать как механизм доставки информации об ошибки до точки программы, где эту ошибку наиболее естественно обрабатывать. Доставка ошибки при этом будет выполняться точно также, как и серия операторов return
в приведенном выше фрагменте кода — каждая функция получившая исключение передает управление «наверх» (раскручивая стек), процесс продолжается до тех пор, пока исключение не попадет в блок trt{}
, содержащий обработчик, совместимый с типом исключения.
Исключением также называют объект, некоторого класса, являющийся представлением ошибки (исключительного случая).
Синтаксические конструкции механизма обработки исключений
Фрагмент кода, внутри которого могут вырабатываться исключения должен быть помещен в блок try{}
, к которому могут быть добавлены обработчики — блоки catch(ExceptionType) {}
. Функция может вырабатывать исключения при помощи оператора throw
. Например:
try { socket = connect_to_server(host); // throw BadHostException or ServerNotAvailableException send_message(socket, message); // throw ServerNotAvailableException or BadMsgException } catch(BadHostException exception) { // BadHostException hadler // can charge other host from user } catch(ServerNotAvailableException exception) { // ServerNotAvailableException handler // can reconnect for example } catch(BadMsgException exception) { // BadMsgException hadler // can notify user by window } catch(...) { // other types of exceptions hadler }
При возникновении исключения (вызове throw
) в какой либо из функций начинается раскрутка стека (в том числе освобождается память из под всех локальных объектов), до тех пор, пока не будет обнаружен подходящий catch
в функции, которая непосредственно или косвенно вызвала функцию, сгенерировавшую исключение.
Исключения могут организовываться в иерархии посредством наследования. Например, в стандартной библиотеке C++ определен базовый класс std::exception
, от которого наследуется std::logic_error
(класс логических ошибок), являющий базовым для std::out_of_range
. Для фрагмента кода, рассмотренного выше, мог быть выделен класс NetworkException
, базовый для BadHostException
и ServerNotAvailableException
. При этом, если при обработке нам не важно какие именно проблемы с сетью произошли — мы можем написать обработчик для базового класса.
К блоку try{}
может быть написано несколько обработчиков, совместимых с одним и тем же типом исключения. Блоки catch
обрабатываются в порядке их перечисления, поэтому будет выбран тот, который описан выше. Например:
catch(BadHostException exception) { // BadHostException hadler } catch(NetworkException exception) { // all network exceptions, but not BadHostException } catch(ServerNotAvailableException exception) { // never call, because ServerNotAvailableException was hanled as NetworkException }
Рекомендации по использованию исключений
Выше описаны общие (подходящие для большинства языков) принципы обработки исключений. Однако, в каждом языке есть свои тонкости. Так, например, в языке Java есть ключевое слово finally
, задающее фрагмент кода, который будет выполнен при возникновении исключения любого вида. В языке C++, например: надо учитывать то, что при обработке throw
создается копия исключения; в обработчике исключения запись throw;
позволяет передать текущее исключение дальше без копирования; и множество других тонкостей [3]. Во многих языках можно задать спецификацию исключений для функции (используйте спецификации исключений), т.е. список исключений, которые могут выходить из нее — если вдруг какой-либо участок кода начнет вырабатывать новый тип исключения — вы получите ошибку на этапе компиляции. Изучите особенности реализации механизма исключений для своего языка программирования.
Исключения являются более безопасным и удобным механизмом, чем коды ошибок — за счет спецификации исключений компилятор гарантирует, что вы не пропустите по невнимательности обработку ошибки, кроме того, ошибки будут сами доставляться до подходящего обработчика. Страуструп пишет, что в ряде случаев только за счет использования исключений удается сократить объем кода обработки ошибок в два раза [1]. В связи с этим, следует использовать исключения вместо кодов ошибок, такой подход окажет положительное влияние на архитектуру всего приложения.
Роберт Мартин рекомендует начинать написание функции с try-catch-finally
. Это заставит программиста еще раз задумать о том, какие ошибки могут произойти — на самом деле, почти любая функция может столкнуться с ситуациями, в которых не сможет продолжить вычисления [2].
Старайтесь использовать иерархии исключений. Нередко, внутри библиотеки может вырабатываться огромное количество разных видов исключений, однако пользователю могут быть не важны детали проблемы — например, ему достаточно знать, что видеокарта не поддерживает нужный ему набор функций, но совершенно ненужно знать почему. Мартин советует определять классы исключений в контексте потребностей вызывающей стороны [2], однако чаще всего этого можно достичь при помощи иерархий, т.к. клиент всегда сможет обработать как конкретные, так и базовые классы исключений.
Пишите код, безопасный с точки зрения исключений (exception-safe)
Что такое «код, безопасный с точки зрения исключений»? Пусть имеется какой-то произвольный фрагмент кода. Где-то в процессе выполнения этого кода генерируется исключение и перехватывается снаружи. Этот фрагмент кода безопасен с точки зрения исключений если он, в идеале, отвечает следующим требованиям:
- Все ресурсы, выделенные внутри блока try до генерации исключения, должны быть корректно освобождены;
- Все объекты, созданные внутри блока try до генерации исключения, должны быть корректно уничтожены;
- Должен произойти полный откат всех изменений в системе, внесенных кодом, который выполнился от начала блока try до момента генерации исключения.
Эти три пункта указываются почти в любой литературе, в которой идет речь о безопасности с точки зрения исключений. По большому счету, первые два пункта это частные случаи более общего правила, указанного в третьем пункте. Код, безопасный с точи зрения исключений, это код, обладающий транзакционным поведением.
Транзакционность и «бизнес-логика»
Блоками try
следует охватывать те участки кода, которые должны иметь транзакционное поведение с точки зрения «бизнес-логики» самой программы.
Рассмотрим в качестве примера графический редактор, имеющий multi-dialog интерфейс, и следующий сценарий работы: пользователь выбирает у главном меню «Open file(s)», выделяет десять картинок и нажимает «Open». Восемь картинок загружаются корректно, загрузка девятой генерирует исключение.
Для того, чтобы понять, какой именно участок кода следует охватить блоком try, необходимо решить, какая именно логика работы с точки зрения сценария должна быть реализована. В рассматриваемом примере возможны два очевидных варианта, и оба вполне имеют право на существование. Первый — открыть только те картинки, которые корректно загрузились и показать сообщение об ошибке. Второй — показать сообщение об ошибке и выбросить все успешно загруженные картинки, тем самым выполнив полный откат системы. В первом случае элементом транзакции является код загрузки отдельно взятой картинки — он и берется в блок try. Во втором случае элементом транзакции является код загрузки множества картинок, то есть фрагмент, находящийся на один логический этаж выше.
Итак, блоками try
следует охватывать участки кода, которые должны представлять собой транзакцию с точки зрения «бизнес-логики». При этом следует стараться расположить блок try
как можно выше относительно вложенности функциональности, делая шаги вверх по этажам до тех пор, пока языковая транзакция не начнет противоречить логике, которая должна быть реализована в программе. И логика эта, как показывает практика, может быть очень разнообразной.
Обеспечение транзакционности
Некоторые авторитетные люди, чьи книги издаются чуть ли не миллионными тиражами, утверждают, что одна из причин, по которой исключения лучше чем коды ошибок, заключается в том, что код, занимающийся ремонтом программы можно отделить от логики работы программы, поместив его в блоки catch
. К сожалению, этот подход устарел уже на следующий день после выхода первого Стандарта языка C++, а его использование говорит о полном непонимании всей прелести языка.
В идеале, весь код по ремонту программы должен быть реализован на уровне описания типов, то есть так или иначе посредством идиомы владения. В блоках catch
следует содержать только код, занимающийся оповещением об ошибке. Если это оконное приложение, то достаточно показать MessageBox
, если серверная система, то сделать запись в лог или отправить сообщение по почте.
Следует внимательно следить за тем, чтобы способ оповещения об ошибке не генерировал исключений, поскольку исключение при попытке проинформировать об ошибке — ситуация не из приятных. Если требуется оповестить об ошибке способом, который может сгенерировать исключение (например — отправка сообщения по почте), то такую работу лучше осуществлять в очереди отложенных задач в отдельном потоке, добавляя в блоке catch
негенерирующим исключения способом новую отложенную задачу.
Типы данных
Практически во всех ситуациях наиболее естественный тип данных для пользовательских исключений с точки зрения C++ это тип, так или иначе производный от std::exception
. Моя рекомендация состоит в том, чтобы в качестве типа данных для пользовательских исключений использовать std::runtime_error
, или его наследников, если это необходимо.
Некоторые люди (не буду показывать пальцем) используют оператор new для генерации исключений и перехватывают их по указателю. Такая конструкция в свое время была (а может быть и до сих пор есть) в макросах Visual Assist-а. Такой подход в C++ в корне неверен — применять его можно только в языках со сборщиком мусора (gc). Использование такого подхода в C++ говорит о полном непонимании механизма исключений. Генерировать исключения следует по значению, перехватывать — по константной ссылке.
Классификация и оповещение
Любая ошибка имеет причину и влечет следствие. Используйте виртуальную функцию std::exception::what()
для того, чтобы получить человеческое описание причины возникновения ошибки.
Различные варианты наследников std::runtime_error
помогут классифицировать тип ошибки для правильного способа оповещения, и, если это все же по каким-то причинам необходимо, для выполнения необходимых действий по приведению программы в корректное работоспособное состояние. Например, исключения различных подсистем могут иметь различный тип данных.
namespace network { class error : public std::runtime_error { virtual char const* what() const; }; } // namespace network namespace ui { class error : public std::runtime_error { virtual char const* what() const; }; } // namespace ui int main() { application app; while(app.alive()) { try { app.run(); } catch(network::error const& e) { std::cout << "Network exception: " << e.what() << std::endl; mailto("admin@microsoft.com", e.what()); } catch(ui::error const& e) { std::cout << "UI exception: " << e.what() << std::endl; message_box(e.what()); } catch(std::exception const& e) { std::cout << "Exception: " << e.what() << std::endl; } } return 0; }
Следствие ошибки напрямую зависит от того места, где исключение было перехвачено. Следствие любого перехваченного исключения в коде, безопасном с точки зрения исключений, это откат системы на блок try
, в котором это исключение было сгенерированно. То есть, говоря по-простому, следствие это «что не получилось сделать», в то время как причина это «почему это не получилось сделать». Место генерации исключения и его тип — это причина, место его перехвата — это следствие.
Составление сообщения об ошибке — очень ответственная задача, с которой, к сожалению, зачастую не справляются даже самые крупные программные продукты. Хорошее сообщение об ошибке должно содержать информацию как о причине ошибки, так и о ее следствии. Вот пример хорошего сообщения об ошибке:
Не могу загрузить изображение (следствие):
недостаточно прав для чтения файла 001.jpg (причина)
Подведем итоги
В этой статье я уделил много времени описанию того, что стоит понимать под исключительной ситуацией (ошибкой). Это очень важно, т.к. может возникать соблазн использовать этот механизм для других целей. Даже если это сработает эффективно в каком-либо случае, код будет очень трудно читаться.
Можно порассуждать о том, должна ли функция вычисления квадратного корня или оператор деления вырабатывать исключения при отрицательном или нулевом значениях аргумента. Во-первых это могло быть не эффективно, во-вторых, скорее всего гораздо удобнее в таких случаях проверить корректность аргумента до вызова функции, чем обрабатывать ошибки после. Однако, подобная предварительная проверка была бы не эффективной например для функции преобразования строки в число.
Тут же, можно вспомнить, что исключения не используются во многих проектах по причинам недостаточной эффективности — например в Яндекс.Браузере. Кроме того, исключения не использовались в библиотеке Qt (по крайней мере 4.х версии) из-за сложности их реализации на некоторых архитектурах. Тот факт, что в настоящее время библиотека Qt сейчас использует механизм исключений означает, что его поддерживает подавляющее большинство компиляторов С++ под самые разные платформы.
Мое личное мнение — нужно стараться использовать исключения вместо кодов ошибок, однако при этом надо думать и об архитектуре приложения, и об удобстве использования ваших классов и функций. В большинстве случаев исключения помогут вам написать более чистый, понятный и безопасный код. В совсем редких случаях они окажут какое-либо заметное влияние на производительность, ведь исключения вообще не должны вырабатываться в программе часто — это механизм обработки ошибок, которые не должны сыпаться как из рога изобилия.
Пишите код, транзакционный с точки зрения исключений. Реализуйте транзакционность на уровне описания типов данных, максимально минимизируя ремонт программы в блоках catch
. Сопоставляйте блокам try соответствующие транзакции с точки зрения «бизнес-логики». Используйте std::runtime_error
или его наследников в качестве типов данных для исключений. Генерируйте исключения по значению, перехватывайте по константной ссылке. Классифицируйте ошибки, если это необходимо. Предоставляйте понятное человеку сообщение об ошибке; указывайте как причину, так и следствие. Оповещайте об исключении способом, не генерирующим исключения.
Литература по теме обработки исключений:
- Б. Страуструп Язык программирования С++. Специальное издание. Пер. с англ. – М.: Издательство Бином, 2011 г. – 1136 с.
- Мартин Р. Чистый код. Создание, анализ и рефакторинг. Библиотека программиста. – СПб.: Питер, 2014. – 464 с.
- Мейерс С. Эффективное использование С++. 35 новых рекомендаций по улучшению ваших программ и проектов. – М.: ДМК Пресс, 2014. – 294с.
EXCEPTION блок
Обработка ошибок производится в блоке exception
:
begin
-- Код
exception
-- Обработка ошибок
when .... then .....;
when .... then .....;
when .... then .....;
end;
Ошибки отлавливаются в пределах блока begin-end
. Работает это так:
- Сначала выполняется код между
begin
иexception
- Если ошибок не произошло, тогда секция между
exception
иend
ингорируется - Если в процессе выполнения кода происходит ошибка, выполнение останавливается
и переходит в блокexception
. - Если в блоке находится обработчик для исключения, вызывается код после
then
- Если обработчик не найден, исключение выбрасывается за пределы блока
begin-end
Пример блока с обработчиком исключений:
declare
l_val number;
begin
select 1 into l_var
where 2 > 3;
exception
when no_data_found then
dbms_output.put_line('Нет данных');
when dup_val_on_index then
dbms_output.put_line('Такая строка уже есть');
end;
Предопределённые ошибки
Ошибки обрабатываются по их имени, поэтому часть наиболее частых ошибок в PL/SQL
уже предопределена, как например вышеуказанные no_data_found
и dup_val_on_index
.
Ниже показан их список и в каких случаях ошибка может возникнуть.
Ошибка | Когда возникает |
---|---|
ACCESS_INTO_NULL | Попытка присвоить значение атрибуту неинициализированного объекта. |
CASE_NOT_FOUND | В выражении CASE не нашлось подходящего условия When , и в нём отсутствует условие Else . |
COLLECTION_IS_NULL | Попытка вызвать любой метод коллеции(за исключением Exists ) в неинициализированной вложенной таблице или ассоциативном массиве, или попытка присвоить значения элементам неинициализированной вложенной таблице или ассоциативного массива. |
CURSOR_ALREADY_OPEN | Попытка открыть уже открытый курсор. Курсор должен быть закрыт до момента его открытия. Цикл FOR автоматически открывает курсор, который использует, поэтому его нельзя открывать внутри тела цикла. |
DUP_VAL_ON_INDEX | Попытка вставить в таблицу значения, которые нарушают ограничения, созданные уникальным индексом. Иными словами, ошибка возникает, когда в колонки уникального индекса добавляются дублирующие записи. |
INVALID_CURSOR | Попытка вызова недопустимой операции с курсором, например закрытие не открытого курсора. |
INVALID_NUMBER | Ошибка приведения строки в число в SQL запросе, потому что строка не является числовым представлением (В PL/SQL коде в таких случаях выбрасывается VALUE_ERROR). Также может возникнуть, если значение параметра LIMIT в выражении Bulk collect не является положительным числом. |
LOGIN_DENIED | Попытка подключиться к БД с неправильным логином или паролем. |
NO_DATA_FOUND | Выражение SELECT INTO не возвращает ни одной строки, или программа ссылается на удалённый элемент во вложенной таблице или неинициализированному объекту в ассоциативной таблице. Агрегатные функции в SQL, такие как AVG или SUM, всегда возвращают значение или null. Поэтому, SELECT INTO , которое вызывает только агрегатные функции, никогда не выбросит NO_DATA_FOUND . Выражение FETCH работает так, что ожидает отсутствия строк в определённый момент, поэтому ошибка также не выбрасывается. |
NOT_LOGGED_ON | Обращение к БД будучи неподключенным к ней |
PROGRAM_ERROR | Внутренняя проблема в PL/SQL. |
ROWTYPE_MISMATCH | Курсорные переменные, задействованные в присваивании, имеют разные типы. |
SELF_IS_NULL | Попытка вызвать метод неинициализированного объекта. |
STORAGE_ERROR | Переполнение памяти или память повреждена. |
SUBSCRIPT_BEYOND_COUNT | Попытка обратиться к элементу вложенной таблицы или ассоциативного массива по индексу, который больше, чем количество элементов в коллекции. |
SUBSCRIPT_OUTSIDE_LIMIT | Попытка обратиться к элементу коллекции по индексу(например, -1) вне допустимого диапазона. |
SYS_INVALID_ROWID | Ошибка конвертации строки в rowid. |
TIMEOUT_ON_RESOURCE | Возникает при ожидании доступности ресурса. |
TOO_MANY_ROWS | Выражение SELECT INTO возвращает более одной строки. |
VALUE_ERROR | Арифметическая ошибка, ошибка конвертации, или превышение размерности типа. Может возникнуть, к примеру, если в переменную с типом number(1) попытаться присвоить значение 239 . |
ZERO_DIVIDE | Попытка деления на ноль. |
Объявление собственных ошибок
Можно объявлять собственные исключения, давая им
названия, которые полнее раскрывают их суть.
declare
-- Объявление собственного исключения,
-- которое мы выбрасываем, если значение заработной
-- платы ниже дозволенного минимума.
exc_too_low_salary exception;
l_salary number := 100;
begin
if l_salary < 200 then
-- Бросаем ошибку.
raise exc_too_low_salary;
end if;
exception
when exc_too_low_salary then
dbms_output.put_line('Обработчик исключения');
end;
Область видимости собственного исключения в данном случае — блок, в котором оно
объявлено. Вне этого блока обработать исключение не получится.
Для более удобной работы с собственными исключениями их можно вынести в отдельный пакет:
create or replace pck_hr_errors is
-- Объявляем исключения в спецификации пакета.
-- Тела пакет не имеет, только спецификацию.
exc_wrong_name exception;
exc_too_low_salary exception;
exc_incorrect_pass exception;
end;
Далее работать с этими исключениями можно подобным образом:
begin
-- Какой-то код
...
exception
when pck_hr_errors.exc_too_low_salary then
-- Обработка исключения
...
end;
Обработка непредопределённых ошибок
Не все ошибки в Oracle являются предопределёнными. Когда возникает необходимость
их обрабатывать, нужно связать переменную типа exception
с кодом ошибки, которую нужно обработать:
declare
-- объявляем ошибку
e_incorrect_date exception;
-- связываем ошибку с кодом
pragma exception_init(e_incorrect_date, -1830);
begin
dbms_output.put_line(to_date('2022-02-01', 'dd.mm.yyyy'));
exception
when e_incorrect_date then
dbms_output.put_line ('Неправильный формат даты');
end;
Следует помнить, что коды ошибок являются отрицательными числами.
Ошибки и вложенные блоки
Если ошибка не обрабатывается в пределах блока begin ..end
,
она выбрасывается за его пределы. Далее эта ошибка может быть
обработана блоком exception
внешнего блока. Если и там ошибка
не обрабатывается, она выбрасывается и за его пределы, и так
далее.
declare
a number;
-- Внешний блок
begin
-- Вложенный блок
begin
a := 1 / 0;
-- Важно помнить, что после возникновения ошибки
-- выполнение кода в пределах блока прекращается.
-- Следующий код не будет выполнен
dbms_output.put_line('Этот код не будет выполнен');
end;
exception
when zero_divide:
dbms_otuput.put_line('Ошибка обработана внешним блоком');
end;
raise_application_error
Если ошибка, брошенная Oracle, достигает клиентского приложения,
то она имеет примерно такой текст: ORA-01722 invalid number
.
Процедура raise_application_error
позволяет вызвать исключение
с заданным номером, связать его с сообщением и отправить его
вызывающему приложению.
begin
raise_application_error(-20134, 'Неправильный номер паспорта');
end;
Диапазон возможных кодов ошибок [-20999, 20000]. Сообщение должно
помещаться в тип varchar2(2000)
.
Можно указать третий boolean параметр, который в случае
значения true
добавит текущую ошибку в список предыдущих
ошибок, возникших в приложении. По умолчанию значение равно false
,
что значит, про сообщение об ошибке заменяет все предыдущие ошибки
собой.
Мы можем объявить собственное исключение, связать его с номером
в диапазоне [-20999, 20000] и использовать для обработки исключений,
брошенных с помощью raise_application_error
:
declare
e_wrong_passport exception;
-- связываем ошибку с кодом
pragma exception_init(e_wrong_passport, -20999);
begin
raise_application_error(-20999, 'Неправильный номер паспорта');
exception
when e_wrong_password then
dbms_output.put_line ('Неправильный номер паспорта');
end;