и снова про исключения
От: MadHuman Россия  
Дата: 24.12.18 14:13
Оценка:
Всем привет!

Допустим вызываем MethodA (который в свою очередь может вызвать MethodB и ещё ряд подобных, каждый из которых в свою очередь тоже что-то вызывает) и вызов закончился ошибкой.
Возможные подходы по обработке/генерации исключений:
а. в MethodA делаем кэтч и генерим новое исключение (возможно спец. класса) с передачей в качестве InnerException исходного исключения.
особенно такое имеет смысл если с уровня MethodA надо в новое исключение включить какую-то доп. информацию. Например о параметре с которым вызван метод или о том, на какой фазе процесса упали и тп.

б. тоже, но перегенерим исходное исключение, добавив в исходный инстанс исключения в Data эту доп. информацию.

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

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

Господа, кто на практике к чему пришел в подобных случаях?
Re: и снова про исключения
От: fmiracle  
Дата: 24.12.18 14:38
Оценка: 8 (2) +4
Здравствуйте, MadHuman, Вы писали:

MH>Господа, кто на практике к чему пришел в подобных случаях?


— Либо пойманное исключение никак не меняется и отправляется выше (try/catch выполняет только подчистку или даже отсутсвует вообще если подчищать нечего), это по-умолчанию.

— Либо выбрасывается собственное исключение, где исходное содержится в Inner либо даже отсутствует вообще (если считается, что более полезной информации в нем не осталось). Это для случая, когда возможно вызывающему коду как-то более осмысленно описать ситуацию.

Предположим, у нас есть система, которая работает с MS SQL Server. И вот в коде DAL ловим SqlException, проверяем в нем код ошибки и если это 1205, то выбрасываем собственное DatabaseDeadlockException — и это сигнал вызывающему коду, что операция не прошла, но ее имеет смысл попробовать повторить позже — очень вероятно, что при повторном выполнении операция пройдет успешно, что отличается от, например, случая нарушения констрейнта — тут второй раз наверняка будет то же самое, и автоматическую обработку ситуации не сделать, надо просто писать все в лог и сообщать разработчикам. Соответственно, если на уровнях выше ничего осмысленного кроме записи в лог делаться не будет, то можно пробрасывать наверх ровно исходное исключение.
При этому вызывающему коду не нужно копаться в кишках SqlException, про который он как-то не в теме, чтобы различить случаи что именно не так с БД — он просто отлавливает определенные типы, которые знает, и делает обработку, либо исключение уходит еще выше.

Если вдруг позже добавляется альтернативный DAL для работы с Ораклом, то в нем по-своему определяется дедлок это или нет, а код выше никак не меняется.
Re[2]: и снова про исключения
От: MadHuman Россия  
Дата: 27.12.18 19:28
Оценка:
Здравствуйте, fmiracle, Вы писали:


MH>>Господа, кто на практике к чему пришел в подобных случаях?


F>- Либо пойманное исключение никак не меняется и отправляется выше (try/catch выполняет только подчистку или даже отсутсвует вообще если подчищать нечего), это по-умолчанию.


F>- Либо выбрасывается собственное исключение, где исходное содержится в Inner либо даже отсутствует вообще (если считается, что более полезной информации в нем не осталось). Это для случая, когда возможно вызывающему коду как-то более осмысленно описать ситуацию.


это ок. а как бы вы сделали в следующем случае — допустим есть процесс приёма заказа, допустим при его сохранении есть настраиваемые пользователем "триггеры". допустим триггер — содержит некое условие срабатывания и некое описание действия. допустим что или в настраиваемом пользователем условии или действии возможны ошибки времени выполнения. соотвественно есть процедура ApplayCustomsTriggers, внутри обработка каждого триггера. и вот подошли к основному вопросу — внутри ApplayCustomsTriggers надо ловить ошибку обработки каждого триггера и её поднимать выше (тк это по сути и есть основное исключение), но к информации имеющейся в исключении нужно присобачить доп. информацию, а именно — в каком конкретно тригере это случилось.
подобных примеров можно найти много, главная суть — поднимаемое выше исключение необходимости менять нет (ибо оно и несёт основную инфу об ошибке и именно с ним и надо разбираться выше) но к нему хочется добавить дополнительную тех. инфу...
Отредактировано 27.12.2018 19:29 MadHuman . Предыдущая версия .
Re[3]: и снова про исключения
От: MozgC США http://nightcoder.livejournal.com
Дата: 27.12.18 20:31
Оценка:
Здравствуйте, MadHuman, Вы писали:

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


А из stack trace будет непонятно в каком тригере это случилось? Какую еще дополнительную тех инфу надо добавлять?
Как вариант, можно залогировать с дополнительной инфой и пустить дальше, типа

