Обработка исключений в n-уровневых приложениях? - PullRequest
30 голосов
/ 06 ноября 2010

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

  • Где вы должны разместить try/catch блоки?
  • Где вы должны реализовать регистрацию?
  • Существует ли предлагаемый шаблон для управления исключениями в n-уровневых приложениях?

Рассмотрим простой пример.Предположим, у вас есть пользовательский интерфейс, который вызывает бизнес-уровень, который вызывает уровень данных:

//UI
protected void ButtonClick_GetObject(object sender, EventArgs e) 
{
    try {
        MyObj obj = Business.GetObj();
    }
    catch (Exception ex) {
        Logger.Log(ex); //should the logging happen here, or at source?
        MessageBox.Show("An error occurred");
    }
}

//Business
public MyObj GetObj()
{
    //is this try/catch block redundant?  
    try {
        MyObj obj = DAL.GetObj();
    }
    catch (Exception ex) {
        throw new Exception("A DAL Exception occurred", ex);
    }
}

//DAL
public MyObj GetObj()
{
    //Or is this try/catch block redundant? 
    try {
        //connect to database, get object
    }
    catch (SqlException ex) {
        throw new Exception("A SQLException occurred", ex);
    }
}

Какую критику вы бы высказали в связи с обработкой исключений, приведенной выше?

Ответы [ 5 ]

19 голосов
/ 06 ноября 2010

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

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

  1. Исключение фактически обработано. Например. соединение не удалось, но уровень повторяет попытку.
  2. Исключение перебрасывается с дополнительной информацией (которая еще не доступна при просмотре стека). Например. DAL может сообщить, к какой БД он пытался подключиться, о чем SqlException не скажет.
  3. Исключение переводится в более общее исключение , которое является частью интерфейса для этого уровня и может (или не может) обрабатываться выше. Например. DAL может перехватить ошибки соединения и выдать DatabaseUnavailableException. BL может игнорировать это для операций, которые не являются критическими, или он может позволить ему распространяться на те, которые являются. Если BL поймал SqlException, вместо этого он был бы раскрыт в деталях реализации DAL. Вместо этого возможность броска DatabaseUnavailableException является частью интерфейса DAL.

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

11 голосов
/ 07 ноября 2010

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

  • исключения должны захватываться только в слоях, которые могут реализовать логику их обработки.В большинстве случаев это происходит в самых верхних слоях.Как правило, перед реализацией перехвата внутри слоя спросите себя, зависит ли каким-либо образом логика какого-либо из верхних уровней от существования исключения.Например, на бизнес-уровне у вас может быть следующее правило: загружать данные из внешнего сервиса, если сервис доступен, если не загружать его из локального кэша.Если при вызове службы возникает исключение, вы можете перехватить и записать его на уровне BL, а затем вернуть данные из кэша.В этом случае верхний уровень пользовательского интерфейса не должен предпринимать никаких действий.Однако если произойдет сбой вызова службы и кэша, исключение придется перейти на уровень пользовательского интерфейса, чтобы пользователю могло отображаться сообщение об ошибке.
  • все исключения внутри приложения должны быть перехвачены, и если нет специальной логики для их обработки, они должны быть как минимум зарегистрированы.Конечно, это не означает, что вы должны обернуть код из всех методов в блоки try / catch.Вместо этого у любого типа приложения есть глобальный обработчик для необработанных исключений.Например, в приложениях Windows должно быть реализовано событие Application.ThreadException.В приложениях ASP .Net должен быть реализован обработчик событий Application_Error из global.asax.Эти места являются самыми верхними местами, где вы можете ловить исключения в своем коде.Во многих приложениях это будет то место, где вы поймаете большинство исключений, и помимо регистрации, здесь вы, вероятно, также реализуете общее и понятное окно сообщения об ошибке, которое будет представлено пользователю.
  • вы можете реализовать функцию try / finally там, где вам это нужно, без реализации блока catch.Блок перехвата не должен быть реализован, если вам не нужно реализовывать какую-либо логику обработки исключений.Например:
SqlConnection conn = null;  
try
{
    conn = new SqlConnection(connString);
    ...
}
// do not implement any catch in here. db exceptions logic should be implemented at upper levels
finally
{
    // finalization code that should always be executed.
    if(conn != null) conn.Dispose();
}
  • , если вам нужно перебросить исключение из блока catch, сделайте это, просто используя throw;.Это обеспечит сохранение трассировки стека.Использование throw ex; сбросит трассировку стека.
  • , если вам нужно перебросить исключение с другим типом, который будет иметь больше смысла для верхних уровней, включите исходное исключение в свойство InnerException недавно созданного исключения.
  • , если вам нужногенерировать исключения из вашего кода, использовать значимые типы исключений.
3 голосов
/ 06 ноября 2010

Первое, что нужно исправить, - это никогда не бросать общее Exception.

Второе, если нет действительно веской причины для исключения, просто наберите throw; вместо throw new...ваше предложение catch.

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

2 голосов
/ 06 ноября 2010

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

  • Исключения, которые должны произойти, только если ваш код неверен (или вы не понимаете, или у вас нетконтроль над ними):

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

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

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

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

  • Исключения, которые фактически вызваны пользователем:

Пример: Пользователь пытается обновить виджет foobar и присвоить ему новое имя, но виджет foobar уже существует с тем именем, которое он хочет.

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

  • Исключения, которые вы можете обработать:

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

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

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

0 голосов
/ 04 декабря 2010

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

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