.NET: Избежание пользовательских исключений за счет использования существующих типов, но какие? - PullRequest
4 голосов
/ 18 марта 2011

Рассмотрим следующий код (ASP.NET/C#):

private void Application_Start(object sender, EventArgs e)
{
    if (!SetupHelper.SetUp())
    {
        throw new ShitHitFanException();
    }
}

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

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

Брэд Абрамс опубликовал эту статью, в которой перечислены некоторые из доступных типов исключений .Я говорю несколько, потому что статья написана в 2005 году, и, хотя я стараюсь идти в ногу со временем, это более чем правдоподобное предположение, что в будущие версии фреймворка было добавлено больше, о которых я до сих пор не знаю.Конечно, Visual Studio предоставляет хорошо отформатированный, прокручиваемый список исключений с помощью Intellisense - но даже анализируя их, я не нахожу ни одного, который может показаться достаточным для этой ситуации ...

ApplicationException: ... при возникновении нефатальной ошибки приложения

Имя кажется разумным, но ошибка совершенно определенно фатальная - приложение не работает.

ExecutionEngineException: ... когда есть внутренняя ошибка в механизме исполнения CLR

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

HttpApplicationException: ... при возникновении ошибки при обработке HTTP-запроса

Итак, мы запускаем приложение ASP.NET!Но мы также просто потянем за соломинку.

InvalidOperationException: ... когда вызов недопустим для текущего состояния экземпляра

Это не правильно, но я добавляю его в список «возможно, если вы положите пистолет мне в голову, да».

OperationCanceledException: ...при отмене операции, которую выполнял поток

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

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

Ответы [ 9 ]

2 голосов
/ 18 марта 2011

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

Я бы сделал одну из двух вещей. Первым делом нужно изменить метод .SetUp так, чтобы он возвращал что-то, что давало мне указание на то, почему это не удалось

Другим способом было бы, чтобы метод .SetUp сам выдавал исключение и решал в вашем событии Application_Start, позволять ли ему всплывать в ОС или исправлять проблему, и пытаться снова.

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

Если по какой-то причине вы не можете сделать это, то есть что-то не получилось, и у вас нет идеи, почему тогда я бы создал новое исключение с сообщением по умолчанию: «Произошла ошибка в {0}», где {0 } это модуль или метод, который, по крайней мере, дает мне представление о том, с чего начать.

Между прочим, фатальный обычно означает полный сбой CLR, который немного отличается от невозможности обработки вашего приложения в зависимости от его состояния.

2 голосов
/ 18 марта 2011

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

Я обычно подхожу к этой проблеме с помощью контрактов.

private void Application_Start(object sender, EventArgs e)
{
  // If this doesn't work why keep executing???
  Contract.Requires(SetupHelper.SetUp());
  ...
}

Примечание. В зависимости от ограничений проекта я буду использовать .Net-контракты из BCL или мой собственный вариант, например,

public static class Contracts {
  private sealed class ContractException : Exception { ... }
  public static void Requires(bool value) { 
    if (!value) {
      throw new ContractException(); 
    }
  }
}

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

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

Вам нужно прочитать эти статьи на MSDN:

Для вашего примера кода выбрасывание InvalidOperationException является наиболее подходящим: если инициализация не удалась, вы, скорее всего, находитесь в «недопустимом состоянии».

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

SystemException должен был быть корнем системных (например, CLR) исключений, а ApplicationException корень пользовательских / прикладных исключений. Они выяснили, что на практике это не принесло особой пользы: из статьи «Лучшие практики» выше:

Для большинства приложений выведите пользовательские исключения из класса Exception. Первоначально считалось, что пользовательские исключения должны быть производными от класса ApplicationException; однако на практике это не было найдено, чтобы добавить значительную ценность.

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

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

using System;
using System.Runtime.Serialization;
using System.Security.Permissions;

namespace sandbox
{
    /// <summary>
    ///   <para>
    ///     Minimum scaffolding required for a custom exception.
    ///   </para>
    ///   <para>
    ///     Consider providing custom properties/fields to provide context and semantic details
    ///     regarding the exception instance. Alternatively, use the dictionary provided by the
    ///     base class for that purpose. See <see cref="System.Exception.Data"/> for more details.
    ///   </para>
    /// </summary>
    [Serializable]
    class MyCustomException : Exception
    {

        #region public constructors

        public MyCustomException() : base()
        {
            // set Message to the default localized message for your exception here
            // The base constructor for System.Exception sets InnerException to null as well as setting a default message.
            return ;
        }

        public MyCustomException( string message ) : base( message )
        {
            return ;
        }

        public MyCustomException( string message , Exception innerException ) : base( message , innerException )
        {
            return ;
        }

        #endregion public constructors

        #region serialization-related constructor/method

        /// <summary>
        /// protected constructor. Used for deserialization
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        protected MyCustomException( SerializationInfo info , StreamingContext context ) : base( info , context )
        {
            // do other deserialization-related initialization here (e.g. set your object properties from info and context).
            return ;
        }

        /// <summary>
        /// Perform custom serialization
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        [SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter=true)]
        public override void GetObjectData( SerializationInfo info, StreamingContext context ) 
        {
            // do any serialization work required by your custom exception here
            base.GetObjectData( info, context );
            return ;
        }

        #endregion serialization-related constructor/method

    }
}
1 голос
/ 18 марта 2011

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