catch(Exception ex)
{
  Log.Error("Trigger X failed bla bla bla", ex);
  throw;
}


Ну т.е. у нас такие основные варианты:

1) Ловить наверху. Точно ли недостаточно будет данных чтобы потом разобраться в проблеме?
2) Ловить раньше, логировать, отправлять наверх (код выше). В этом варианте возможно повторное логирование, т.е. залогируем и внизу и вверху, что не очень хорошо.
3) Ловить раньше, добавлять инфу в словарь ex.Data, отправлять наверх
4) Ловить раньше, оборачивать в новое исключение, отправлять наверх. Но не люблю без реальной надобности городить новые типы исключений.

В общем больше мне симпатизируют варианты 1 и 3. Варианты 2 и 4 — больше по ситуации, когда именно они подходят лучше.

Еще вариант, это ловить наверху, но во многих местах в коде логировать что происходит. Типа вначале метода можно сделать
Log.Info("Starting trigger X bla bla bla.");

Тогда потом если поднять логи, то можно будет понять после чего произошло исключение.
Re[3]: и снова про исключения
От: fmiracle  
Дата: 28.12.18 06:38
Оценка:
Здравствуйте, MadHuman, Вы писали:

MH>внутри ApplayCustomsTriggers надо ловить ошибку обработки каждого триггера и её поднимать выше (тк это по сути и есть основное исключение), но к информации имеющейся в исключении нужно присобачить доп. информацию, а именно — в каком конкретно тригере это случилось.

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

Если тебя интересует случай "Ошибка исполнения триггера", то и надо вводить новое TriggerExecutionErrorException, в которое добавлять всю нужную информацию о сломавшемся триггере и вкладывать исходное исключение как InnerException.

Пойми, в коде выше никто не будет ловить FormatException или NullReferenceException чтобы с ними что-то специальное сделать. Их даже пользователю нельзя нормально показать кроме-как "Произошла неизвестная ошибка", можно только в лог записать и все. Собственное TriggerExecutionErrorException, обернувшее FormatException, тоже можно записать в лог — и там будут и добавленные тобой детали, и вся информация о вложенном исключении, что позволит разобраться разработчику. С другой стороны, с ним уже пользователю можно показать (не обязательно ui, можно и почтой отправить) сообщение вида "Сбой выполнения пользовательского триггера '{Название триггера из исключения}'" (и возможно еще какая-то детализация про ошибку), что уже будет ему понятнее, раз это пользовательские триггеры.
Отредактировано 28.12.2018 6:46 fmiracle . Предыдущая версия .
Re[4]: и снова про исключения
От: MadHuman Россия  
Дата: 28.12.18 08:02
Оценка:
Здравствуйте, MozgC, Вы писали:


MC>А из stack trace будет непонятно в каком тригере это случилось? Какую еще дополнительную тех инфу надо добавлять?

в том-то и проблема что из стэк-трэйс это будет непонятно. в стэк-трэйс мы увидим ApplayCustomsTrigger, а она внутри себя в цикле и крутит триггеры.

MC>Ну т.е. у нас такие основные варианты:

MC>1) Ловить наверху. Точно ли недостаточно будет данных чтобы потом разобраться в проблеме?
да, в этом и проблема. мы не будем знать точно в каком триггере это произошло. только гадать по косвенным признакам, что увеличивает время понимания проблемы.

MC>2) Ловить раньше, логировать, отправлять наверх (код выше). В этом варианте возможно повторное логирование, т.е. залогируем и внизу и вверху, что не очень хорошо.

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

MC>3) Ловить раньше, добавлять инфу в словарь ex.Data, отправлять наверх

вот этот вариант мне нравится больше. но вот как-то мало практик о нём слышал.

MC>4) Ловить раньше, оборачивать в новое исключение, отправлять наверх. Но не люблю без реальной надобности городить новые типы исключений.

да, тоже не люблю городить новые типы исключений, особенно когда добавляемая инфа полезная, но не заслуживает статуса аж отдельного вида исключения.
всё таки, это видится доп. инфой а не спец. видом исключения.
Re[4]: и снова про исключения
От: MadHuman Россия  
Дата: 28.12.18 08:15
Оценка:
Здравствуйте, fmiracle, Вы писали:

F>Если тебя интересует случай "Ошибка исполнения триггера", то и надо вводить новое TriggerExecutionErrorException, в которое добавлять всю нужную информацию о сломавшемся триггере и вкладывать исходное исключение как InnerException.

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

F>Пойми, в коде выше никто не будет ловить FormatException или NullReferenceException чтобы с ними что-то специальное сделать. Их даже пользователю нельзя нормально показать кроме-как "Произошла неизвестная ошибка", можно только в лог записать и все.

