Какая должна быть лучшая стратегия обработки исключений

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

UI → Метод1 → Метод2 → Метод3

Я хочу показать сообщение об ошибке пользователю, если какое-либо исключение возникает в любом из методов.

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

Помимо исключения Throwing и ловить его у вызывающего есть ли лучший способ справиться с этим?

Я не хочу использовать соглашение С++, где в качестве результата возвращается целое число.

Ответ 1

Учитывая ваш пример UI → Method1 → Method2 → Method3, я бы удостоверился, что пользовательский интерфейс имеет try/catch, но тогда ни один из других методов не должен перехватывать исключения, если они не смогут обрабатывать их без повторно метание. И даже если они справятся с этим исключением, вы должны задать вопрос, должно ли это исключение происходить в первую очередь. Если вы обрабатываете исключение и идете по своему весёлом пути, то это исключение является частью вашего нормального процесса, который является серьезным запахом кода.

Вот мои рекомендации:

1) Поместите код обработки исключений во все ваши события пользовательского интерфейса, а затем приведите фактическое действие к другому методу. Не разбрасывайте код обработки исключений повсюду. Это неудобно и делает код трудным для чтения.

protected void Button1_Click(object sender, EventArgs e) {
   try {
      DoSomething();
   }
   catch Exception e {
      HandleError(e);
   }
}

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

try {
   DoSomething();
}
catch Exception e {
   throw e; // don't rethrow!!!
}

Если вы не собираетесь обрабатывать исключение, не поймайте его. Или используйте голый бросок, как это:

try {
   DoSomething();
}
catch SomeException e {
   HandleException(e);
}
catch {
   throw ; // keep my stack trace.
}

3) Бросьте исключения только в исключительных обстоятельствах. Не используйте блоки try/catch как часть обычного процесса.

4) Если вы собираетесь выбросить исключение, никогда не бросайте исключение System.Exception. Выведите объект исключения и выбросите его. Я часто просто общий класс BusinessException. Таким образом, пользователи вашего кода могут определить, какие исключения вы составили и какие из них связаны с системой/средой.

5) Бросьте ArgumentException, ArgumentNullException или ArgumentOutOfRangeException, если вызывающий объект нарушает контракт (предварительные условия) вашего метода. Если вы поймаете одну из них, это ошибка программирования. Пользователь никогда не должен видеть эти исключения.

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

Ответ 2

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

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

Ответ 3

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

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

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

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

Если вы хотите улучшить сообщение об ошибке, вы будете ловить, обернуть в новое исключение и бросить. Например, вы можете обернуть общее FileNotFoundException при чтении файла в новом исключении с более подробным сообщением пользователю о том, какой файл не удалось найти.

Ответ 4

Что бы вы ни решили - быть последовательным. Через 30 дней вам (или другому разработчику) нужно будет понять ваш код.

Кроме того, как Скотт Гензельман любит цитировать на своем подкасте (и я думаю, что это может быть в книге Beautiful Code) -

Все проблемы в информатике могут быть решены другим уровнем косвенности ", - знаменитая цитата, приписываемая Батлеру Лампсону, ученым, который в 1972 году представил современный персональный компьютер.

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

public class MyException : Exception
{
   private MyException(Exception ex, string msg) {
       this.Message = msg;
       this.InnerException = ex;
   }

   internal static MyException GetSomethingBadHappened(Exception ex, string someInfo) {
      return new MyException(ex, someInfo);
   }
}

Ответ 5

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

public enum AddCustomerResult
{
    Success,
    CustomerAlreadyExists,
    CustomerPreviouslyRetired
}

Вы все равно получите исключенные исключения, ошибки, недоступные для базы данных, и тому подобное; но вы будете тестировать ожидаемые (хотя, возможно, редкие) случаи и указывать успех/неудачу/etc по мере необходимости.

Это всего лишь один метод, который хорошо работает для меня.

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

Например, в DAL вы захотите поймать исключения и сбросить их как внутреннее исключение в DataAccessException; на веб-сервисе вы обернете все свои методы в обработчиках исключений, чтобы восстановить его с помощью MyWebServiceException. В пользовательском интерфейсе (который перемещается из приложения на экран пользователя) вы хотите поймать, зайти в журнал и дать им приятное сообщение. Насколько я не вижу причин, чтобы поймать или реконструировать где-нибудь еще.

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

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

Надеюсь, что это поможет!

Ответ 6

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

Ответ 7

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

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

После завершения операции или уровня пользовательского интерфейса можно проверить журнал ошибок и представить сообщение "Следующие ошибки произошли:...".