С#: перетасовка пользовательских исключений
Я прочитал несколько других вопросов, касающихся практики обработки исключений С#, но никто, кажется, не спрашивает, что я ищу.
Если я реализую собственное собственное исключение для определенного класса или набора классов. Должны ли все ошибки, относящиеся к этим классам, инкапсулироваться в мое исключение, используя внутреннее исключение, или я должен позволить им провалиться?
Я думал, что было бы лучше поймать все исключения, чтобы исключение можно было сразу узнать из моего источника. Я все еще передаю исходное исключение как внутреннее исключение. С другой стороны, я думал, что было бы лишним отказаться от исключения.
Исключение:
class FooException : Exception
{
//...
}
Вариант 1: Foo замаскирует все Исключения:
class Foo
{
DoSomething(int param)
{
try
{
if (/*Something Bad*/)
{
//violates business logic etc...
throw new FooException("Reason...");
}
//...
//something that might throw an exception
}
catch (FooException ex)
{
throw;
}
catch (Exception ex)
{
throw new FooException("Inner Exception", ex);
}
}
}
Вариант 2: Foo выбрасывает определенные FooExceptions, но позволяет пропустить другие Исключения:
class Foo
{
DoSomething(int param)
{
if (/*Something Bad*/)
{
//violates business logic etc...
throw new FooException("Reason...");
}
//...
//something that might throw an exception and not caught
}
}
Ответы
Ответ 1
Основываясь на моем опыте работы с библиотеками, вы должны обернуть все (что вы можете ожидать) в FooException
по нескольким причинам:
-
Люди знают, что это произошло из ваших занятий или, по крайней мере, с их использованием. Если они видят FileNotFoundException
, они могут смотреть на все это. Вы помогаете им сузить его. (Теперь я понимаю, что трассировка стека служит для этой цели, поэтому, возможно, вы можете игнорировать эту точку.)
-
Вы можете предоставить больше контекста. Обернув FNF своим собственным исключением, вы можете сказать: "Я пытался загрузить этот файл для этой цели и не смог его найти. Это указывает на возможные правильные решения.
-
Ваша библиотека может корректно обрабатывать очистку. Если вы позволите пузырю исключения, вы заставляете пользователя очищать. Если вы правильно инкапсулировали то, что делаете, тогда они не имеют понятия, как справиться с ситуацией!
Не забудьте только обернуть исключения, которые вы можете ожидать, например FileNotFound
. Не просто оберните Exception
и надейтесь на лучшее.
Ответ 2
Посмотрите на лучшие методы MSDN.
Предположим, что вместо throw ex
использовать throw
, если вы хотите повторно выбрасывать пойманные исключения, потому что таким образом исходная стоп-таблица сохраняется (номера строк и т.д.).
Ответ 3
Я всегда добавляю несколько свойств при создании настраиваемого исключения. Одним из них является имя пользователя или идентификатор. Я добавляю свойство DisplayMessage для переноса текста, который будет отображаться для пользователя. Затем я использую свойство Message для передачи технических данных в журнал.
Я улавливаю каждую ошибку в Уровне доступа к данным на уровне, где я все еще могу захватить имя хранимой процедуры и значения переданных параметров. Или встроенный SQL. Возможно, имя базы данных или частичное соединение (без учетных данных, пожалуйста). Они могут отображаться в сообщении или в собственном новом пользовательском свойстве DatabaseInfo.
Для веб-страниц я использую одно и то же настраиваемое исключение. Я вложу в свойство Message информацию о форме - то, что пользователь ввел в каждый элемент управления вводом данных на веб-странице, идентификатор редактируемого элемента (клиент, продукт, сотрудник и т.д.) И действие пользователя когда происходило исключение.
Итак, моя стратегия по вашему вопросу: только поймать, когда я могу что-то сделать об исключении. И довольно часто все, что я могу сделать, это регистрировать детали. Таким образом, я только поймаю в точке, где эти данные доступны, а затем реконструируют, чтобы исключить возможность появления пузыря в пользовательском интерфейсе. И я сохраняю исходное исключение в своем обычном исключении.
Ответ 4
Цель пользовательских исключений - предоставить подробную, контекстуальную информацию для stacktrace, чтобы помочь в отладке. Вариант 1 лучше, потому что без него вы не получите "происхождение" исключения, если оно произошло "ниже" в стеке.
Ответ 5
если вы запустите фрагмент кода для "Исключения" в Visual Studio, у вас есть шаблон написания исключения из практики.
Ответ 6
Примечание
Вариант 1: ваш throw new FooException("Reason...");
не будет пойман, поскольку он находится вне блока try/catch
- Вы должны только перехватывать исключения, которые хотите обработать.
- Если вы не добавляете никаких дополнительных данных в исключение, чем используйте
throw;
, так как он не будет убивать ваш стек. В Варианте 2 вы все равно можете выполнить некоторую обработку внутри catch и просто вызвать throw;
, чтобы восстановить исходное исключение с исходным стеком.
Ответ 7
Самое главное, что код, который должен знать при обнаружении исключения, который, к сожалению, полностью отсутствует в объекте Exception, является состоянием системы относительно того, что он должен "(предположительно исключение было выброшено, потому что было что-то неправильно). Если в методе LoadDocument возникает ошибка, предположительно, документ не загружался успешно, но есть как минимум два возможных состояния системы:
- Состояние системы может быть таким, как если бы загрузка никогда не выполнялась. В этом случае было бы совершенно правильно, если приложение будет продолжать, если оно может сделать это без загруженного документа.
- Состояние системы может быть достаточно искажено, что наилучшим способом было бы сохранить то, что можно сохранить в файлах восстановления (не заменяйте хорошие файлы пользователя с поврежденными данными) и завершите работу.
Очевидно, что между этими крайностями часто бывают другие возможные состояния. Я бы предположил, что нужно стремиться к созданию специального исключения, которое явно указывает, что состояние №1 существует и, возможно, одно для # 2, если это может привести к непредвиденным, но неизбежным обстоятельствам. Любые исключения, которые происходят и приводят к состоянию # 1, должны быть обернуты в объект исключения, указывающий состояние # 1. Если исключения могут происходить таким образом, что состояние системы может быть скомпрометировано, их следует либо обернуть как # 2, либо разрешить перколяцию.
Ответ 8
Вариант 2 лучше. Я считаю, что лучшая практика заключается в том, чтобы перехватывать исключения только тогда, когда вы планируете что-то делать с исключением.
В этом случае вариант 1 просто обертывает исключение с вашим собственным исключением. Это не добавляет никакой ценности, и пользователи вашего класса больше не могут просто перехватывать ArgumentException, например, им также нужно поймать FooException, а затем разобрать внутреннее исключение. Если внутреннее исключение не является исключением, они могут сделать что-то полезное, и им нужно будет сбросить.