это да.

F>Собственное TriggerExecutionErrorException, обернувшее FormatException, тоже можно записать в лог — и там будут и добавленные тобой детали, и вся информация о вложенном исключении, что позволит разобраться разработчику. С другой стороны, с ним уже пользователю можно показать (не обязательно ui, можно и почтой отправить) сообщение вида "Сбой выполнения пользовательского триггера '{Название триггера из исключения}'" (и возможно еще какая-то детализация про ошибку), что уже будет ему понятнее, раз это пользовательские триггеры.


не совсем так. в пользовательском триггере, может быть логика генерирующая прикладную ошибку, как раз для показа пользователю. поэтому обёртывание в TriggerExecutionErrorException маскирует её, и показ общего сообщения "Сбой выполнения пользовательского триггера" вариант плохой (при условии что исходная ошибка как раз для пользователя).
а может быть и не прикладная, а run-time ошибка. и вот для неё особенно полезно знать в каком триггере она произошла.
то есть, есть конфликт — с одной стороны лучше не менять тип исключения. тогда пользовательская ошибка уйдёт наверх и будет как надо показана.
если ошибка не пользовательская — то по сути надо добавить инфу в каком триггере случилось основная ошибка. и делать это путём ввода нового TriggerExecutionErrorException — замаскирует пользовательскую ошибку.

без перегенерации ошибки нового типа TriggerExecutionErrorException, эти задачи КМК решаются проще..
Re: и снова про исключения
От: Aquilaware  
Дата: 29.12.18 20:06
Оценка:
Здравствуйте, MadHuman, Вы писали:

MH>Господа, кто на практике к чему пришел в подобных случаях?


Вариант А. Либо вообще не ловить/не перебрасывать когда ничего специального делать не надо. Пусть идет наверх.

Вас смущает, как это показать пользователю. Тут несколько одновремменых подходов:

1) Визуализатор ошибок. Специальная хрень, которая берет на вход исключение и пишет очень понятную строку. Она же разворачивает вложенные исключения. Например:

Ошибка: Произошла ошибка обращения к БД --> Колонка "Microcosm" уже существует.

2) Если нужно придать доп. смысл или пояснение, то используйте ваш вариант А. Например, можно сделать акцент:

Ошибка: Инициализация системы "Одуванчик" завершилась неудачей --> Произошла ошибка обращения к БД --> Колонка "Microcosm" уже существует.

3) Используйте спец коды для частых публичных ошибок. В Data можно проставить код, например так: exception.Data["Code"] = "ОД1298". Визуализатор может потом выцыплять код (если есть) и делать так:

Ошибка ОД1298: Инициализация системы "Одуванчик" завершилась неудачей --> Произошла ошибка обращения к БД --> Колонка "Microcosm" уже существует.

Зная код ошибки, пользователь сможет поискать в интернете. Вы также можете ссылаться на ошибки по кодам в своей доке, даже сдеоать глоссарий ошибок можно.
Re: и снова про исключения
От: Mystic Artifact  
Дата: 30.12.18 22:00
Оценка:
Здравствуйте, MadHuman, Вы писали:

Я предпочитаю такую логику: покуда специальная обработка исключения (в методе A) просто прерывает исполнение — по возможности (!) следует избегать какой-либо их обработки и перезаворачиваний. И соответственно выплевывать оригинальное исключение, если это возможно. Благодаря этому мы не пишем никаких лишних строк.

Обратная цена этому — мы говорим клиенту: мы не знаем точно, но какие-то исключения мы кидаем.

На практике, это абсолютно не важно, т.к. реальные обработчики будут ловить или абсолютно все исключения (допустим для логирования), или что-то специфичное, задокументированное (например IO/FileNotFound).

Все это балансируется тем фактом, что в любой системе исключений — всегда может что-нибудь вылезти, даже, то, что теоретически не возможно. Сбойжелеза вполне способен спровоцировать AV, например. И именно поэтому, клиент всегда должен быть готов к нештатной ситуации любого вида. Под готовностью, я НЕ подразумеваю заворачивать все и вся в трай кэтчи, а наоборот, или давать упасть совсем, если это допустимо, или же ловить и предпринимать соответствующие адекватные меры.

На практике 2: все вышесказанное в двух словах: избегать try/catch везде, где это возможно.
Re: и снова про исключения
От: Sinatr Германия  
Дата: 02.01.19 09:06
Оценка:
Здравствуйте, MadHuman, Вы писали:

MH>Допустим вызываем MethodA (который в свою очередь может вызвать MethodB и ещё ряд подобных, каждый из которых в свою очередь тоже что-то вызывает) и вызов закончился ошибкой.

MH>Возможные подходы по обработке/генерации исключений:

