где поставить try/catch в трехуровневой архитектуре

У меня есть трехуровневое веб-приложение. Я хочу использовать блок try catch на уровне бизнес-логики. Правильно ли использовать блок try/catch в бизнес-логике или мне нужно использовать его на моем уровне пользовательского интерфейса?

см. мой код для DAL.

Data Access Layer

#region Insert in to Logbook
public int Insert_LogBook(string Vehicle_Number, DateTime Vehicle_Booking_Date, TimeSpan Time_From, TimeSpan Time_To, int KM_Start, int KM_End, string Vehicle_Used_By, string Cost_Code, string Budget_Line, DateTime Entry_Date)
{
    try
    {
        SqlCommand com = new SqlCommand("Insert_LogBook", con);
        com.Parameters.Add("@Vehicle_Number", SqlDbType.NVarChar, 100).Value = Vehicle_Number;
        com.Parameters.Add("@Vehicle_Booking_Date", SqlDbType.DateTime).Value = Vehicle_Booking_Date;
        com.Parameters.Add("@Time_From", SqlDbType.Time).Value = Time_From;
        com.Parameters.Add("@Time_To", SqlDbType.Time).Value = Time_To;
        com.Parameters.Add("@KM_Start", SqlDbType.Int).Value = KM_Start;
        com.Parameters.Add("@KM_End", SqlDbType.Int).Value = KM_End;
        com.Parameters.Add("@Vehicle_Used_Byr", SqlDbType.VarChar, 100).Value = Vehicle_Used_By;
        com.Parameters.Add("@Cost_Code", SqlDbType.NVarChar, 50).Value = Cost_Code;
        com.Parameters.Add("@Budget_Line", SqlDbType.NVarChar, 50).Value = Budget_Line;
        com.Parameters.Add("@Entry_Date", SqlDbType.DateTime).Value = Entry_Date;
        con.Open();
        int res = com.ExecuteNonQuery();

    }
    catch (Exception ex)
    {
        WebMsgBox.Show(ex.Message);
    }
    finally
    {
        con.Close();
        con.Dispose();

    }
    return 1;
}
#endregion

Так что я должен использовать его в моем слое ИЛИ В пользовательском интерфейсе, или мой код в порядке. потому что, если я не использую try/catch в своем слое пользовательского интерфейса, он не поймает исключение (если оно есть) и покажет страницу с ошибкой.


person Gaurav    schedule 21.02.2013    source источник


Ответы (4)


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

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

Вы можете и должны использовать try/catch/(finally) почти везде, но...

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

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

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

Вы также можете сделать DataAccessException абстрактным и создать подклассы для более конкретных исключений, таких как SqlDataAccessException или SecurityDataAccessException.

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

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

Вы можете подумать: «Как я могу поймать конкретное исключение, если я еще не знаю, какие исключения будут сгенерированы?» Вы должны увидеть их задокументированными в методе ExecuteNonSql, и именно поэтому так важно документировать ваш собственный API/компонент с исключениями, которые он выдает. Для этого используйте XML-комментарии, а при отправке общедоступной библиотеки DLL включите генератор файлов XML-комментариев.

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

На этом этапе вашей жизни программиста я настоятельно рекомендую прочитать Руководство по проектированию фреймворка от Квалины и Абрамса. Это поможет вам быстро сделать правильный выбор во всех этих типах вопросов, и вы обнаружите, что использование собственного кода так же приятно, как и использование API Microsoft (в основном).

Люк

Добавлю немного о сообщениях. Я использую такие вещи в сообщении об ошибке.

"Невозможно {выполнить некоторую функцию}. Возник {тип исключения}. {Предложите совет по устранению или общие причины ошибки}. См. {внутреннее исключение|дополнительные записи журнала}".

Например, в компоненте для автосохранения состояния в приложении:

