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

StackOverflow https://stackoverflow.com/questions/95547

  •  01-07-2019
  •  | 
  •  

Вопрос

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

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

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

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

Это было полезно?

Решение

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

Другие советы

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

Моя лучшая практика заключается в следующем:

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

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

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

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

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

Приложение.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, - а не создавать исключение.

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

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);
   }
}

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

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

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

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

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

Например (псевдокод 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
    }
}

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

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

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

В одном из наших приложений все наши страницы являются производными от объекта BasePage, и этот объект обрабатывает обработку исключений и протоколирование ошибок.

Если это единственное, что он делает, я думаю, лучше удалить try / catch из этих классов и позволить исключению быть передано классу, который отвечает за их обработку.Таким образом, вы получаете только один журнал для каждого исключения, что дает вам более четкие журналы, и даже вы можете регистрировать stacktrace, чтобы вы не пропустили, откуда возникло исключение.

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

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

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

public foo(..)
{
   try
   {
     ...
   }
   catch (NullReferenceException ex) {
     DoSmth(e);
   }
   catch (ArgumentExcetion ex) {
     DoSmth(e);
   }
   catch (Exception ex) {
     DoSmth(e);
   }
}

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

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

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

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

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

  catch (const Exception& ex) { ... }

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top