Слишком абстрактно. Не получится все исключения обрабатывать/перебрасывать одинаково.

MH>чем не нравится а. — верхнее исключение как-бы маскирует базовое и надо точно понимать ситуацию.


Есть очень простое проавило — всегда смотрите InnerException. По цепочке InnerException можно дойти до точного места ошибки, ничего не потеряв, если соблюдать этот принцип.

MH>б. тоже, но перегенерим исходное исключение, добавив в исходный инстанс исключения в Data эту доп. информацию.


Что вы имели в виду под "исходным исключением"? Такой же тип? Без проблем, усли вы вызвали метод, который кидает InvalidOperationException и хотите "скрыть" от вызывающего кода детали, то да, если этот тип подходит, можно выкинуть свой InvalidOperationException, с более подходящим сообщением, засунув первое в InnerException.

Data никогда не использовал, и похоже, не я один. Очень похоже что Data используется в виде cвоеобразного Tag, чтобы добавлять пользовательскую информацию. Хмм, у меня лично всегда были проблемы с параметрами метода, когда просматриваешь логи. Решение — включать параметры в само сообщение:
            // version check - skip unknown versions
            if (Version?.StartsWith("1.") != true)
                throw new InvalidOperationException($"Unknown config version: {Version}");
Похоже, что передавать все параметры (а также локальные переменные и возможно даже поля, для целостности картины) в Data и кастомизировать логгер — другой вариант. Не уверен, какой из них более правильный или требует меньше работы. Опять же, зависит от контекста, в каком-то конкретном случае что-то подойдет лучше.
---
ПроГLамеры объединяйтесь..
Re: и снова про исключения
От: AlexRK  
Дата: 02.01.19 09:19
Оценка: 1 (1)
Здравствуйте, MadHuman, Вы писали:

MH>Господа, кто на практике к чему пришел в подобных случаях?


Стараюсь минимизировать число любых возбуждаемых вручную исключений, кидать их только в случае фатальных ошибок. Никаких "слоев" исключений нет, обработка в подавляющем большинстве случаев однотипная — запись в лог и абстрактное сообщение юзеру, там, где это уместно.
Re: и снова про исключения
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 14.01.19 21:03
Оценка: +1
Здравствуйте, MadHuman, Вы писали:

MH>а. в MethodA делаем кэтч и генерим новое исключение (возможно спец. класса) с передачей в качестве InnerException исходного исключения.

MH>особенно такое имеет смысл если с уровня MethodA надо в новое исключение включить какую-то доп. информацию. Например о параметре с которым вызван метод или о том, на какой фазе процесса упали и тп.

MH>б. тоже, но перегенерим исходное исключение, добавив в исходный инстанс исключения в Data эту доп. информацию.


в. Вообще не трогаем исключения без лишней необходимости.

MH>особенно эта неоднозначность проявляется при построении какой-то финальной обработки ошибки (например веб-реквеста), когда хз что выдать юзеру — либо верхнее, либо вложенное.


В случае веб-реквеста пишется специальный мидлвер, который знает в лицо некоторое количество well known исключений — UnauthorizedAccessException, NotFoundException, ArgumentException и т.п., и заворачивает их в соответствующие HTTP респонсы с error info в теле. А если не знает — вертает 500 и, при соотв. настройках, в теле json с инфой об исключении.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
AVK Blog
Re[2]: и снова про исключения
От: MadHuman Россия  
Дата: 18.01.19 10:18
Оценка:
Здравствуйте, AndrewVK, Вы писали:



AVK>в. Вообще не трогаем исключения без лишней необходимости.

А если для целей упрощения последующих разборов причин проблемы нужна доп. техническая информация из конкретного метода через который исключение поднимается... как быть? не трогать?
в лог писать не предлагать — способ понятный, но менее удобный.


MH>>особенно эта неоднозначность проявляется при построении какой-то финальной обработки ошибки (например веб-реквеста), когда хз что выдать юзеру — либо верхнее, либо вложенное.

AVK>В случае веб-реквеста пишется специальный мидлвер, который знает в лицо некоторое количество well known исключений — UnauthorizedAccessException, NotFoundException, ArgumentException и т.п., и заворачивает их в соответствующие HTTP респонсы с error info в теле. А если не знает — вертает 500 и, при соотв. настройках, в теле json с инфой об исключении.
Всё так, но:
А если исключение которое обработчик знает (пусть например UnauthorizedAccessException) завернуто где-то в глубине цепочки исключений (в InnerException-ах)? смотреть поверхнему (и выдать что-то невнятное) или всё таки идти в глубь?
А если так случилось что в цепочке исключений есть несколько которые обработчик "знает"? возникает конфликт и неясно как быть.. какое из них считать более "главным"?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.