Подтвердить что ты не робот

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

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

  • Где вы можете разместить блоки 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);
    }
}

Какие критические замечания вы высказали бы в отношении обработки исключенных исключений?

спасибо

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

  • исключения должны попадать только в слои, которые могут реализовать логику их обработки. В большинстве случаев это происходит в верхних слоях. Как правило, перед реализацией catch внутри слоя спросите себя, не зависит ли логика любого из верхних уровней от существования исключения. Например, на уровне вашего бизнеса вы можете иметь следующее правило: загружать данные из внешней службы, если служба доступна, если не загружать ее из локального кеша. Если вызов службы выдает исключение, вы можете поймать и зарегистрировать его на уровне BL, а затем вернуть данные из кеша. В этом случае верхний слой пользовательского интерфейса не должен предпринимать никаких действий. Однако, если вызов службы и кэша завершится, исключение должно перейти на уровень пользовательского интерфейса, чтобы сообщение пользователя могло быть отображено сообщение об ошибке.
  • все исключения внутри приложения должны быть пойманы, и если для их обработки нет специальной логики, они должны быть зарегистрированы как минимум. Конечно, это не означает, что вам нужно обернуть код из всех методов в блоках try/catch. Вместо этого у любого типа приложения есть глобальный обработчик для исключенных исключений. Например, в приложениях Windows должно быть реализовано событие Application.ThreadException. В приложениях ASP.Net должен выполняться обработчик событий Application_Error из global.asax. Эти места являются самыми верхними местами, где вы можете перехватывать исключения в своем коде. Во многих приложениях это место, где вы будете улавливать большинство исключений и помимо ведения журнала, здесь вы, вероятно, также внесете общее и дружественное окно сообщений об ошибке, которое будет представлено пользователю.
  • вы можете реализовать функциональность try/finally, где она вам нужна, без реализации блока catch. Блок 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; будет reset трассировки стека.
  • если вам нужно перестроить исключение с другим типом, который будет иметь больше смысла для верхних уровней, включите исходное исключение в свойстве InnerException только что созданного исключения.
  • Если вам нужно перебрасывать исключения из вашего кода, используйте значащие типы исключений.

Ответ 3

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

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

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

Ответ 4

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 5

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