C#: Выбрасывание Пользовательских Исключений

Я прочитал несколько других вопросов, касающихся практики обработки исключений C#, но никто не спрашивает, Что я ищу.

Если я реализую свое собственное исключение для определенного класса или набора классов. Должны ли все ошибки, относящиеся к этим классам, быть инкапсулированы в мое исключение с использованием внутреннего исключения или я должен позволить им провалиться?

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

исключения:

class FooException : Exception
{
    //...
}

Вариант 1: Фу encasulates все исключения:

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
    }
}

8 ответов


основываясь на моем опыте работы с библиотеками, вы должны обернуть все (что вы можете предвидеть) в FooException по нескольким причинам:

  1. люди знают, что это пришло из ваших классов, или, по крайней мере, их использование. Если они увидят FileNotFoundException они могут искать его повсюду. Ты помогаешь им сузить круг подозреваемых. (Теперь я понимаю, что трассировка стека служит этой цели, поэтому, возможно, вы можете игнорировать этот момент.)

  2. вы можете предоставить больше контекста. Обертывая FNF за вашим собственным исключением, вы можете сказать: "я пытался загрузить этот файл С этой целью, и не смог его найти. Это намекает на возможные правильные решения.

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

не забудьте только обернуть исключения, которые вы можете предвидеть, например FileNotFound. Не просто обернуть Exception и надеяться на лучшее.


посмотри MSDN-лучшие практики.

использовать throw вместо throw ex Если вы хотите повторно выбросить пойманные исключения, потому что на этом пути исходный stacktrace сохраняется (номера строк и т. д.).


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

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

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

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


целью пользовательских исключений является предоставление подробной контекстной информации в stacktrace для помощи в отладке. Вариант 1 лучше, потому что без него вы не получите "происхождение" исключения, если оно произошло "ниже" в стеке.


Если вы запустите фрагмент кода для "исключения" в Visual Studio, у вас есть шаблон хорошей практики написания исключений.


Примечание Вариант 1: Ваш throw new FooException("Reason..."); не будет пойман, так как он снаружи try / catch block

  1. вы должны только ловить исключения, которые вы хотите обработать.
  2. если вы не добавляете никаких дополнительных данных к исключению, чем использовать throw; так как это не убьет ваш стек. В Варианте 2 вы все еще можете выполнить некоторую обработку внутри catch и просто позвонить throw; перестроить исходное исключение с исходным стеком.

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

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

очевидно, что между этими крайностями часто будут другие возможные состояния. Я бы предложил, чтобы следует стремиться к тому, чтобы иметь пользовательское исключение, которое явно указывает на существование состояния #1, и, возможно, одно для #2, если его могут вызвать предсказуемые, но неизбежные обстоятельства. Любые исключения, которые происходят и приведут к состоянию #1, должны быть обернуты в объект исключения, указывающий состояние #1. Если исключения могут произойти таким образом, что состояние системы может быть скомпрометировано, их следует либо обернуть как #2, либо разрешить просачиваться.


2 является лучшей. Я считаю, что лучше всего ловить исключения только тогда, когда вы планируете что-то делать с исключением.

в этом случае Вариант 1 просто обертывает исключение с вашим собственным исключением. Он не добавляет никакого значения, и пользователи вашего класса больше не могут просто поймать ArgumentException, например, им также нужно поймать ваше исключение FooException, а затем выполнить синтаксический анализ внутреннего исключения. Если внутреннее исключение не является исключением, они могут сделать что-то полезное с ними придется переосмыслить.