В зависимости от того, почему ShitHitTheFan, я бы сказал, что InvalidOperationException или ApplicationException имеют достаточный смысл.Добавление подробного сообщения может показаться достаточным для объяснения причины.Я склонен быть прагматичным, поэтому тот факт, что документация указывает на то, что исключения ApplicationException не являются фатальными, не имеет для меня никакого значения.Любое исключение может быть фатальным, если оно не перехвачено ...

В зависимости от имеющейся у вас инфраструктуры отчетности / ведения журнала сбоев может быть также полезно заполнить свойство Exception.Data информацией о состоянии, которая быпомочь вам в отладке проблемы.

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

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

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

private void Application_Start(object sender, EventArgs e)
{
    try
    {
        SetupHelper.SetUp()
        //Awesome
    }
    catch(InvalidDisplaySettingsException ex)
    {
        //Do Something based on the type of the exception
    }
    catch(DatabaseConfigurationException ex)
    {
        //Do something based on the type of the exception
    }
    catch(Exception ex)
    {
        //Now sh.. hit the fan
    }
}

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

0 голосов
/ 18 марта 2011

Ваша проблема здесь в том, что метод Setup использует возвращаемое значение вместо исключения для сообщения об ошибке.

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

  • ConfigurationErrorsException если установка не удалась из-за отсутствия или неверной информации о конфигурации в файле конфигурации вашего приложения.

  • SqlException, если во время установки произошел сбой при доступе к базе данных SQL Server.

  • ... и т.д ...

0 голосов
/ 18 марта 2011

Я бы бросил InvalidOperationException. Поскольку это исключение не предназначено для перехвата, вам не нужно беспокоиться о типе, поэтому важно сообщение. Я также добавил бы это в методе SetupUp вместо возврата Boolean.

0 голосов
/ 18 марта 2011

Я бы посчитал, что большинство исключений .net относительно бесполезны для выброса в любых обстоятельствах, в которых вы можете ожидать, что они будут пойманы в чем-либо, кроме ситуации, охватывающей все.Проблема в том, что даже если состояние вашего объекта может быть известно во всех случаях, когда вы генерируете TimeoutException, это не означает, что состояние вашего объекта будет известно, если какой-то код, который вы вызываете, генерирует TimeoutException и вы 'просто позволяю ему пузыриться в стеке.

Я бы сказал, что лучше иметь больше разных типов исключений, чем использовать один тип исключения для нескольких разных целей.Если необходимость скопировать шаблон исключения становится раздражающей, вот хитрость: определите тип исключения с одним или несколькими общими параметрами.Обратите внимание, что MisterDissapointedException не будет наследоваться от MisterDissapointedException , но вы можете определить MisterDissapointedExceptionэто будет наследоваться от MisterDissapointedException .

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

0 голосов
/ 18 марта 2011

Invalidoperationexception, кажется, подходит, так как его цель - предупредить о том, что пошло не так и не может быть восстановлено.

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