C #: создание пользовательских исключений Best Practices - PullRequest
47 голосов
/ 21 января 2011

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

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

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

Исключение:

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 выбрасывает определенные исключения FooException, но позволяет другим исключениям проходить через:

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 ]

54 голосов
/ 21 января 2011

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

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

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

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

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

18 голосов
/ 21 января 2011

Взгляните на это MSDN-best-Practices .

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

6 голосов
/ 21 января 2011

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

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

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

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

3 голосов
/ 21 января 2011

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

1 голос
/ 22 марта 2011

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

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

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

1 голос
/ 21 января 2011

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

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

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

0 голосов
/ 21 января 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...