Должен ли я ловить исключения только для их регистрации? - PullRequest
27 голосов
/ 18 сентября 2008

Должен ли я перехватывать исключения для целей регистрации?

public foo(..)
{
   try
   {
     ...
   } catch (Exception ex) {
     Logger.Error(ex);
     throw;
   }
}

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

Имеет ли смысл делать это, если мои слои находятся в отдельных проектах и ​​только в общедоступных интерфейсах есть try / catch в них? Зачем? Почему бы и нет? Могу ли я использовать другой подход?

Ответы [ 15 ]

35 голосов
/ 18 сентября 2008

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

18 голосов
/ 18 сентября 2008

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

Моя лучшая практика:

  1. Только try / catch в открытых методах (в общем случае; очевидно, если вы перехватываете конкретную ошибку, вы бы там ее проверили)
  2. Входите только в слой пользовательского интерфейса непосредственно перед подавлением ошибки и перенаправлением на страницу / форму ошибки.
9 голосов
/ 18 сентября 2008

Общее правило заключается в том, что вы можете поймать исключение, только если действительно можете что-то с этим сделать. Таким образом, на уровне Business или Data вы могли бы поймать исключение только в такой ситуации:

            try
            {
                this.Persist(trans);
            }
            catch(Exception ex)
            {
                trans.Rollback();
                throw ex;
            }

Мой бизнес / Уровень данных пытается сохранить данные - если генерируется исключение, все транзакции откатываются и исключение отправляется на уровень пользовательского интерфейса.

На уровне пользовательского интерфейса вы можете реализовать общий обработчик исключений:

Application.ThreadException + = новый ThreadExceptionEventHandler (Application_ThreadException);

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

    static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
    {
        LogException(e.Exception);
    }
    static void LogException(Exception ex)
    {
        YYYExceptionHandling.HandleException(ex,
            YYYExceptionHandling.ExceptionPolicyType.YYY_Policy,
            YYYExceptionHandling.ExceptionPriority.Medium,
            "An error has occurred, please contact Administrator");
    } 

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

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

2 голосов
/ 18 сентября 2008

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

public void connect() throws ConnectionException {
   try {
       File conf = new File("blabla");
       ...
   } catch (FileNotFoundException ex) {
       LOGGER.error("log message", ex);
       throw new ConnectionException("The configuration file was not found", ex);
   }
}
1 голос
/ 24 октября 2008

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

Например (псевдокод Java):

public void methodWithDynamicallyGeneratedSQL() throws SQLException {
    String sql = ...; // Generate some SQL
    try {
        ... // Try running the query
    }
    catch (SQLException ex) {
        // Don't bother to log the stack trace, that will
        // be printed when the exception is handled for real
        logger.error(ex.toString()+"For SQL: '"+sql+"'");
        throw ex;  // Handle the exception long after the SQL is gone
    }
}

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

1 голос
/ 18 сентября 2008

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

в вашем примере вы ничего не делаете, только перехватываете исключение, регистрируете его и выбрасываете его снова ... почему бы просто не перехватить его на самом высоком уровне с помощью одной попытки / перехвата, а не внутри каждого метода, если все, что вы делаете, это ведение журнала это?

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

1 голос
/ 18 сентября 2008

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

0 голосов
/ 19 сентября 2008

Этот радиоподкаст Software Engineering является очень хорошим справочником по передовым методам обработки ошибок. На самом деле есть 2 лекции.

0 голосов
/ 18 сентября 2008

Вам необходимо разработать стратегию обработки исключений. Я не рекомендую ловить и перебрасывать. В дополнение к лишним записям в журнале это затрудняет чтение кода. Рассмотрим запись в журнал в конструкторе для исключения. Это резервирует try / catch для исключений, которые вы хотите восстановить; облегчая чтение кода Чтобы справиться с непредвиденными или неисправимыми исключениями, вы можете захотеть попробовать / поймать возле внешнего слоя программы для регистрации диагностической информации.

Кстати, если это C ++, ваш блок catch создает копию объекта исключения, который может стать потенциальным источником дополнительных проблем. Попробуйте перехватить ссылку на тип исключения:

  catch (const Exception& ex) { ... }
0 голосов
/ 18 сентября 2008

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

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

Я использую этот шаблон в бизнес-уровнях приложений, которые используют Remoting или веб-сервисы ASMX. С WCF вы можете перехватывать и регистрировать исключение, используя IErrorHandler, прикрепленный к вашему ChannelDispatcher (еще один предмет) - так что вам не нужен шаблон try / catch / throw.

...