...
catch(FileNotFoundException fnfe)
{
    string m = String.Format("Cannot save changes. A FileNotFoundException occurred. Check the path '{0}' is valid, that your network is up, and any removable media is available. Please see inner exception.", path);

    _log.Error(m, fnfe);

    throw new StorageLifecycleException(m, fnfe);
}
person Luke Puplett    schedule 21.02.2013

Это просто зависит от того, вы можете использовать блоки try catch в обоих слоях.

Но проблема не в том, где вы используете код обработки исключений; проблема в том, как вы используете. В приведенном вами примере вы ловите общее exception, вы не знаете, является ли это SqlException или любым другим исключением.

В целом,

  1. ловить только те исключения, которые вы можете обработать (в примере поймать SqlException, а не все исключения)

  2. отображать удобное для пользователя сообщение (в вашем примере простое отображение сообщения об ошибке не имеет смысла для пользователя)

  3. зарегистрировать исключение

  4. обрабатывать исключение там, где оно возникает; если это исключение, связанное с DAL, обработайте его на уровне DAL, если это исключение, связанное с пользовательским интерфейсом, обработайте его на уровне пользовательского интерфейса.

person daryal    schedule 21.02.2013
comment
так что не могли бы вы уточнить, используя мой код? Я не использовал в 3 уровне - person Gaurav; 21.02.2013
comment
+1 К сообщению об ошибке. Мы все выросли на занудных сообщениях, поэтому мы думаем, что сообщения должны звучать занудно. Нет. Они должны звучать как друг, объясняющий проблему простыми словами и даже предлагающий возможные решения. - person Luke Puplett; 21.02.2013

Вы должны написать блок try-catch, в котором вы сможете обработать исключение. Не существует такой вещи, как «всегда ставить try-catch здесь или там». Я вижу, вы обрабатываете исключение следующим образом:

catch (Exception ex)
{
    WebMsgBox.Show(ex.Message);
}

Это плохо по нескольким причинам:

  1. Вы ловите общий тип Exception. Я видел вопрос об этом несколько дней назад: https://stackoverflow.com/a/14727026/238682
  2. Вы пытаетесь обработать исключение с помощью WebMsgBox.Show на уровне доступа к данным, что нарушает границы уровня.

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

public int Insert_LogBook(string Vehicle_Number, DateTime Vehicle_Booking_Date, TimeSpan Time_From, TimeSpan Time_To, int KM_Start, int KM_End, string Vehicle_Used_By, string Cost_Code, string Budget_Line, DateTime Entry_Date)
{
    using(SqlCommand com = new SqlCommand("Insert_LogBook", con))
    {
        com.Parameters.Add("@Vehicle_Number", SqlDbType.NVarChar, 100).Value = Vehicle_Number;
        com.Parameters.Add("@Vehicle_Booking_Date", SqlDbType.DateTime).Value = Vehicle_Booking_Date;
        com.Parameters.Add("@Time_From", SqlDbType.Time).Value = Time_From;
        com.Parameters.Add("@Time_To", SqlDbType.Time).Value = Time_To;
        com.Parameters.Add("@KM_Start", SqlDbType.Int).Value = KM_Start;
        com.Parameters.Add("@KM_End", SqlDbType.Int).Value = KM_End;
        com.Parameters.Add("@Vehicle_Used_Byr", SqlDbType.VarChar, 100).Value = Vehicle_Used_By;
        com.Parameters.Add("@Cost_Code", SqlDbType.NVarChar, 50).Value = Cost_Code;
        com.Parameters.Add("@Budget_Line", SqlDbType.NVarChar, 50).Value = Budget_Line;
        com.Parameters.Add("@Entry_Date", SqlDbType.DateTime).Value = Entry_Date;
        con.Open();
        int res = com.ExecuteNonQuery();

        return 1;
    }

}

public void SomeMethodWhichUsesThatInsert()
{
    try
    {
        //call Insert_LogBook
    }
    catch(SomeException e)
    {
        //handle
    }

}
person Peter Porfy    schedule 21.02.2013

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

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

person Laird Streak    schedule 21.02.2013