Что вы думаете о лучшей практике «Не ловить неожиданные исключения»? - PullRequest
7 голосов
/ 25 февраля 2009

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

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

У вас огромный сервер. Миллион строк кода.

При запуске загружает всех клиентов в свой локальный кеш.

Для меня имеет смысл написать следующее:

           foreach (string CustomerID in Customers)
                try
                {
                    LoadCustomer(CustomerID);
                }
                catch (Exception ex) // blind catch of all exceptions
                {
                    // log the exception, and investigate later.

                }

Без слепой уловки, если не загрузить ни одного Клиента, просто произойдет сбой всего сервера.

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

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

Есть что-то, что я здесь пропускаю? Известны ли лучшие практики (кроме стратегии «никогда не поймать неожиданное исключение»?)

Лучше ли перехватывать исключение в методе LoadCustomer (), отбрасывать оттуда «CustomerLoadException» и перехватывать CustomerLoadException вместо всех исключений в вызывающем коде?

Ответы [ 14 ]

13 голосов
/ 25 февраля 2009

Это вопрос Надежность , которая продолжает работать даже в случае необъяснимых ошибок, против Корректность , предпочитая полный отказ, а не получение ненадежных результатов.

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

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

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

2 голосов
/ 25 февраля 2009

Я думаю, что это зависит от senario, но, как правило, я улавливаю только исключение, которое, как я ожидаю, происходит при повседневном использовании приложения и использую какой-либо неисследованный инструмент для составления отчетов и регистрации исключений, такой как мониторинг состояния ASP.Net. Однако, если это критическая часть приложения, в которой просто не может быть необработанного исключения, я перехватываю все исключения и снова сообщаю / регистрирую их.

2 голосов
/ 25 февраля 2009

«Есть ли что-то, что я здесь пропускаю?»

Да.

«Известны ли лучшие практики (кроме стратегии« никогда не ловить неожиданное исключение »?)»

Да. Инкапсуляция. Распределение ответственности. S. О. Л. И. Д. Принципы.

Лучше ли перехватывать исключение в методе LoadCustomer (), отбрасывать оттуда «CustomerLoadException» и перехватывать CustomerLoadException вместо всех исключений в вызывающем коде?

Да. Это инкапсулирует все, что может пойти не так в определении класса, которое предоставляет метод loadCustomer.

1 голос
/ 26 сентября 2009

Прекрасно ловить исключения, о которых вы знаете. Вы можете ожидать NullObjectException в вашем примере. Ловить это вполне разумно.

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

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

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

Нет ничего хорошего в том, чтобы поймать Exception.

1 голос
/ 25 февраля 2009

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

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

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

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

1 голос
/ 25 февраля 2009
Я определенно предпочитаю, чтобы мой сервер работал с небольшим неизвестным побочным эффектом на одного клиента, а не на весь сервер.

Откуда вы знаете, что ошибка, которая вызвала исключение в LoadCustomer (), больше ничего не скрывала? В целом, я думаю, я бы предпочел «регистрировать исключение, перебросить исключение на более высокий уровень» и там, вероятно, выйти.

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

1 голос
/ 25 февраля 2009

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

0 голосов
/ 16 октября 2011

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

В существующих системах обработки исключений мне бы не хватало одной функции - виртуального свойства IsResolved в Exception. После обработки блока «catch» система проверит, возвращает ли IsResolved false. Если он не равен NULL и возвращает true, будет вызываться исключение UnresolvedExceptionException с предыдущим исключением в качестве его InnerException (UnresolvedExceptionException.IsResolved будет вызывать метод IsResolved своего InnerException).

Большинство реализаций IsResolved будут проверять, был ли список делегатов функций, возвращающих Boolean, пустым; если нет, верните true, если любой из перечисленных делегатов вернет True. Если исключение подразумевает, что неразмещенный объект был поврежден, метод IsResolved мог возвратить флаг Disposed этого объекта. Если объект был уничтожен, его повреждение больше не будет актуальным. Если объект обернут в блок Using, такое поведение приведет к тому, что исключение будет просачиваться до точки, где объект был удален, но не дальше.

Действительно неприятные исключения, такие как OutOfMemoryException, могли бы сделать их метод IsResolved только счастливым явным вызовом чего-то вроде OutOfMemoryException.AcknowledgeOutOfMemoryCondition. Во многих других подпрограммах, которые генерируют исключения, метод IsResolved немедленно возвращает true (например, Dictionary.GetValue выбрасывает, потому что ключ не найден, тогда, что касается словаря, исключение будет разрешено, как только возникнет исключение) .

К сожалению, я не уверен, насколько хорошо такая схема может быть добавлена ​​к существующей платформе, такой как .net. Тем не менее, если кто-то разработает подобную систему в будущем, было бы полезно включить ее.

0 голосов
/ 25 февраля 2009

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

  1. Пессимистичный вид: показать исключение и сдаться.
  2. Оптимистичный взгляд: у вас есть уверенность, что вы уже поймали все критические исключения, поэтому вы поглощаете исключение и продолжаете (ON ERROR RESUME NEXT, кто-нибудь?)

В любом случае вы регистрируете исключение и регулярно проверяете журналы.

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

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

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

0 голосов
/ 25 февраля 2009

Меня поймали раньше, поймав общее «Исключение», а не индивидуальный тип. Это означает, что вы теряете некоторую информацию о том, что на самом деле пошло не так. Обычно вы хотите обрабатывать каждый тип исключений по-разному.

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

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